fish_lib/game/services/
user_service.rs1use 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}