diff --git a/README.md b/README.md index 3a88f92..8dc9c50 100644 --- a/README.md +++ b/README.md @@ -137,7 +137,7 @@ brewlog bag Manage bags of coffee brewlog gear Manage brewing gear (grinders, brewers, filter papers) brewlog brew Manage brews (add, list, get, delete — no update) brewlog cafe Manage cafes -brewlog cup Manage cups (cafe visits with ratings) +brewlog cup Manage cups (cafe visits) brewlog token Manage API tokens (create, list, revoke) brewlog backup Export all data to JSON on stdout (requires BREWLOG_TOKEN) brewlog restore --file F Restore data from a JSON backup into an empty database (requires BREWLOG_TOKEN) diff --git a/migrations/0020_remove_cup_rating.sql b/migrations/0020_remove_cup_rating.sql new file mode 100644 index 0000000..b639543 --- /dev/null +++ b/migrations/0020_remove_cup_rating.sql @@ -0,0 +1 @@ +ALTER TABLE cups DROP COLUMN rating; diff --git a/scripts/bootstrap-db.sh b/scripts/bootstrap-db.sh index 36a2343..0da21f7 100755 --- a/scripts/bootstrap-db.sh +++ b/scripts/bootstrap-db.sh @@ -503,40 +503,33 @@ fi # Monmouth Coffee (London) - 2 cups ./target/debug/brewlog cup add \ --roast-id "$(./target/debug/brewlog roast list | jq -r '.[] | select(.name=="Red Brick Espresso") | .id')" \ - --cafe-id "$(./target/debug/brewlog cafe list | jq -r '.[] | select(.name=="Monmouth Coffee") | .id')" \ - --rating 4 + --cafe-id "$(./target/debug/brewlog cafe list | jq -r '.[] | select(.name=="Monmouth Coffee") | .id')" ./target/debug/brewlog cup add \ --roast-id "$(./target/debug/brewlog roast list | jq -r '.[] | select(.name=="Kamwangi") | .id')" \ - --cafe-id "$(./target/debug/brewlog cafe list | jq -r '.[] | select(.name=="Monmouth Coffee") | .id')" \ - --rating 5 + --cafe-id "$(./target/debug/brewlog cafe list | jq -r '.[] | select(.name=="Monmouth Coffee") | .id')" # Prufrock Coffee (London) - 3 cups ./target/debug/brewlog cup add \ --roast-id "$(./target/debug/brewlog roast list | jq -r '.[] | select(.name=="Kochere") | .id')" \ - --cafe-id "$(./target/debug/brewlog cafe list | jq -r '.[] | select(.name=="Prufrock Coffee") | .id')" \ - --rating 5 + --cafe-id "$(./target/debug/brewlog cafe list | jq -r '.[] | select(.name=="Prufrock Coffee") | .id')" ./target/debug/brewlog cup add \ --roast-id "$(./target/debug/brewlog roast list | jq -r '.[] | select(.name=="La Laja") | .id')" \ - --cafe-id "$(./target/debug/brewlog cafe list | jq -r '.[] | select(.name=="Prufrock Coffee") | .id')" \ - --rating 4 + --cafe-id "$(./target/debug/brewlog cafe list | jq -r '.[] | select(.name=="Prufrock Coffee") | .id')" ./target/debug/brewlog cup add \ --roast-id "$(./target/debug/brewlog roast list | jq -r '.[] | select(.name=="Ben Saïd Natural") | .id')" \ - --cafe-id "$(./target/debug/brewlog cafe list | jq -r '.[] | select(.name=="Prufrock Coffee") | .id')" \ - --rating 5 + --cafe-id "$(./target/debug/brewlog cafe list | jq -r '.[] | select(.name=="Prufrock Coffee") | .id')" # Hola Coffee (Madrid) - 2 cups ./target/debug/brewlog cup add \ --roast-id "$(./target/debug/brewlog roast list | jq -r '.[] | select(.name=="Cerro Azul") | .id')" \ - --cafe-id "$(./target/debug/brewlog cafe list | jq -r '.[] | select(.name=="Hola Coffee") | .id')" \ - --rating 4 + --cafe-id "$(./target/debug/brewlog cafe list | jq -r '.[] | select(.name=="Hola Coffee") | .id')" ./target/debug/brewlog cup add \ --roast-id "$(./target/debug/brewlog roast list | jq -r '.[] | select(.name=="Guji Highland") | .id')" \ - --cafe-id "$(./target/debug/brewlog cafe list | jq -r '.[] | select(.name=="Hola Coffee") | .id')" \ - --rating 3 + --cafe-id "$(./target/debug/brewlog cafe list | jq -r '.[] | select(.name=="Hola Coffee") | .id')" # Toma Café (Madrid) - 1 cup ./target/debug/brewlog cup add \ @@ -546,68 +539,56 @@ fi # The Barn (Berlin) - 3 cups ./target/debug/brewlog cup add \ --roast-id "$(./target/debug/brewlog roast list | jq -r '.[] | select(.name=="Gatomboya") | .id')" \ - --cafe-id "$(./target/debug/brewlog cafe list | jq -r '.[] | select(.name=="The Barn") | .id')" \ - --rating 4 + --cafe-id "$(./target/debug/brewlog cafe list | jq -r '.[] | select(.name=="The Barn") | .id')" ./target/debug/brewlog cup add \ --roast-id "$(./target/debug/brewlog roast list | jq -r '.[] | select(.name=="Los Pirineos") | .id')" \ - --cafe-id "$(./target/debug/brewlog cafe list | jq -r '.[] | select(.name=="The Barn") | .id')" \ - --rating 4 + --cafe-id "$(./target/debug/brewlog cafe list | jq -r '.[] | select(.name=="The Barn") | .id')" ./target/debug/brewlog cup add \ --roast-id "$(./target/debug/brewlog roast list | jq -r '.[] | select(.name=="Halo Beriti") | .id')" \ - --cafe-id "$(./target/debug/brewlog cafe list | jq -r '.[] | select(.name=="The Barn") | .id')" \ - --rating 5 + --cafe-id "$(./target/debug/brewlog cafe list | jq -r '.[] | select(.name=="The Barn") | .id')" # Companion Coffee (Berlin) - 2 cups ./target/debug/brewlog cup add \ --roast-id "$(./target/debug/brewlog roast list | jq -r '.[] | select(.name=="El Paraiso 92 Anaerobic") | .id')" \ - --cafe-id "$(./target/debug/brewlog cafe list | jq -r '.[] | select(.name=="Companion Coffee") | .id')" \ - --rating 5 + --cafe-id "$(./target/debug/brewlog cafe list | jq -r '.[] | select(.name=="Companion Coffee") | .id')" ./target/debug/brewlog cup add \ --roast-id "$(./target/debug/brewlog roast list | jq -r '.[] | select(.name=="Oreti SL28") | .id')" \ - --cafe-id "$(./target/debug/brewlog cafe list | jq -r '.[] | select(.name=="Companion Coffee") | .id')" \ - --rating 4 + --cafe-id "$(./target/debug/brewlog cafe list | jq -r '.[] | select(.name=="Companion Coffee") | .id')" # Man Versus Machine (Munich) - 2 cups ./target/debug/brewlog cup add \ --roast-id "$(./target/debug/brewlog roast list | jq -r '.[] | select(.name=="Finca Tamana Washed") | .id')" \ - --cafe-id "$(./target/debug/brewlog cafe list | jq -r '.[] | select(.name=="Man Versus Machine") | .id')" \ - --rating 4 + --cafe-id "$(./target/debug/brewlog cafe list | jq -r '.[] | select(.name=="Man Versus Machine") | .id')" ./target/debug/brewlog cup add \ --roast-id "$(./target/debug/brewlog roast list | jq -r '.[] | select(.name=="La Esmeralda Geisha") | .id')" \ - --cafe-id "$(./target/debug/brewlog cafe list | jq -r '.[] | select(.name=="Man Versus Machine") | .id')" \ - --rating 5 + --cafe-id "$(./target/debug/brewlog cafe list | jq -r '.[] | select(.name=="Man Versus Machine") | .id')" # Vits der Kaffee (Munich) - 1 cup ./target/debug/brewlog cup add \ --roast-id "$(./target/debug/brewlog roast list | jq -r '.[] | select(.name=="San Fermin") | .id')" \ - --cafe-id "$(./target/debug/brewlog cafe list | jq -r '.[] | select(.name=="Vits der Kaffee") | .id')" \ - --rating 3 + --cafe-id "$(./target/debug/brewlog cafe list | jq -r '.[] | select(.name=="Vits der Kaffee") | .id')" # Full Court Press (Bristol) - 2 cups ./target/debug/brewlog cup add \ --roast-id "$(./target/debug/brewlog roast list | jq -r '.[] | select(.name=="Suke Quto") | .id')" \ - --cafe-id "$(./target/debug/brewlog cafe list | jq -r '.[] | select(.name=="Full Court Press") | .id')" \ - --rating 5 + --cafe-id "$(./target/debug/brewlog cafe list | jq -r '.[] | select(.name=="Full Court Press") | .id')" ./target/debug/brewlog cup add \ --roast-id "$(./target/debug/brewlog roast list | jq -r '.[] | select(.name=="Death to Decaf") | .id')" \ - --cafe-id "$(./target/debug/brewlog cafe list | jq -r '.[] | select(.name=="Full Court Press") | .id')" \ - --rating 3 + --cafe-id "$(./target/debug/brewlog cafe list | jq -r '.[] | select(.name=="Full Court Press") | .id')" # Small Street Espresso (Bristol) - 2 cups ./target/debug/brewlog cup add \ --roast-id "$(./target/debug/brewlog roast list | jq -r '.[] | select(.name=="Simbi") | .id')" \ - --cafe-id "$(./target/debug/brewlog cafe list | jq -r '.[] | select(.name=="Small Street Espresso") | .id')" \ - --rating 4 + --cafe-id "$(./target/debug/brewlog cafe list | jq -r '.[] | select(.name=="Small Street Espresso") | .id')" ./target/debug/brewlog cup add \ --roast-id "$(./target/debug/brewlog roast list | jq -r '.[] | select(.name=="La Chumeca") | .id')" \ - --cafe-id "$(./target/debug/brewlog cafe list | jq -r '.[] | select(.name=="Small Street Espresso") | .id')" \ - --rating 4 + --cafe-id "$(./target/debug/brewlog cafe list | jq -r '.[] | select(.name=="Small Street Espresso") | .id')" # ============================================================================ # Bags - Various bags from different roasters with amounts ranging 100g-500g diff --git a/src/application/routes/checkin.rs b/src/application/routes/checkin.rs index 4583a04..c0023cd 100644 --- a/src/application/routes/checkin.rs +++ b/src/application/routes/checkin.rs @@ -56,8 +56,6 @@ pub(crate) struct CheckInSubmission { #[serde(default)] cafe_website: Option, roast_id: String, - #[serde(default)] - rating: i32, } #[tracing::instrument(skip(state, _auth_user, headers, payload))] @@ -105,16 +103,9 @@ pub(crate) async fn submit_checkin( cafe.id }; - let rating = if (1..=5).contains(&submission.rating) { - Some(submission.rating) - } else { - None - }; - let new_cup = NewCup { roast_id: RoastId::from(roast_id), cafe_id, - rating, }; let cup = state diff --git a/src/application/routes/cups.rs b/src/application/routes/cups.rs index 99f96ae..9fd4b35 100644 --- a/src/application/routes/cups.rs +++ b/src/application/routes/cups.rs @@ -1,5 +1,5 @@ use axum::Json; -use axum::extract::{Path, Query, State}; +use axum::extract::{Query, State}; use axum::http::{HeaderMap, StatusCode}; use axum::response::{IntoResponse, Redirect, Response}; @@ -12,7 +12,7 @@ use crate::application::routes::support::{ FlexiblePayload, ListQuery, PayloadSource, is_datastar_request, }; use crate::application::server::AppState; -use crate::domain::cups::{Cup, CupFilter, CupSortKey, CupWithDetails, NewCup, UpdateCup}; +use crate::domain::cups::{CupFilter, CupSortKey, CupWithDetails, NewCup}; use crate::domain::ids::CupId; use crate::domain::listing::{ListRequest, SortDirection}; use crate::presentation::web::templates::CupListTemplate; @@ -54,12 +54,6 @@ pub(crate) async fn create_cup( let (request, search) = query.into_request_and_search::(); let (new_cup, source) = payload.into_parts(); - if let Some(rating) = new_cup.rating - && !(1..=5).contains(&rating) - { - return Err(AppError::validation("rating must be between 1 and 5").into()); - } - let cup = state .cup_repo .insert(new_cup) @@ -94,33 +88,6 @@ pub(crate) async fn list_cups( define_enriched_get_handler!(get_cup, CupId, CupWithDetails, cup_repo, get_with_details); -#[tracing::instrument(skip(state, _auth_user))] -pub(crate) async fn update_cup( - State(state): State, - _auth_user: AuthenticatedUser, - Path(id): Path, - Json(payload): Json, -) -> Result, ApiError> { - let has_changes = payload.rating.is_some(); - - if !has_changes { - return Err(AppError::validation("no changes provided").into()); - } - - if let Some(rating) = payload.rating.as_ref() - && !(1..=5).contains(rating) - { - return Err(AppError::validation("rating must be between 1 and 5").into()); - } - - let cup = state - .cup_repo - .update(id, payload) - .await - .map_err(AppError::from)?; - Ok(Json(cup)) -} - define_delete_handler!( delete_cup, CupId, diff --git a/src/application/routes/mod.rs b/src/application/routes/mod.rs index 3b2dc0a..9b56c81 100644 --- a/src/application/routes/mod.rs +++ b/src/application/routes/mod.rs @@ -91,12 +91,7 @@ pub fn app_router(state: AppState) -> axum::Router { .route("/scan", post(scan::submit_scan)) .route("/check-in", post(checkin::submit_checkin)) .route("/cups", get(cups::list_cups).post(cups::create_cup)) - .route( - "/cups/:id", - get(cups::get_cup) - .put(cups::update_cup) - .delete(cups::delete_cup), - ) + .route("/cups/:id", get(cups::get_cup).delete(cups::delete_cup)) .route( "/tokens", post(tokens::create_token).get(tokens::list_tokens), diff --git a/src/domain/cups.rs b/src/domain/cups.rs index 1c9d9b8..425ccd1 100644 --- a/src/domain/cups.rs +++ b/src/domain/cups.rs @@ -9,7 +9,6 @@ pub struct Cup { pub id: CupId, pub roast_id: RoastId, pub cafe_id: CafeId, - pub rating: Option, pub created_at: DateTime, pub updated_at: DateTime, } @@ -30,12 +29,6 @@ pub struct CupWithDetails { pub struct NewCup { pub roast_id: RoastId, pub cafe_id: CafeId, - pub rating: Option, -} - -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -pub struct UpdateCup { - pub rating: Option, } /// Filter criteria for cup queries. @@ -73,7 +66,6 @@ pub enum CupSortKey { CreatedAt, CafeName, RoastName, - Rating, } impl SortKey for CupSortKey { @@ -86,7 +78,6 @@ impl SortKey for CupSortKey { "created-at" => Some(CupSortKey::CreatedAt), "cafe" => Some(CupSortKey::CafeName), "roast" => Some(CupSortKey::RoastName), - "rating" => Some(CupSortKey::Rating), _ => None, } } @@ -96,13 +87,12 @@ impl SortKey for CupSortKey { CupSortKey::CreatedAt => "created-at", CupSortKey::CafeName => "cafe", CupSortKey::RoastName => "roast", - CupSortKey::Rating => "rating", } } fn default_direction(self) -> SortDirection { match self { - CupSortKey::CreatedAt | CupSortKey::Rating => SortDirection::Desc, + CupSortKey::CreatedAt => SortDirection::Desc, _ => SortDirection::Asc, } } diff --git a/src/domain/repositories.rs b/src/domain/repositories.rs index 8e26d52..8e93499 100644 --- a/src/domain/repositories.rs +++ b/src/domain/repositories.rs @@ -5,7 +5,7 @@ use crate::domain::listing::{ListRequest, Page, SortDirection, SortKey}; use crate::domain::bags::{Bag, BagFilter, BagSortKey, BagWithRoast, NewBag, UpdateBag}; use crate::domain::brews::{Brew, BrewFilter, BrewSortKey, BrewWithDetails, NewBrew}; use crate::domain::cafes::{Cafe, CafeSortKey, NewCafe, UpdateCafe}; -use crate::domain::cups::{Cup, CupFilter, CupSortKey, CupWithDetails, NewCup, UpdateCup}; +use crate::domain::cups::{Cup, CupFilter, CupSortKey, CupWithDetails, NewCup}; use crate::domain::gear::{Gear, GearFilter, GearSortKey, NewGear, UpdateGear}; use crate::domain::ids::{ BagId, BrewId, CafeId, CupId, GearId, PasskeyCredentialId, RegistrationTokenId, RoastId, @@ -222,7 +222,6 @@ pub trait CupRepository: Send + Sync { request: &ListRequest, search: Option<&str>, ) -> Result, RepositoryError>; - async fn update(&self, id: CupId, changes: UpdateCup) -> Result; async fn delete(&self, id: CupId) -> Result<(), RepositoryError>; } diff --git a/src/infrastructure/client/cups.rs b/src/infrastructure/client/cups.rs index d09ab78..1a4b2ea 100644 --- a/src/infrastructure/client/cups.rs +++ b/src/infrastructure/client/cups.rs @@ -1,7 +1,7 @@ use anyhow::{Context, Result}; use reqwest::StatusCode; -use crate::domain::cups::{Cup, CupWithDetails, NewCup, UpdateCup}; +use crate::domain::cups::{Cup, CupWithDetails, NewCup}; use crate::domain::ids::CupId; use super::BrewlogClient; @@ -52,19 +52,6 @@ impl<'a> CupsClient<'a> { self.inner.handle_response(response).await } - pub async fn update(&self, id: CupId, payload: &UpdateCup) -> Result { - let url = self.inner.endpoint(&format!("api/v1/cups/{id}"))?; - let response = self - .inner - .request(reqwest::Method::PUT, url) - .json(payload) - .send() - .await - .context("failed to issue update cup request")?; - - self.inner.handle_response(response).await - } - pub async fn delete(&self, id: CupId) -> Result<()> { let url = self.inner.endpoint(&format!("api/v1/cups/{id}"))?; let response = self diff --git a/src/infrastructure/repositories/cups.rs b/src/infrastructure/repositories/cups.rs index ca90500..9ca0c82 100644 --- a/src/infrastructure/repositories/cups.rs +++ b/src/infrastructure/repositories/cups.rs @@ -1,10 +1,9 @@ use async_trait::async_trait; use chrono::{DateTime, Utc}; -use sqlx::{QueryBuilder, query, query_as}; +use sqlx::{query, query_as}; -use super::macros::push_update_field; use crate::domain::RepositoryError; -use crate::domain::cups::{Cup, CupFilter, CupSortKey, CupWithDetails, NewCup, UpdateCup}; +use crate::domain::cups::{Cup, CupFilter, CupSortKey, CupWithDetails, NewCup}; use crate::domain::ids::{CafeId, CupId, RoastId}; use crate::domain::listing::{ListRequest, Page, SortDirection}; use crate::domain::repositories::CupRepository; @@ -13,7 +12,7 @@ use crate::infrastructure::database::DatabasePool; const BASE_SELECT: &str = r" SELECT - c.id, c.roast_id, c.cafe_id, c.rating, + c.id, c.roast_id, c.cafe_id, c.created_at, c.updated_at, r.name as roast_name, r.slug as roast_slug, rr.name as roaster_name, rr.slug as roaster_slug, @@ -48,7 +47,6 @@ impl SqlCupRepository { CupSortKey::RoastName => { format!("LOWER(r.name) {dir_sql}, c.created_at DESC") } - CupSortKey::Rating => format!("c.rating {dir_sql}, c.created_at DESC"), } } @@ -57,7 +55,6 @@ impl SqlCupRepository { id: CupId::new(record.id), roast_id: RoastId::new(record.roast_id), cafe_id: CafeId::new(record.cafe_id), - rating: record.rating, created_at: record.created_at, updated_at: record.updated_at, } @@ -69,7 +66,6 @@ impl SqlCupRepository { id: CupId::new(record.id), roast_id: RoastId::new(record.roast_id), cafe_id: CafeId::new(record.cafe_id), - rating: record.rating, created_at: record.created_at, updated_at: record.updated_at, }, @@ -101,7 +97,7 @@ impl SqlCupRepository { } fn details_for_cup(cup_with_details: &CupWithDetails) -> Result { - let mut details = vec![ + let details = vec![ TimelineEventDetail { label: "Coffee".to_string(), value: cup_with_details.roast_name.clone(), @@ -116,13 +112,6 @@ impl SqlCupRepository { }, ]; - if let Some(rating) = cup_with_details.cup.rating { - details.push(TimelineEventDetail { - label: "Rating".to_string(), - value: format!("{rating}/5"), - }); - } - serde_json::to_string(&details).map_err(|err| { RepositoryError::unexpected(format!("failed to encode timeline event details: {err}")) }) @@ -139,12 +128,11 @@ impl CupRepository for SqlCupRepository { .map_err(|err| RepositoryError::unexpected(err.to_string()))?; let record = query_as::<_, CupRecord>( - "INSERT INTO cups (roast_id, cafe_id, rating) VALUES (?, ?, ?) \ - RETURNING id, roast_id, cafe_id, rating, created_at, updated_at", + "INSERT INTO cups (roast_id, cafe_id) VALUES (?, ?) \ + RETURNING id, roast_id, cafe_id, created_at, updated_at", ) .bind(new_cup.roast_id.into_inner()) .bind(new_cup.cafe_id.into_inner()) - .bind(new_cup.rating) .fetch_one(&mut *tx) .await .map_err(|err| RepositoryError::unexpected(err.to_string()))?; @@ -190,7 +178,7 @@ impl CupRepository for SqlCupRepository { async fn get(&self, id: CupId) -> Result { let record = query_as::<_, CupRecord>( - "SELECT id, roast_id, cafe_id, rating, created_at, updated_at FROM cups WHERE id = ?", + "SELECT id, roast_id, cafe_id, created_at, updated_at FROM cups WHERE id = ?", ) .bind(i64::from(id)) .fetch_optional(&self.pool) @@ -258,29 +246,6 @@ impl CupRepository for SqlCupRepository { .await } - async fn update(&self, id: CupId, changes: UpdateCup) -> Result { - let mut builder = QueryBuilder::new("UPDATE cups SET updated_at = CURRENT_TIMESTAMP"); - let mut sep = true; - - push_update_field!(builder, sep, "rating", changes.rating); - let _ = sep; - - builder.push(" WHERE id = "); - builder.push_bind(i64::from(id)); - - let result = builder - .build() - .execute(&self.pool) - .await - .map_err(|err| RepositoryError::unexpected(err.to_string()))?; - - if result.rows_affected() == 0 { - return Err(RepositoryError::NotFound); - } - - self.get(id).await - } - async fn delete(&self, id: CupId) -> Result<(), RepositoryError> { let result = query("DELETE FROM cups WHERE id = ?") .bind(i64::from(id)) @@ -301,7 +266,6 @@ struct CupRecord { id: i64, roast_id: i64, cafe_id: i64, - rating: Option, created_at: DateTime, updated_at: DateTime, } @@ -311,7 +275,6 @@ struct CupWithDetailsRecord { id: i64, roast_id: i64, cafe_id: i64, - rating: Option, created_at: DateTime, updated_at: DateTime, roast_name: String, diff --git a/src/presentation/cli/cups.rs b/src/presentation/cli/cups.rs index 0531907..f9a8103 100644 --- a/src/presentation/cli/cups.rs +++ b/src/presentation/cli/cups.rs @@ -3,7 +3,7 @@ use clap::{Args, Subcommand}; use super::macros::{define_delete_command, define_get_command}; use super::print_json; -use crate::domain::cups::{NewCup, UpdateCup}; +use crate::domain::cups::NewCup; use crate::domain::ids::{CafeId, CupId, RoastId}; use crate::infrastructure::client::BrewlogClient; @@ -15,8 +15,6 @@ pub enum CupCommands { List, /// Get a cup by ID Get(GetCupCommand), - /// Update a cup - Update(UpdateCupCommand), /// Delete a cup Delete(DeleteCupCommand), } @@ -26,7 +24,6 @@ pub async fn run(client: &BrewlogClient, cmd: CupCommands) -> Result<()> { CupCommands::Add(c) => add_cup(client, c).await, CupCommands::List => list_cups(client).await, CupCommands::Get(c) => get_cup(client, c).await, - CupCommands::Update(c) => update_cup(client, c).await, CupCommands::Delete(c) => delete_cup(client, c).await, } } @@ -37,15 +34,12 @@ pub struct AddCupCommand { pub roast_id: i64, #[arg(long)] pub cafe_id: i64, - #[arg(long)] - pub rating: Option, } pub async fn add_cup(client: &BrewlogClient, command: AddCupCommand) -> Result<()> { let payload = NewCup { roast_id: RoastId::new(command.roast_id), cafe_id: CafeId::new(command.cafe_id), - rating: command.rating, }; let cup = client.cups().create(&payload).await?; @@ -58,25 +52,4 @@ pub async fn list_cups(client: &BrewlogClient) -> Result<()> { } define_get_command!(GetCupCommand, get_cup, CupId, cups); - -#[derive(Debug, Args)] -pub struct UpdateCupCommand { - #[arg(long)] - pub id: i64, - #[arg(long)] - pub rating: Option, -} - -pub async fn update_cup(client: &BrewlogClient, command: UpdateCupCommand) -> Result<()> { - let payload = UpdateCup { - rating: command.rating, - }; - - let cup = client - .cups() - .update(CupId::new(command.id), &payload) - .await?; - print_json(&cup) -} - define_delete_command!(DeleteCupCommand, delete_cup, CupId, cups, "cup"); diff --git a/src/presentation/web/views/cups.rs b/src/presentation/web/views/cups.rs index d2ac982..08f5731 100644 --- a/src/presentation/web/views/cups.rs +++ b/src/presentation/web/views/cups.rs @@ -9,19 +9,11 @@ pub struct CupView { pub roaster_slug: String, pub cafe_name: String, pub cafe_slug: String, - pub rating: String, - pub has_rating: bool, pub created_at: String, } impl CupView { pub fn from_domain(cup: CupWithDetails) -> Self { - let rating = cup - .cup - .rating - .map_or_else(|| "—".to_string(), |r| format!("{r}/5")); - let has_rating = cup.cup.rating.is_some(); - Self { id: cup.cup.id.to_string(), roast_name: cup.roast_name, @@ -30,8 +22,6 @@ impl CupView { roaster_slug: cup.roaster_slug, cafe_name: cup.cafe_name, cafe_slug: cup.cafe_slug, - rating, - has_rating, created_at: cup.cup.created_at.format("%Y-%m-%d %H:%M").to_string(), } } diff --git a/tests/server/cups_api.rs b/tests/server/cups_api.rs index 0320739..1d46fd3 100644 --- a/tests/server/cups_api.rs +++ b/tests/server/cups_api.rs @@ -1,7 +1,7 @@ use crate::helpers::{ create_default_cafe, create_default_roast, create_default_roaster, spawn_app_with_auth, }; -use brewlog::domain::cups::{Cup, CupWithDetails, NewCup, UpdateCup}; +use brewlog::domain::cups::{Cup, CupWithDetails, NewCup}; use brewlog::domain::ids::{CafeId, RoastId}; #[tokio::test] @@ -16,7 +16,6 @@ async fn creating_a_cup_returns_a_201_for_valid_data() { let new_cup = NewCup { roast_id: roast.id, cafe_id: cafe.id, - rating: Some(5), }; let response = client @@ -32,36 +31,6 @@ async fn creating_a_cup_returns_a_201_for_valid_data() { let cup: Cup = response.json().await.expect("Failed to parse response"); assert_eq!(cup.roast_id, roast.id); assert_eq!(cup.cafe_id, cafe.id); - assert_eq!(cup.rating, Some(5)); -} - -#[tokio::test] -async fn creating_a_cup_without_optional_fields_returns_a_201() { - let app = spawn_app_with_auth().await; - let client = reqwest::Client::new(); - - let roaster = create_default_roaster(&app).await; - let roast = create_default_roast(&app, roaster.id).await; - let cafe = create_default_cafe(&app).await; - - let new_cup = NewCup { - roast_id: roast.id, - cafe_id: cafe.id, - rating: None, - }; - - let response = client - .post(app.api_url("/cups")) - .bearer_auth(app.auth_token.as_ref().unwrap()) - .json(&new_cup) - .send() - .await - .expect("Failed to execute request"); - - assert_eq!(response.status(), 201); - - let cup: Cup = response.json().await.expect("Failed to parse response"); - assert_eq!(cup.rating, None); } #[tokio::test] @@ -72,7 +41,6 @@ async fn creating_a_cup_requires_authentication() { let new_cup = NewCup { roast_id: RoastId::new(1), cafe_id: CafeId::new(1), - rating: None, }; let response = client @@ -114,7 +82,6 @@ async fn listing_cups_returns_a_200_with_enriched_data() { let new_cup = NewCup { roast_id: roast.id, cafe_id: cafe.id, - rating: Some(4), }; client @@ -152,7 +119,6 @@ async fn getting_a_cup_returns_a_200_with_details() { let new_cup = NewCup { roast_id: roast.id, cafe_id: cafe.id, - rating: Some(3), }; let create_response = client @@ -196,91 +162,6 @@ async fn getting_a_nonexistent_cup_returns_a_404() { assert_eq!(response.status(), 404); } -#[tokio::test] -async fn updating_a_cup_returns_a_200_for_valid_data() { - let app = spawn_app_with_auth().await; - let client = reqwest::Client::new(); - - let roaster = create_default_roaster(&app).await; - let roast = create_default_roast(&app, roaster.id).await; - let cafe = create_default_cafe(&app).await; - - let new_cup = NewCup { - roast_id: roast.id, - cafe_id: cafe.id, - rating: None, - }; - - let create_response = client - .post(app.api_url("/cups")) - .bearer_auth(app.auth_token.as_ref().unwrap()) - .json(&new_cup) - .send() - .await - .expect("Failed to create cup"); - - let cup: Cup = create_response - .json() - .await - .expect("Failed to parse response"); - - let update = UpdateCup { rating: Some(4) }; - - let response = client - .put(app.api_url(&format!("/cups/{}", cup.id))) - .bearer_auth(app.auth_token.as_ref().unwrap()) - .json(&update) - .send() - .await - .expect("Failed to execute request"); - - assert_eq!(response.status(), 200); - - let updated: Cup = response.json().await.expect("Failed to parse response"); - assert_eq!(updated.rating, Some(4)); -} - -#[tokio::test] -async fn updating_a_cup_with_no_changes_returns_a_400() { - let app = spawn_app_with_auth().await; - let client = reqwest::Client::new(); - - let roaster = create_default_roaster(&app).await; - let roast = create_default_roast(&app, roaster.id).await; - let cafe = create_default_cafe(&app).await; - - let new_cup = NewCup { - roast_id: roast.id, - cafe_id: cafe.id, - rating: None, - }; - - let create_response = client - .post(app.api_url("/cups")) - .bearer_auth(app.auth_token.as_ref().unwrap()) - .json(&new_cup) - .send() - .await - .expect("Failed to create cup"); - - let cup: Cup = create_response - .json() - .await - .expect("Failed to parse response"); - - let update = UpdateCup { rating: None }; - - let response = client - .put(app.api_url(&format!("/cups/{}", cup.id))) - .bearer_auth(app.auth_token.as_ref().unwrap()) - .json(&update) - .send() - .await - .expect("Failed to execute request"); - - assert_eq!(response.status(), 400); -} - #[tokio::test] async fn deleting_a_cup_returns_a_204_for_valid_id() { let app = spawn_app_with_auth().await; @@ -293,7 +174,6 @@ async fn deleting_a_cup_returns_a_204_for_valid_id() { let new_cup = NewCup { roast_id: roast.id, cafe_id: cafe.id, - rating: None, }; let create_response = client