fish_lib/game/services/
user_service.rs

1use crate::data::location_data::LocationData;
2use crate::dto::location_unlock_requirements::LocationUnlockRequirements;
3use crate::game::errors::resource::GameResourceError;
4use crate::game::errors::GameResult;
5use crate::game::repositories::fishing_history_entry_repository::FishingHistoryEntryRepositoryInterface;
6use crate::game::repositories::user_repository::UserRepositoryInterface;
7use crate::models::user::{NewUser, User};
8use crate::models::user_location::UserLocation;
9use std::sync::Arc;
10
11pub trait UserServiceInterface: Send + Sync {
12    fn create_and_save_user(&self, external_id: i64) -> GameResult<User>;
13    fn get_unmet_location_unlock_requirements(
14        &self,
15        user: &User,
16        location: Arc<LocationData>,
17    ) -> GameResult<LocationUnlockRequirements>;
18    fn unlock_location(
19        &self,
20        user: &User,
21        location_data: Arc<LocationData>,
22    ) -> GameResult<UserLocation>;
23    fn get_unlocked_locations(&self, user: &User) -> GameResult<Vec<UserLocation>>;
24    fn get_unlocked_location_ids(&self, user: &User) -> GameResult<Vec<i32>>;
25}
26
27pub struct UserService {
28    fishing_history_entry_repository: Arc<dyn FishingHistoryEntryRepositoryInterface>,
29    user_repository: Arc<dyn UserRepositoryInterface>,
30}
31
32impl UserService {
33    pub fn new(
34        fishing_history_entry_repository: Arc<dyn FishingHistoryEntryRepositoryInterface>,
35        user_repository: Arc<dyn UserRepositoryInterface>,
36    ) -> UserService {
37        UserService {
38            fishing_history_entry_repository,
39            user_repository,
40        }
41    }
42}
43
44impl UserServiceInterface for UserService {
45    fn create_and_save_user(&self, external_id: i64) -> GameResult<User> {
46        let user = NewUser { external_id };
47        Ok(self.user_repository.create(user)?)
48    }
49
50    fn get_unmet_location_unlock_requirements(
51        &self,
52        user: &User,
53        location: Arc<LocationData>,
54    ) -> GameResult<LocationUnlockRequirements> {
55        let unlocked_location_ids = self.get_unlocked_location_ids(user)?;
56        let missing_location_ids: Vec<i32> = location
57            .required_locations_unlocked
58            .iter()
59            .copied()
60            .filter(|required_location_id| !unlocked_location_ids.contains(required_location_id))
61            .collect();
62
63        let caught_species_ids = self
64            .fishing_history_entry_repository
65            .find_caught_species_ids_by_user(user.id)?;
66        let missing_species_ids: Vec<i32> = location
67            .required_species_caught
68            .iter()
69            .copied()
70            .filter(|required_species_id| !caught_species_ids.contains(required_species_id))
71            .collect();
72
73        Ok(LocationUnlockRequirements {
74            locations_unlocked: missing_location_ids,
75            species_caught: missing_species_ids,
76        })
77    }
78
79    fn unlock_location(
80        &self,
81        user: &User,
82        location_data: Arc<LocationData>,
83    ) -> GameResult<UserLocation> {
84        let unmet_unlock_requirements =
85            self.get_unmet_location_unlock_requirements(user, location_data.clone())?;
86        if !unmet_unlock_requirements.is_empty() {
87            return Err(
88                GameResourceError::unmet_location_unlock_requirements(location_data.id).into(),
89            );
90        }
91
92        self.user_repository
93            .unlock_location(user.id, location_data.id)
94            .map_err(|e| match e.get_database_error() {
95                Some(db_error) if db_error.is_unique_constraint_violation() => {
96                    GameResourceError::location_already_unlocked(user.external_id, location_data.id)
97                        .into()
98                }
99                _ => e.into(),
100            })
101    }
102
103    fn get_unlocked_locations(&self, user: &User) -> GameResult<Vec<UserLocation>> {
104        Ok(self.user_repository.find_unlocked_locations(user.id)?)
105    }
106
107    fn get_unlocked_location_ids(&self, user: &User) -> GameResult<Vec<i32>> {
108        Ok(self.user_repository.find_unlocked_location_ids(user.id)?)
109    }
110}