refactor: move NearbyCafe to domain layer to fix dependency violation
The presentation layer was importing NearbyCafe directly from
infrastructure::foursquare, violating the dependency flow
(presentation -> application -> domain <- infrastructure). Introduce
NearbyCafeResult in domain:☕:nearby_cafes and update all
references.
This commit is contained in:
parent
f7b210f234
commit
3d49236c13
6 changed files with 30 additions and 23 deletions
|
|
@ -3,6 +3,7 @@ pub mod brews;
|
||||||
pub mod cafes;
|
pub mod cafes;
|
||||||
pub mod cups;
|
pub mod cups;
|
||||||
pub mod gear;
|
pub mod gear;
|
||||||
|
pub mod nearby_cafes;
|
||||||
pub mod roasters;
|
pub mod roasters;
|
||||||
pub mod roasts;
|
pub mod roasts;
|
||||||
|
|
||||||
|
|
|
||||||
16
src/domain/coffee/nearby_cafes.rs
Normal file
16
src/domain/coffee/nearby_cafes.rs
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// A nearby cafe result from a location-based search.
|
||||||
|
///
|
||||||
|
/// This is a domain-level representation that decouples the presentation
|
||||||
|
/// layer from any specific third-party API (e.g. Foursquare).
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct NearbyCafeResult {
|
||||||
|
pub name: String,
|
||||||
|
pub latitude: f64,
|
||||||
|
pub longitude: f64,
|
||||||
|
pub city: String,
|
||||||
|
pub country: String,
|
||||||
|
pub website: Option<String>,
|
||||||
|
pub distance_meters: u32,
|
||||||
|
}
|
||||||
|
|
@ -13,5 +13,5 @@ pub mod repositories;
|
||||||
// Re-exports for backward compatibility
|
// Re-exports for backward compatibility
|
||||||
pub use analytics::{ai_usage, country_stats, stats, timeline};
|
pub use analytics::{ai_usage, country_stats, stats, timeline};
|
||||||
pub use auth::{passkey_credentials, registration_tokens, sessions, tokens, users};
|
pub use auth::{passkey_credentials, registration_tokens, sessions, tokens, users};
|
||||||
pub use coffee::{bags, brews, cafes, cups, gear, roasters, roasts};
|
pub use coffee::{bags, brews, cafes, cups, gear, nearby_cafes, roasters, roasts};
|
||||||
pub use errors::RepositoryError;
|
pub use errors::RepositoryError;
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use isocountry::CountryCode;
|
use isocountry::CountryCode;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::Deserialize;
|
||||||
|
|
||||||
use crate::application::errors::AppError;
|
use crate::application::errors::AppError;
|
||||||
|
use crate::domain::nearby_cafes::NearbyCafeResult;
|
||||||
|
|
||||||
pub const FOURSQUARE_SEARCH_URL: &str = "https://places-api.foursquare.com/places/search";
|
pub const FOURSQUARE_SEARCH_URL: &str = "https://places-api.foursquare.com/places/search";
|
||||||
const USER_AGENT: &str = "Brewlog/1.0";
|
const USER_AGENT: &str = "Brewlog/1.0";
|
||||||
|
|
@ -13,17 +14,6 @@ const REQUEST_TIMEOUT: Duration = Duration::from_secs(10);
|
||||||
const FIELDS: &str = "name,latitude,longitude,location,website,distance";
|
const FIELDS: &str = "name,latitude,longitude,location,website,distance";
|
||||||
const API_VERSION: &str = "2025-06-17";
|
const API_VERSION: &str = "2025-06-17";
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct NearbyCafe {
|
|
||||||
pub name: String,
|
|
||||||
pub latitude: f64,
|
|
||||||
pub longitude: f64,
|
|
||||||
pub city: String,
|
|
||||||
pub country: String,
|
|
||||||
pub website: Option<String>,
|
|
||||||
pub distance_meters: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Location mode for Foursquare search.
|
/// Location mode for Foursquare search.
|
||||||
pub enum SearchLocation {
|
pub enum SearchLocation {
|
||||||
/// Search near GPS coordinates with a fixed radius.
|
/// Search near GPS coordinates with a fixed radius.
|
||||||
|
|
@ -39,7 +29,7 @@ pub async fn search_nearby(
|
||||||
api_key: &str,
|
api_key: &str,
|
||||||
location: &SearchLocation,
|
location: &SearchLocation,
|
||||||
query: &str,
|
query: &str,
|
||||||
) -> Result<Vec<NearbyCafe>, AppError> {
|
) -> Result<Vec<NearbyCafeResult>, AppError> {
|
||||||
let mut request = client
|
let mut request = client
|
||||||
.get(base_url)
|
.get(base_url)
|
||||||
.header("User-Agent", USER_AGENT)
|
.header("User-Agent", USER_AGENT)
|
||||||
|
|
@ -90,7 +80,7 @@ pub async fn search_nearby(
|
||||||
Ok(cafes)
|
Ok(cafes)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_cafe(place: FoursquarePlace, location: &SearchLocation) -> Option<NearbyCafe> {
|
fn parse_cafe(place: FoursquarePlace, location: &SearchLocation) -> Option<NearbyCafeResult> {
|
||||||
if place.name.is_empty() {
|
if place.name.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
@ -111,7 +101,7 @@ fn parse_cafe(place: FoursquarePlace, location: &SearchLocation) -> Option<Nearb
|
||||||
|
|
||||||
let website = place.website.filter(|w| !w.trim().is_empty());
|
let website = place.website.filter(|w| !w.trim().is_empty());
|
||||||
|
|
||||||
Some(NearbyCafe {
|
Some(NearbyCafeResult {
|
||||||
name: place.name,
|
name: place.name,
|
||||||
latitude: lat,
|
latitude: lat,
|
||||||
longitude: lng,
|
longitude: lng,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::domain::cafes::Cafe;
|
use crate::domain::cafes::Cafe;
|
||||||
use crate::domain::countries::{country_to_iso, iso_to_flag_emoji};
|
use crate::domain::countries::{country_to_iso, iso_to_flag_emoji};
|
||||||
use crate::infrastructure::foursquare::NearbyCafe;
|
use crate::domain::nearby_cafes::NearbyCafeResult;
|
||||||
|
|
||||||
use super::{LegendEntry, build_map_data};
|
use super::{LegendEntry, build_map_data};
|
||||||
|
|
||||||
|
|
@ -144,8 +144,8 @@ pub struct NearbyCafeView {
|
||||||
pub location: String,
|
pub location: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<NearbyCafe> for NearbyCafeView {
|
impl From<NearbyCafeResult> for NearbyCafeView {
|
||||||
fn from(cafe: NearbyCafe) -> Self {
|
fn from(cafe: NearbyCafeResult) -> Self {
|
||||||
let distance = if cafe.distance_meters < 1000 {
|
let distance = if cafe.distance_meters < 1000 {
|
||||||
format!("{} m", cafe.distance_meters)
|
format!("{} m", cafe.distance_meters)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use brewlog::infrastructure::foursquare::NearbyCafe;
|
use brewlog::domain::nearby_cafes::NearbyCafeResult;
|
||||||
use wiremock::matchers::{header, method, path, query_param};
|
use wiremock::matchers::{header, method, path, query_param};
|
||||||
use wiremock::{Mock, ResponseTemplate};
|
use wiremock::{Mock, ResponseTemplate};
|
||||||
|
|
||||||
|
|
@ -58,7 +58,7 @@ async fn nearby_search_returns_results() {
|
||||||
|
|
||||||
assert_eq!(response.status(), 200);
|
assert_eq!(response.status(), 200);
|
||||||
|
|
||||||
let cafes: Vec<NearbyCafe> = response.json().await.expect("Failed to parse response");
|
let cafes: Vec<NearbyCafeResult> = response.json().await.expect("Failed to parse response");
|
||||||
assert_eq!(cafes.len(), 2);
|
assert_eq!(cafes.len(), 2);
|
||||||
|
|
||||||
assert_eq!(cafes[0].name, "Prufrock Coffee");
|
assert_eq!(cafes[0].name, "Prufrock Coffee");
|
||||||
|
|
@ -98,7 +98,7 @@ async fn nearby_search_returns_empty_for_no_matches() {
|
||||||
|
|
||||||
assert_eq!(response.status(), 200);
|
assert_eq!(response.status(), 200);
|
||||||
|
|
||||||
let cafes: Vec<NearbyCafe> = response.json().await.expect("Failed to parse response");
|
let cafes: Vec<NearbyCafeResult> = response.json().await.expect("Failed to parse response");
|
||||||
assert!(cafes.is_empty());
|
assert!(cafes.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -199,7 +199,7 @@ async fn nearby_search_with_near_param() {
|
||||||
|
|
||||||
assert_eq!(response.status(), 200);
|
assert_eq!(response.status(), 200);
|
||||||
|
|
||||||
let cafes: Vec<NearbyCafe> = response.json().await.expect("Failed to parse response");
|
let cafes: Vec<NearbyCafeResult> = response.json().await.expect("Failed to parse response");
|
||||||
assert_eq!(cafes.len(), 2);
|
assert_eq!(cafes.len(), 2);
|
||||||
assert_eq!(cafes[0].name, "Prufrock Coffee");
|
assert_eq!(cafes[0].name, "Prufrock Coffee");
|
||||||
assert_eq!(cafes[0].city, "London");
|
assert_eq!(cafes[0].city, "London");
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue