place-service 0.2.0

Place Service - A place administration microservice that interoperates with the place-matcher crate
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;

use super::address::PostalAddress;
use super::amenity::AmenityFeature;
use super::geo::GeoCoordinates;
use super::identifier::PlaceIdentifier;
use super::opening_hours::OpeningHoursSpecification;
use super::place_type::PlaceType;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Place {
    pub id: Uuid,
    pub name: String,
    pub alternate_name: Option<String>,
    pub description: Option<String>,
    pub place_type: Option<PlaceType>,
    pub address: Option<PostalAddress>,
    pub geo: Option<GeoCoordinates>,
    pub telephone: Option<String>,
    pub fax_number: Option<String>,
    pub url: Option<String>,
    pub global_location_number: Option<String>,
    pub branch_code: Option<String>,
    pub contained_in_place: Option<Uuid>,
    pub keywords: Vec<String>,
    pub identifiers: Vec<PlaceIdentifier>,
    pub amenity_features: Vec<AmenityFeature>,
    pub opening_hours: Vec<OpeningHoursSpecification>,
    pub is_accessible_for_free: Option<bool>,
    pub public_access: Option<bool>,
    pub smoking_allowed: Option<bool>,
    pub maximum_attendee_capacity: Option<u32>,
    pub is_deleted: bool,
    pub deleted_at: Option<DateTime<Utc>>,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
}

impl Place {
    pub fn new(name: &str) -> Self {
        let now = Utc::now();
        Self {
            id: Uuid::new_v4(),
            name: name.to_string(),
            alternate_name: None,
            description: None,
            place_type: None,
            address: None,
            geo: None,
            telephone: None,
            fax_number: None,
            url: None,
            global_location_number: None,
            branch_code: None,
            contained_in_place: None,
            keywords: Vec::new(),
            identifiers: Vec::new(),
            amenity_features: Vec::new(),
            opening_hours: Vec::new(),
            is_accessible_for_free: None,
            public_access: None,
            smoking_allowed: None,
            maximum_attendee_capacity: None,
            is_deleted: false,
            deleted_at: None,
            created_at: now,
            updated_at: now,
        }
    }

    pub fn soft_delete(&mut self) {
        self.is_deleted = true;
        self.deleted_at = Some(Utc::now());
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_place_new() {
        let place = Place::new("Central Park");
        assert_eq!(place.name, "Central Park");
        assert!(place.id != uuid::Uuid::nil());
        assert!(!place.is_deleted);
    }

    #[test]
    fn test_place_default_fields() {
        let place = Place::new("Test");
        assert!(place.alternate_name.is_none());
        assert!(place.description.is_none());
        assert!(place.address.is_none());
        assert!(place.geo.is_none());
        assert!(place.place_type.is_none());
        assert!(place.telephone.is_none());
        assert!(place.url.is_none());
        assert!(place.identifiers.is_empty());
        assert!(place.amenity_features.is_empty());
        assert!(!place.is_deleted);
    }

    #[test]
    fn test_place_with_address() {
        let addr = PostalAddress {
            street_address: Some("14 E 60th St".into()),
            address_locality: Some("New York".into()),
            address_region: Some("NY".into()),
            address_country: Some("US".into()),
            postal_code: Some("10022".into()),
        };
        let mut place = Place::new("Central Park");
        place.address = Some(addr);
        assert_eq!(place.address.as_ref().unwrap().address_locality.as_deref(), Some("New York"));
    }

    #[test]
    fn test_place_with_geo() {
        let geo = GeoCoordinates {
            latitude: 40.7829,
            longitude: -73.9654,
            elevation: None,
        };
        let mut place = Place::new("Central Park");
        place.geo = Some(geo);
        assert!((place.geo.as_ref().unwrap().latitude - 40.7829).abs() < f64::EPSILON);
    }

    #[test]
    fn test_place_serialization_roundtrip() {
        let mut place = Place::new("Test Place");
        place.description = Some("A test".into());
        let json = serde_json::to_string(&place).unwrap();
        let deserialized: Place = serde_json::from_str(&json).unwrap();
        assert_eq!(deserialized.name, "Test Place");
        assert_eq!(deserialized.description.as_deref(), Some("A test"));
        assert_eq!(deserialized.id, place.id);
    }

    #[test]
    fn test_place_soft_delete() {
        let mut place = Place::new("To Delete");
        assert!(!place.is_deleted);
        place.soft_delete();
        assert!(place.is_deleted);
        assert!(place.deleted_at.is_some());
    }
}