diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..aa81982 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,40 @@ +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/* diff --git a/.woodpecker.yml b/.woodpecker.yml deleted file mode 100644 index 0f82769..0000000 --- a/.woodpecker.yml +++ /dev/null @@ -1,27 +0,0 @@ -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 diff --git a/bank/src/account.rs b/bank/src/account.rs index 10c3cd2..80dce03 100644 --- a/bank/src/account.rs +++ b/bank/src/account.rs @@ -1,4 +1,3 @@ -use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; use thiserror::Error; @@ -26,23 +25,6 @@ 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 { @@ -60,6 +42,12 @@ 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 b3e62d8..556acc0 100644 --- a/bank/src/bank.rs +++ b/bank/src/bank.rs @@ -13,11 +13,11 @@ impl Bank { Default::default() } - pub fn account_action Result, R>( + pub fn account_action Result>( 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/index.html b/http-client/index.html index 42ec302..0eb7c67 100644 --- a/http-client/index.html +++ b/http-client/index.html @@ -4,6 +4,5 @@ Vesys Bank - \ No newline at end of file diff --git a/http-client/src/api.rs b/http-client/src/api.rs index 523afd7..e6347d5 100644 --- a/http-client/src/api.rs +++ b/http-client/src/api.rs @@ -27,14 +27,8 @@ pub async fn fetch_account(host: &str, nr: &str) -> Result Result<(), FetchError> { +pub async fn set_balance(host: &str, nr: &str, balance: f64) -> Result<(), FetchError> { let data = JsValue::from_str(&format!("amount={}", balance)); - client::put(&format! {"{}/accounts/{}", host, nr}, data, Some(etag)).await?; - + client::put(&format! {"{}/accounts/{}", host, nr}, data).await?; Ok(()) } diff --git a/http-client/src/client.rs b/http-client/src/client.rs index 7ab4729..bc88630 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, if_match: Option) -> Result<(), FetchError> { +pub async fn put(url: &str, data: JsValue) -> Result<(), FetchError> { let mut opts = RequestInit::new(); opts.method("PUT"); opts.mode(RequestMode::Cors); @@ -63,11 +63,6 @@ pub async fn put(url: &str, data: JsValue, if_match: Option) -> Result<( 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/account.rs b/http-client/src/components/account.rs index 8d66bb7..d913797 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/src/components/accounts.rs b/http-client/src/components/accounts.rs index b178e84..c484b93 100644 --- a/http-client/src/components/accounts.rs +++ b/http-client/src/components/accounts.rs @@ -40,13 +40,9 @@ impl Component for Accounts { fn view(&self, ctx: &Context) -> Html { html! { <> - 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)) } -
- } +
    + { 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 b226be5..078fd03 100644 --- a/http-client/src/components/main.rs +++ b/http-client/src/components/main.rs @@ -77,17 +77,8 @@ 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, hash).await { + match api::set_balance("http://localhost:8000", &nr, balance).await { Err(e) => Event::ShowError(e.to_string()), Ok(_) => Event::SelectAccountNr(nr), } @@ -108,12 +99,8 @@ impl Component for Main {

{"welcome to your vaults"}

-
- - if !self.account_nrs.is_empty() { - - } -
+ + } diff --git a/http-client/styles.css b/http-client/styles.css index 030d40b..2d0b893 100644 --- a/http-client/styles.css +++ b/http-client/styles.css @@ -7,19 +7,12 @@ html, body { } .title { - margin-top: 0; -} - -.inner-content { - display: flex; - justify-content: flex-start; - gap: 10px; + margin: 0; } .account { border-style: solid; border-color: black; - flex-grow: 1; } .account__grid { @@ -32,7 +25,6 @@ html, body { background-color: black; color: white; padding: 3px 6px; - margin-left: 7px; } .account__label { @@ -55,21 +47,6 @@ 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; @@ -109,9 +86,6 @@ 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; @@ -140,19 +114,14 @@ html, body { min-width: 370px; } -@media (max-width: 1005px) { - .inner-content { - flex-direction: column; - } -} - -@media (max-width: 615px) { - .accounts { - display: block; - } - +@media (max-width: 605px) { .account__grid { - display: flex; - flex-direction: column; + display: grid; + grid-template-columns: 1fr; + } + + .account__accounts { + grid-column: 1; + grid-row: 2; } } diff --git a/http-server/src/handlers/update_account.rs b/http-server/src/handlers/update_account.rs index 76cd9c8..a0cbaf6 100644 --- a/http-server/src/handlers/update_account.rs +++ b/http-server/src/handlers/update_account.rs @@ -1,7 +1,9 @@ -use actix_web::{put, web, HttpRequest, HttpResponse, Responder, Result}; +use actix_web::{put, web, 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; @@ -16,7 +18,6 @@ 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; @@ -28,22 +29,11 @@ pub async fn route( info!("updating account {}...", nr); let bank = data.bank.read().unwrap(); - 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()) - } - } + match Bank::account_action(bank, &nr, |account| { + account.balance = amount; + Ok(amount) + }) { + Err(e) => Err(HttpAccountError(e).into()), + Ok(_) => Ok(HttpResponse::Ok().finish()), } } diff --git a/http-server/src/main.rs b/http-server/src/main.rs index b15e967..0e41684 100644 --- a/http-server/src/main.rs +++ b/http-server/src/main.rs @@ -155,30 +155,6 @@ 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()