Skip to content

Commit

Permalink
Get strikes from remote (#17)
Browse files Browse the repository at this point in the history
  • Loading branch information
tbsklg authored Aug 30, 2024
1 parent afde8c1 commit 8987dc4
Show file tree
Hide file tree
Showing 17 changed files with 438 additions and 147 deletions.
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,14 @@ terraform init
terraform plan
terraform apply
```
This will create a S3 bucket and a DynamoDB table to store the terraform state. Afterwards you can deploy the infrastructure by navigating to the infrastructure directory and running:
This will create a S3 bucket and a DynamoDB table to store the terraform state.

Then build releases for all lambda functions within the /lambdas folder:
```bash
cargo lambda build --release
```

Afterwards you can deploy the infrastructure by navigating to the infrastructure directory and running:
```bash
terraform init
terraform plan
Expand Down
2 changes: 1 addition & 1 deletion cli-client/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion cli-client/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "strikes"
version = "0.2.3"
version = "0.2.4"
edition = "2021"

[lib]
Expand Down
4 changes: 2 additions & 2 deletions cli-client/src/clients/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ use crate::tarnished::Tarnished;

#[async_trait]
pub trait StrikeClient {
async fn add_strike(&self, name: &str) -> Result<i8, String>;
fn get_tarnished(&self) -> Vec<Tarnished>;
async fn add_strike(&self, name: &str) -> Result<u8, String>;
async fn get_tarnished(&self) -> Result<Vec<Tarnished>, String>;
fn clear_strikes(&self);
async fn check_health(&self) -> Result<(), String>;
}
24 changes: 13 additions & 11 deletions cli-client/src/clients/local_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ pub struct LocalClient {

#[async_trait]
impl StrikeClient for LocalClient {
async fn add_strike(&self, name: &str) -> Result<i8, String> {
async fn add_strike(&self, name: &str) -> Result<u8, String> {
let db_path = &self.db_path;
if !db_path.exists() {
std::fs::create_dir_all(db_path.parent().unwrap()).unwrap();
}

let raw = std::fs::read_to_string(db_path).unwrap_or_else(|_| json!({}).to_string());
let db: &mut HashMap<String, i8> = &mut serde_json::from_str(&raw).unwrap();
let db: &mut HashMap<String, u8> = &mut serde_json::from_str(&raw).unwrap();
let count = db.get(name).unwrap_or(&0);
db.insert(name.to_string(), count + 1);

Expand All @@ -27,14 +27,14 @@ impl StrikeClient for LocalClient {
Ok(*db.get(name).unwrap())
}

fn get_tarnished(&self) -> Vec<Tarnished> {
async fn get_tarnished(&self) -> Result<Vec<Tarnished>, String> {
let db_path = &self.db_path;
let raw = std::fs::read_to_string(db_path).unwrap_or_else(|_| json!({}).to_string());
let db: HashMap<String, u8> = serde_json::from_str(&raw).unwrap_or(HashMap::new());

Tarnished::sort_desc_by_strike(Tarnished::as_tarnished(db))
Ok(Tarnished::sort_desc_by_strike(Tarnished::from_map(db))
.into_iter()
.collect()
.collect())
}

fn clear_strikes(&self) {
Expand Down Expand Up @@ -80,7 +80,7 @@ mod unit_tests {
.iter()
.cloned()
.collect::<HashMap<String, u8>>();
let tarnished = Tarnished::sort_desc_by_strike(Tarnished::as_tarnished(raw.clone()));
let tarnished = Tarnished::sort_desc_by_strike(Tarnished::from_map(raw.clone()));

assert_eq!(
tarnished,
Expand Down Expand Up @@ -117,7 +117,7 @@ mod integration_tests {
};

let _ = client.add_strike("guenther").await?;
let strikes = client.get_tarnished();
let strikes = client.get_tarnished().await.unwrap();

assert_eq!(
strikes,
Expand All @@ -140,7 +140,7 @@ mod integration_tests {
let _ = client.add_strike("heinz").await?;
let _ = client.add_strike("guenther").await?;

let strikes = client.get_tarnished();
let strikes = client.get_tarnished().await.unwrap();

assert_eq!(
strikes,
Expand All @@ -159,8 +159,8 @@ mod integration_tests {
Ok(())
}

#[test]
fn it_should_clear_strikes() -> Result<(), Box<dyn std::error::Error>> {
#[tokio::test]
async fn it_should_clear_strikes() -> Result<(), Box<dyn std::error::Error>> {
let file = assert_fs::NamedTempFile::new("./tests/fixtures/db.json")?;
let client = LocalClient {
db_path: file.to_path_buf(),
Expand All @@ -171,7 +171,9 @@ mod integration_tests {

client.clear_strikes();

assert!(client.get_tarnished().is_empty());
let strikes = client.get_tarnished().await.unwrap();

assert!(strikes.is_empty());

Ok(())
}
Expand Down
84 changes: 74 additions & 10 deletions cli-client/src/clients/remote_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@ struct HttpClient {
}

#[derive(serde::Deserialize)]
struct StrikeResponse {
strike_count: i8,
pub struct StrikesResponse {
pub name: String,
pub strike_count: u8,
}

#[async_trait]
impl StrikeClient for RemoteClient {
async fn add_strike(&self, username: &str) -> Result<i8, String> {
async fn add_strike(&self, username: &str) -> Result<u8, String> {
let client = HttpClient {
base_url: self.base_url.clone(),
api_key: self.api_key.clone(),
Expand All @@ -30,8 +31,13 @@ impl StrikeClient for RemoteClient {
client.put_strike(username).await
}

fn get_tarnished(&self) -> Vec<Tarnished> {
vec![]
async fn get_tarnished(&self) -> Result<Vec<Tarnished>, String> {
let client = HttpClient {
base_url: self.base_url.clone(),
api_key: self.api_key.clone(),
};

client.get_strikes().await
}

fn clear_strikes(&self) {}
Expand Down Expand Up @@ -66,7 +72,7 @@ impl HttpClient {
}
}

async fn put_strike(&self, username: &str) -> Result<i8, String> {
async fn put_strike(&self, username: &str) -> Result<u8, String> {
let client = reqwest::Client::new();
let response = client
.put(format!("{}/strikes/{}", &self.base_url, username))
Expand All @@ -78,27 +84,49 @@ impl HttpClient {
match response.status() {
reqwest::StatusCode::OK => {
let body = response.text().await.expect("Failed to read response body");
Ok(serde_json::from_str::<StrikeResponse>(&body)
Ok(serde_json::from_str::<StrikesResponse>(&body)
.expect("Failed to parse response")
.strike_count)
}
err => Err(err.to_string()),
}
}

async fn get_strikes(&self) -> Result<Vec<Tarnished>, String> {
let client = reqwest::Client::new();
let response = client
.get(format!("{}/strikes", &self.base_url))
.header("x-api-key", &self.api_key)
.send()
.await
.expect("Failed to execute request");

match response.status() {
reqwest::StatusCode::OK => {
let body = response.text().await.expect("Failed to read response body");
Ok(Tarnished::from_vec(
serde_json::from_str::<Vec<StrikesResponse>>(&body)
.expect("Faild to parse response"),
))
}
err => Err(err.to_string()),
}
}
}

#[cfg(test)]
mod unittests {
mod unit_tests {
use wiremock::{matchers::any, Mock, MockServer, ResponseTemplate};

use crate::clients::remote_client::HttpClient;
use crate::{clients::remote_client::HttpClient, tarnished::Tarnished};

#[tokio::test]
async fn it_should_add_a_strike() -> Result<(), Box<dyn std::error::Error>> {
let mock_server = MockServer::start().await;
Mock::given(any())
.respond_with(
ResponseTemplate::new(200).set_body_json(serde_json::json!({"strike_count": 3})),
ResponseTemplate::new(200)
.set_body_json(serde_json::json!({"name": "guenther", "strike_count": 3})),
)
.expect(1)
.mount(&mock_server)
Expand All @@ -115,4 +143,40 @@ mod unittests {

Ok(())
}

#[tokio::test]
async fn it_should_fetch_all_strikes() -> Result<(), Box<dyn std::error::Error>> {
let mock_server = MockServer::start().await;
Mock::given(any())
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!([
{"name": "guenther", "strike_count": 2},
{"name": "heinz", "strike_count": 3},
])))
.expect(1)
.mount(&mock_server)
.await;

let client = HttpClient {
api_key: "abc".to_string(),
base_url: mock_server.uri(),
};

let strikes = client.get_strikes().await?;

assert_eq!(
vec![
Tarnished {
name: "guenther".to_string(),
strikes: 2,
},
Tarnished {
name: "heinz".to_string(),
strikes: 3,
}
],
strikes
);

Ok(())
}
}
14 changes: 4 additions & 10 deletions cli-client/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,10 @@ async fn main() {
Ok(strikes) => print_strikes(name, strikes),
Err(err) => eprintln!("Failed to add strike: {}", err),
},
Command::Ls => {
let tarnished = client.get_tarnished();

if tarnished.is_empty() {
println!("No one has been tarnished yet!");
return;
}

print_as_table(tarnished);
}
Command::Ls => match client.get_tarnished().await {
Ok(tarnished) => print_as_table(tarnished),
Err(err) => eprintln!("Failed to get strikes: {}", err),
},
Command::Clear => {
client.clear_strikes();
println!("All strikes have been cleared!");
Expand Down
7 changes: 6 additions & 1 deletion cli-client/src/output.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
use crate::tarnished::Tarnished;

pub fn print_as_table(tarnished: Vec<Tarnished>) {
if tarnished.is_empty() {
println!("No one has been tarnished yet!");
return;
}

println!("{0: <10} | {1: <10} |", "Tarnished", "Strikes");
for tarnished in tarnished {
println!("{0: <10} | {1: <10} |", tarnished.name, tarnished.strikes);
}
}

pub fn print_strikes(name: &str, strikes: i8) {
pub fn print_strikes(name: &str, strikes: u8) {
println!("{} has now {} strikes!", name, strikes);
}
13 changes: 12 additions & 1 deletion cli-client/src/tarnished.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use std::collections::HashMap;

use crate::clients::remote_client::StrikesResponse;

#[derive(Debug, PartialEq, Clone)]
pub struct Tarnished {
pub name: String,
Expand All @@ -13,12 +15,21 @@ impl Tarnished {
tarnished
}

pub fn as_tarnished(db: HashMap<String, u8>) -> Vec<Tarnished> {
pub fn from_map(db: HashMap<String, u8>) -> Vec<Tarnished> {
db.iter()
.map(|(name, strikes)| Tarnished {
name: name.to_string(),
strikes: *strikes,
})
.collect()
}

pub fn from_vec(sr: Vec<StrikesResponse>) -> Vec<Tarnished> {
sr.iter()
.map(|StrikesResponse { name, strike_count }| Tarnished {
name: name.to_string(),
strikes: *strike_count,
})
.collect()
}
}
11 changes: 9 additions & 2 deletions infrastructure/lambdas/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,20 @@ version = "0.1.0"
edition = "2021"

[[bin]]
path = "src/put_strikes.rs"
name = "put_strikes"
path = "src/put_strike.rs"
name = "put_strike"

[[bin]]
path = "src/health.rs"
name = "health"

[[bin]]
path = "src/get_strikes.rs"
name = "get_strikes"

[lib]
path = "src/lib.rs"

[dependencies]
aws-config = "1.5.5"
aws-sdk-dynamodb = "1.42.0"
Expand Down
Loading

0 comments on commit 8987dc4

Please sign in to comment.