From 4f1b866ca57e6d4776f9ccdce131f4f8a04b9cea Mon Sep 17 00:00:00 2001 From: Sebastian Hugentobler Date: Sat, 26 Mar 2022 12:38:27 +0100 Subject: [PATCH 01/10] align account title properly --- http-client/styles.css | 1 + 1 file changed, 1 insertion(+) diff --git a/http-client/styles.css b/http-client/styles.css index 2d0b893..c8c0214 100644 --- a/http-client/styles.css +++ b/http-client/styles.css @@ -25,6 +25,7 @@ html, body { background-color: black; color: white; padding: 3px 6px; + margin-left: 7px; } .account__label { From 430547f821faa3c8c6725bc43305ce1d9a853e4b Mon Sep 17 00:00:00 2001 From: Sebastian Hugentobler Date: Sat, 26 Mar 2022 16:13:18 +0100 Subject: [PATCH 02/10] fix layout switching between grid and flexbox --- http-client/src/components/account.rs | 6 +++--- http-client/styles.css | 24 +++++++++++++++++------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/http-client/src/components/account.rs b/http-client/src/components/account.rs index d913797..8d66bb7 100644 --- a/http-client/src/components/account.rs +++ b/http-client/src/components/account.rs @@ -159,9 +159,9 @@ impl Component for Account { - - - + + + diff --git a/http-client/styles.css b/http-client/styles.css index c8c0214..6487a2b 100644 --- a/http-client/styles.css +++ b/http-client/styles.css @@ -48,6 +48,21 @@ html, body { flex-direction: column; } +.account__deposit { + grid-column: 1; + grid-row: 2; +} + +.account__withdraw { + grid-column: 2; + grid-row: 2; +} + +.account__transfer { + grid-column: 3; + grid-row: 2; +} + .account__button { color: black; border-radius: 0; @@ -117,12 +132,7 @@ html, body { @media (max-width: 605px) { .account__grid { - display: grid; - grid-template-columns: 1fr; - } - - .account__accounts { - grid-column: 1; - grid-row: 2; + display: flex; + flex-direction: column; } } From 2808fb490a4baba4dc3555942ddd3054f0fdafeb Mon Sep 17 00:00:00 2001 From: Sebastian Hugentobler Date: Sat, 26 Mar 2022 16:31:43 +0100 Subject: [PATCH 03/10] make the layout more responsive --- http-client/src/components/main.rs | 6 ++++-- http-client/styles.css | 24 ++++++++++++++++++++++-- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/http-client/src/components/main.rs b/http-client/src/components/main.rs index 078fd03..39cc525 100644 --- a/http-client/src/components/main.rs +++ b/http-client/src/components/main.rs @@ -99,8 +99,10 @@ impl Component for Main {

{"welcome to your vaults"}

- - +
+ + +
} diff --git a/http-client/styles.css b/http-client/styles.css index 6487a2b..030d40b 100644 --- a/http-client/styles.css +++ b/http-client/styles.css @@ -7,12 +7,19 @@ html, body { } .title { - margin: 0; + margin-top: 0; +} + +.inner-content { + display: flex; + justify-content: flex-start; + gap: 10px; } .account { border-style: solid; border-color: black; + flex-grow: 1; } .account__grid { @@ -102,6 +109,9 @@ html, body { .accounts { list-style-type: none; + display: inline-block; + min-width: min-content; + margin: 0; padding: 0.5em; border-style: solid; border-color: black; @@ -130,7 +140,17 @@ html, body { min-width: 370px; } -@media (max-width: 605px) { +@media (max-width: 1005px) { + .inner-content { + flex-direction: column; + } +} + +@media (max-width: 615px) { + .accounts { + display: block; + } + .account__grid { display: flex; flex-direction: column; From 9e37ddc955b7848edd122cabfaf900d04953f247 Mon Sep 17 00:00:00 2001 From: Sebastian Hugentobler Date: Sat, 26 Mar 2022 17:11:17 +0100 Subject: [PATCH 04/10] fix device width on mobile --- http-client/index.html | 1 + 1 file changed, 1 insertion(+) diff --git a/http-client/index.html b/http-client/index.html index 0eb7c67..42ec302 100644 --- a/http-client/index.html +++ b/http-client/index.html @@ -4,5 +4,6 @@ Vesys Bank + \ No newline at end of file From 36e35768c193463c8ec7da2af6e171deba3fdcd2 Mon Sep 17 00:00:00 2001 From: Sebastian Hugentobler Date: Sat, 26 Mar 2022 17:17:43 +0100 Subject: [PATCH 05/10] do not show account details if there are no accounts --- http-client/src/components/accounts.rs | 10 +++++++--- http-client/src/components/main.rs | 6 ++++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/http-client/src/components/accounts.rs b/http-client/src/components/accounts.rs index c484b93..b550737 100644 --- a/http-client/src/components/accounts.rs +++ b/http-client/src/components/accounts.rs @@ -40,9 +40,13 @@ impl Component for Accounts { fn view(&self, ctx: &Context) -> Html { html! { <> -
    - { for ctx.props().account_nrs.iter().map(|e| self.account_entry(e, &ctx.props().selected_nr, ctx)) } -
+ if ctx.props().account_nrs.is_empty() { +

{"No accounts"}

+ } else { +
    + { for ctx.props().account_nrs.iter().map(|e| self.account_entry(e, &ctx.props().selected_nr, ctx)) } +
+ } } } diff --git a/http-client/src/components/main.rs b/http-client/src/components/main.rs index 39cc525..6b73c3a 100644 --- a/http-client/src/components/main.rs +++ b/http-client/src/components/main.rs @@ -100,8 +100,10 @@ impl Component for Main { {"welcome to your vaults"}
- - + + if !self.account_nrs.is_empty() { + + }
From b4fcbdfef8417d775b1f0d8b35764aa1f72d8379 Mon Sep 17 00:00:00 2001 From: Sebastian Hugentobler Date: Sat, 26 Mar 2022 17:19:04 +0100 Subject: [PATCH 06/10] fix intendation in accounts.rs --- http-client/src/components/accounts.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/http-client/src/components/accounts.rs b/http-client/src/components/accounts.rs index b550737..b178e84 100644 --- a/http-client/src/components/accounts.rs +++ b/http-client/src/components/accounts.rs @@ -41,7 +41,7 @@ impl Component for Accounts { html! { <> if ctx.props().account_nrs.is_empty() { -

{"No accounts"}

+

{"No accounts"}

} else {
    { for ctx.props().account_nrs.iter().map(|e| self.account_entry(e, &ctx.props().selected_nr, ctx)) } From 547b7b375a41869f47983569667a60896dcca3b3 Mon Sep 17 00:00:00 2001 From: Sebastian Hugentobler Date: Wed, 30 Mar 2022 09:36:59 +0200 Subject: [PATCH 07/10] add conditional put --- bank/src/account.rs | 24 ++++++++++++++----- bank/src/bank.rs | 4 ++-- http-client/src/api.rs | 10 ++++++-- http-client/src/client.rs | 7 +++++- http-client/src/components/main.rs | 11 ++++++++- http-server/src/handlers/update_account.rs | 28 +++++++++++++++------- http-server/src/main.rs | 24 +++++++++++++++++++ 7 files changed, 87 insertions(+), 21 deletions(-) diff --git a/bank/src/account.rs b/bank/src/account.rs index 80dce03..10c3cd2 100644 --- a/bank/src/account.rs +++ b/bank/src/account.rs @@ -1,3 +1,4 @@ +use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; use thiserror::Error; @@ -25,6 +26,23 @@ pub struct Account { pub is_active: bool, } +impl Account { + pub fn hash_string(&self) -> String { + let mut hasher = DefaultHasher::new(); + self.hash(&mut hasher); + format!("{:x}", hasher.finish()) + } +} + +impl Hash for Account { + fn hash(&self, state: &mut H) { + self.number.hash(state); + self.owner.hash(state); + self.balance.to_string().hash(state); + self.is_active.hash(state); + } +} + impl Default for Account { fn default() -> Self { Account { @@ -42,12 +60,6 @@ impl PartialEq for Account { } } -impl Hash for Account { - fn hash(&self, state: &mut H) { - self.number.hash(state); - } -} - impl Account { #[cfg(test)] pub fn new() -> Self { diff --git a/bank/src/bank.rs b/bank/src/bank.rs index 556acc0..b3e62d8 100644 --- a/bank/src/bank.rs +++ b/bank/src/bank.rs @@ -13,11 +13,11 @@ impl Bank { Default::default() } - pub fn account_action Result>( + pub fn account_action Result, R>( bank: RwLockReadGuard<'_, Bank>, nr: &str, action: F, - ) -> Result { + ) -> Result { match bank.accounts.get(nr) { None => Err(AccountError::NotFound), Some(account) => { diff --git a/http-client/src/api.rs b/http-client/src/api.rs index e6347d5..523afd7 100644 --- a/http-client/src/api.rs +++ b/http-client/src/api.rs @@ -27,8 +27,14 @@ pub async fn fetch_account(host: &str, nr: &str) -> Result Result<(), FetchError> { +pub async fn set_balance( + host: &str, + nr: &str, + balance: f64, + etag: String, +) -> Result<(), FetchError> { let data = JsValue::from_str(&format!("amount={}", balance)); - client::put(&format! {"{}/accounts/{}", host, nr}, data).await?; + client::put(&format! {"{}/accounts/{}", host, nr}, data, Some(etag)).await?; + Ok(()) } diff --git a/http-client/src/client.rs b/http-client/src/client.rs index bc88630..7ab4729 100644 --- a/http-client/src/client.rs +++ b/http-client/src/client.rs @@ -53,7 +53,7 @@ pub async fn get_json(url: &str) -> Result, FetchError> { Ok(text.as_string()) } -pub async fn put(url: &str, data: JsValue) -> Result<(), FetchError> { +pub async fn put(url: &str, data: JsValue, if_match: Option) -> Result<(), FetchError> { let mut opts = RequestInit::new(); opts.method("PUT"); opts.mode(RequestMode::Cors); @@ -63,6 +63,11 @@ pub async fn put(url: &str, data: JsValue) -> Result<(), FetchError> { request .headers() .set("Content-Type", "application/x-www-form-urlencoded")?; + + if let Some(etag) = if_match { + request.headers().set("If-match", &etag)?; + } + self::send_request(request).await?; Ok(()) diff --git a/http-client/src/components/main.rs b/http-client/src/components/main.rs index 6b73c3a..b226be5 100644 --- a/http-client/src/components/main.rs +++ b/http-client/src/components/main.rs @@ -77,8 +77,17 @@ impl Component for Main { true } Event::SetBalance(balance, nr) => { + let acc = bank::account::Account { + number: nr.clone(), + owner: self.selected_owner.clone(), + balance: self.selected_balance, + is_active: true, + }; + + let hash = acc.hash_string(); + ctx.link().send_future(async move { - match api::set_balance("http://localhost:8000", &nr, balance).await { + match api::set_balance("http://localhost:8000", &nr, balance, hash).await { Err(e) => Event::ShowError(e.to_string()), Ok(_) => Event::SelectAccountNr(nr), } diff --git a/http-server/src/handlers/update_account.rs b/http-server/src/handlers/update_account.rs index a0cbaf6..76cd9c8 100644 --- a/http-server/src/handlers/update_account.rs +++ b/http-server/src/handlers/update_account.rs @@ -1,9 +1,7 @@ -use actix_web::{put, web, HttpResponse, Responder, Result}; +use actix_web::{put, web, HttpRequest, HttpResponse, Responder, Result}; use bank::account::AccountError; use serde::Serialize; -use bank::bank::Bank; - use crate::handlers::error::HttpAccountError; use crate::handlers::AmountData; use crate::AppState; @@ -18,6 +16,7 @@ pub async fn route( info: web::Path, form: web::Form, data: web::Data, + req: HttpRequest, ) -> Result { let nr = info.into_inner(); let amount = form.amount; @@ -29,11 +28,22 @@ pub async fn route( info!("updating account {}...", nr); let bank = data.bank.read().unwrap(); - match Bank::account_action(bank, &nr, |account| { - account.balance = amount; - Ok(amount) - }) { - Err(e) => Err(HttpAccountError(e).into()), - Ok(_) => Ok(HttpResponse::Ok().finish()), + match bank.accounts.get(&nr) { + None => Err(HttpAccountError(AccountError::NotFound).into()), + Some(account) => { + let mut account = account.write().unwrap(); + let hash = account.hash_string(); + if let Some(etag) = req.headers().get("If-Match") { + let etag = etag.to_str().unwrap_or(""); + if etag != hash { + Ok(HttpResponse::PreconditionFailed().finish()) + } else { + account.balance = amount; + Ok(HttpResponse::Ok().finish()) + } + } else { + Ok(HttpResponse::PreconditionRequired().finish()) + } + } } } diff --git a/http-server/src/main.rs b/http-server/src/main.rs index 0e41684..cbf07ae 100644 --- a/http-server/src/main.rs +++ b/http-server/src/main.rs @@ -155,6 +155,30 @@ mod tests { .set_form(&payload) .to_request(); let resp = test::call_service(&app, req).await; + assert_eq!(resp.status(), StatusCode::PRECONDITION_REQUIRED); + + let payload = AmountData { amount: 10_f64 }; + let req = test::TestRequest::put() + .uri(&format!("/accounts/{}", nr)) + .append_header((actix_web::http::header::IF_MATCH, "I aspire to be an etag")) + .set_form(&payload) + .to_request(); + let resp = test::call_service(&app, req).await; + assert_eq!(resp.status(), StatusCode::PRECONDITION_FAILED); + + let acc = bank::account::Account { + number: nr.to_string(), + owner: "aaa".to_string(), + balance: 5_f64, + is_active: true + }; + let payload = AmountData { amount: 10_f64 }; + let req = test::TestRequest::put() + .uri(&format!("/accounts/{}", nr)) + .append_header((actix_web::http::header::IF_MATCH, acc.hash_string())) + .set_form(&payload) + .to_request(); + let resp = test::call_service(&app, req).await; assert!(resp.status().is_success()); let req = test::TestRequest::put() From 7f4bb68b9dad3a647b180d87d3addaa028d696d1 Mon Sep 17 00:00:00 2001 From: Sebastian Hugentobler Date: Wed, 30 Mar 2022 10:45:15 +0200 Subject: [PATCH 08/10] full reformat --- http-server/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/http-server/src/main.rs b/http-server/src/main.rs index cbf07ae..b15e967 100644 --- a/http-server/src/main.rs +++ b/http-server/src/main.rs @@ -170,7 +170,7 @@ mod tests { number: nr.to_string(), owner: "aaa".to_string(), balance: 5_f64, - is_active: true + is_active: true, }; let payload = AmountData { amount: 10_f64 }; let req = test::TestRequest::put() From e586fe033d00335d6a0708bae348b95bfac15631 Mon Sep 17 00:00:00 2001 From: Sebastian Hugentobler Date: Fri, 11 Nov 2022 08:09:57 +0000 Subject: [PATCH 09/10] Add '.woodpecker.yml' --- .woodpecker.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 .woodpecker.yml diff --git a/.woodpecker.yml b/.woodpecker.yml new file mode 100644 index 0000000..0f82769 --- /dev/null +++ b/.woodpecker.yml @@ -0,0 +1,27 @@ +pipeline: + test: + image: docker.io/rust:1.59-alpine3.15 + commands: + - apk --no-cache add musl-dev + - cargo test + audit: + image: docker.io/rust:1.59-alpine3.15 + commands: + - apk --no-cache add musl-dev cargo-audit + - cargo audit + build-statically: + image: docker.io/rust:1.59-alpine3.15 + commands: + - apk --no-cache add musl-dev make rustup cargo + - rustup target add x86_64-unknown-linux-musl aarch64-unknown-linux-musl x86_64-pc-windows-gnu wasm32-unknown-unknown + - cargo install trunk + - wget https://musl.cc/aarch64-linux-musl-cross.tgz + - echo "c909817856d6ceda86aa510894fa3527eac7989f0ef6e87b5721c58737a06c38 aarch64-linux-musl-cross.tgz" | sha256sum -c - || exit 1 + - tar -zxvf aarch64-linux-musl-cross.tgz -C / --exclude='aarch64-linux-musl-cross/usr' --strip 1 + - wget https://musl.cc/x86_64-w64-mingw32-cross.tgz + - echo "3a5c90309209a8b2e7ea1715a34b1029692e34189c5e7ecd77e1f102f82f6a02 x86_64-w64-mingw32-cross.tgz" | sha256sum -c - || exit 1 + - tar -zxvf x86_64-w64-mingw32-cross.tgz -C / --exclude='./x86_64-w64-mingw32-cross/usr' --strip 2 + - wget https://musl.cc/x86_64-linux-musl-cross.tgz + - echo "c5d410d9f82a4f24c549fe5d24f988f85b2679b452413a9f7e5f7b956f2fe7ea x86_64-linux-musl-cross.tgz" | sha256sum -c - || exit 1 + - tar -zxvf x86_64-linux-musl-cross.tgz -C / --exclude='x86_64-linux-musl-cross/usr' --strip 1 + - make all \ No newline at end of file From 409602c825318e2f7c9ed2be4dff03728c21f8fe Mon Sep 17 00:00:00 2001 From: Sebastian Hugentobler Date: Fri, 11 Nov 2022 08:10:07 +0000 Subject: [PATCH 10/10] Delete '.gitlab-ci.yml' --- .gitlab-ci.yml | 40 ---------------------------------------- 1 file changed, 40 deletions(-) delete mode 100644 .gitlab-ci.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index aa81982..0000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,40 +0,0 @@ -image: rust:1.59-alpine3.15 - -stages: - - test - - build - -test: - stage: test - before_script: - - apk --no-cache add musl-dev - script: - - cargo test - -audit: - stage: test - before_script: - - apk --no-cache add musl-dev cargo-audit - script: - - cargo audit - -build-statically: - stage: build - before_script: - - apk --no-cache add musl-dev make rustup cargo - - rustup target add x86_64-unknown-linux-musl aarch64-unknown-linux-musl x86_64-pc-windows-gnu wasm32-unknown-unknown - - cargo install trunk - - wget https://musl.cc/aarch64-linux-musl-cross.tgz - - echo "c909817856d6ceda86aa510894fa3527eac7989f0ef6e87b5721c58737a06c38 aarch64-linux-musl-cross.tgz" | sha256sum -c - || exit 1 - - tar -zxvf aarch64-linux-musl-cross.tgz -C / --exclude='aarch64-linux-musl-cross/usr' --strip 1 - - wget https://musl.cc/x86_64-w64-mingw32-cross.tgz - - echo "3a5c90309209a8b2e7ea1715a34b1029692e34189c5e7ecd77e1f102f82f6a02 x86_64-w64-mingw32-cross.tgz" | sha256sum -c - || exit 1 - - tar -zxvf x86_64-w64-mingw32-cross.tgz -C / --exclude='./x86_64-w64-mingw32-cross/usr' --strip 2 - - wget https://musl.cc/x86_64-linux-musl-cross.tgz - - echo "c5d410d9f82a4f24c549fe5d24f988f85b2679b452413a9f7e5f7b956f2fe7ea x86_64-linux-musl-cross.tgz" | sha256sum -c - || exit 1 - - tar -zxvf x86_64-linux-musl-cross.tgz -C / --exclude='x86_64-linux-musl-cross/usr' --strip 1 - script: - - make all - artifacts: - paths: - - release/*