pocket_relay_database/interfaces/
players.rs

1use crate::{
2    entities::{player_data, players, PlayerData},
3    DbResult, Player, PlayerRole,
4};
5use sea_orm::{
6    ActiveModelTrait,
7    ActiveValue::{NotSet, Set},
8    ColumnTrait, DatabaseConnection, DeleteResult, EntityTrait, InsertResult, IntoActiveModel,
9    ModelTrait, PaginatorTrait, QueryFilter, QueryOrder,
10};
11use std::{future::Future, iter::Iterator, pin::Pin};
12
13type DbFuture<'a, T> = Pin<Box<dyn Future<Output = DbResult<T>> + Send + 'a>>;
14
15impl Player {
16    /// Takes all the player models using a cursor starting at the offset row
17    /// and finding the count number of values will check the count + 1 rows
18    /// in order to determine if there are more entires to come.
19    ///
20    /// `db`     The database connection
21    /// `offset` The number of rows to skip
22    /// `count`  The number of rows to collect
23    pub async fn all(
24        db: &DatabaseConnection,
25        page: u64,
26        count: u64,
27    ) -> DbResult<(Vec<Self>, bool)> {
28        let paginate = players::Entity::find()
29            .order_by_asc(players::Column::Id)
30            .paginate(db, count);
31
32        let total_pages = paginate.num_pages().await?;
33        let is_more = page < total_pages;
34        let values = paginate.fetch_page(page).await?;
35
36        Ok((values, is_more))
37    }
38
39    /// Creates a new player with the proivded details and inserts
40    /// it into the database
41    ///
42    /// `db`           The database instance
43    /// `email`        The player account email
44    /// `display_name` The player display name
45    /// `password`     The hashed player password
46    /// `origin`       Whether the account is an origin account
47    pub fn create(
48        db: &DatabaseConnection,
49        email: String,
50        display_name: String,
51        password: Option<String>,
52    ) -> DbFuture<Self> {
53        let active_model = players::ActiveModel {
54            email: Set(email),
55            display_name: Set(display_name),
56            password: Set(password),
57            ..Default::default()
58        };
59        active_model.insert(db)
60    }
61
62    /// Deletes the provided player
63    ///
64    /// `db` The database connection
65    pub fn delete(self, db: &DatabaseConnection) -> DbFuture<DeleteResult> {
66        // Delete player itself
67        let model = self.into_active_model();
68        model.delete(db)
69    }
70
71    /// Retrieves all the player data for the desired player
72    ///
73    /// `id`    The ID of the player
74    /// `db`    The database connection
75    pub fn all_data(
76        id: u32,
77        db: &DatabaseConnection,
78    ) -> impl Future<Output = DbResult<Vec<PlayerData>>> + Send + '_ {
79        player_data::Entity::find()
80            .filter(player_data::Column::PlayerId.eq(id))
81            .all(db)
82    }
83
84    /// Sets the key value data for the provided player. If the data exists then
85    /// the value is updated otherwise the data will be created. The new data is
86    /// returned.
87    ///
88    /// `id`    The ID of the player
89    /// `db`    The database connection
90    /// `key`   The data key
91    /// `value` The data value
92    pub async fn set_data(
93        id: u32,
94        db: &DatabaseConnection,
95        key: String,
96        value: String,
97    ) -> DbResult<PlayerData> {
98        let existing = player_data::Entity::find()
99            .filter(
100                player_data::Column::PlayerId
101                    .eq(id)
102                    .and(player_data::Column::Key.eq(&key as &str)),
103            )
104            .one(db)
105            .await?;
106
107        if let Some(player_data) = existing {
108            let mut model = player_data.into_active_model();
109            model.key = Set(key);
110            model.value = Set(value);
111            model.update(db).await
112        } else {
113            player_data::ActiveModel {
114                player_id: Set(id),
115                key: Set(key),
116                value: Set(value),
117                ..Default::default()
118            }
119            .insert(db)
120            .await
121        }
122    }
123
124    /// Bulk inserts a collection of player data for the provided player. Will not handle
125    /// conflicts so this should only be done on a freshly create player where data doesnt
126    /// already exist
127    ///
128    /// `db`   The database connection
129    /// `data` Iterator of the data keys and values
130    pub fn bulk_insert_data<'a>(
131        &self,
132        db: &'a DatabaseConnection,
133        data: impl Iterator<Item = (String, String)>,
134    ) -> impl Future<Output = DbResult<InsertResult<player_data::ActiveModel>>> + Send + 'a {
135        // Transform the provided key values into active models
136        let models_iter = data.map(|(key, value)| player_data::ActiveModel {
137            id: NotSet,
138            player_id: Set(self.id),
139            key: Set(key),
140            value: Set(value),
141        });
142        // Insert all the models
143        player_data::Entity::insert_many(models_iter).exec(db)
144    }
145
146    /// Deletes the player data with the provided key for the
147    /// current player
148    ///
149    /// `db`    The database connection
150    /// `key`   The data key
151    pub fn delete_data<'a>(
152        &self,
153        db: &'a DatabaseConnection,
154        key: &str,
155    ) -> impl Future<Output = DbResult<DeleteResult>> + Send + 'a {
156        player_data::Entity::delete_many()
157            .belongs_to(self)
158            .filter(player_data::Column::Key.eq(key))
159            .exec(db)
160    }
161
162    /// Gets the player data with the provided key for the
163    /// current player
164    ///
165    /// `db`  The database connection
166    /// `key` The data key
167    pub fn get_data<'a>(
168        &self,
169        db: &'a DatabaseConnection,
170        key: &str,
171    ) -> impl Future<Output = DbResult<Option<PlayerData>>> + Send + 'a {
172        self.find_related(player_data::Entity)
173            .filter(player_data::Column::Key.eq(key))
174            .one(db)
175    }
176
177    /// Gets all the player class data for the current player
178    ///
179    /// `db` The database connection
180    pub fn get_classes<'a>(
181        &self,
182        db: &'a DatabaseConnection,
183    ) -> impl Future<Output = DbResult<Vec<PlayerData>>> + Send + 'a {
184        self.find_related(player_data::Entity)
185            .filter(player_data::Column::Key.starts_with("class"))
186            .all(db)
187    }
188
189    /// Gets all the player character data for the current player
190    ///
191    /// `db` The database connection
192    pub fn get_characters<'a>(
193        &self,
194        db: &'a DatabaseConnection,
195    ) -> impl Future<Output = DbResult<Vec<PlayerData>>> + Send + 'a {
196        self.find_related(player_data::Entity)
197            .filter(player_data::Column::Key.starts_with("char"))
198            .all(db)
199    }
200
201    /// Parses the challenge points value which is the second
202    /// item in the completion list.
203    ///
204    /// `db` The database connection
205    pub async fn get_challenge_points(&self, db: &DatabaseConnection) -> Option<u32> {
206        let list = self.get_data(db, "Completion").await.ok()??.value;
207        let part = list.split(',').nth(1)?;
208        let value: u32 = part.parse().ok()?;
209        Some(value)
210    }
211
212    /// Attempts to find a player with the provided ID will return none
213    /// if there was no players with that ID
214    ///
215    /// `db` The database instance
216    /// `id` The ID of the player to find
217    pub fn by_id(
218        db: &DatabaseConnection,
219        id: u32,
220    ) -> impl Future<Output = DbResult<Option<Player>>> + Send + '_ {
221        players::Entity::find_by_id(id).one(db)
222    }
223
224    /// Attempts to find a player with the provided email.
225    ///
226    /// `db`    The database connection
227    /// `email` The email address to search for
228    pub fn by_email<'a>(
229        db: &'a DatabaseConnection,
230        email: &str,
231    ) -> impl Future<Output = DbResult<Option<Player>>> + Send + 'a {
232        players::Entity::find()
233            .filter(players::Column::Email.eq(email))
234            .one(db)
235    }
236
237    /// Determines whether the current player has permission to
238    /// make actions on behalf of the other player. This can
239    /// occur when they are both the same player or the role of
240    /// self is greater than the other role
241    ///
242    /// `other` The player to check for permission over
243    pub fn has_permission_over(&self, other: &Self) -> bool {
244        self.id == other.id || self.role > other.role
245    }
246
247    /// Updates the password for the provided player returning
248    /// a future resolving to the new player with its updated
249    /// password value
250    ///
251    /// `db`       The database connection
252    /// `password` The new hashed password
253    pub fn set_password(self, db: &DatabaseConnection, password: String) -> DbFuture<'_, Player> {
254        let mut model = self.into_active_model();
255        model.password = Set(Some(password));
256        model.update(db)
257    }
258
259    /// Sets the role of the provided player
260    ///
261    /// `db`   The database connection
262    /// `role` The new role for the player
263    pub fn set_role(self, db: &DatabaseConnection, role: PlayerRole) -> DbFuture<'_, Player> {
264        let mut model = self.into_active_model();
265        model.role = Set(role);
266        model.update(db)
267    }
268
269    /// Updates the basic details of the provided player if
270    /// they are provided
271    ///
272    /// `db`       The database connection
273    /// `username` Optional new username for the player
274    /// `email`    Optional new email for the player
275    pub fn set_details(
276        self,
277        db: &DatabaseConnection,
278        username: Option<String>,
279        email: Option<String>,
280    ) -> DbFuture<'_, Player> {
281        let mut model = self.into_active_model();
282
283        if let Some(username) = username {
284            model.display_name = Set(username);
285        }
286
287        if let Some(email) = email {
288            model.email = Set(email)
289        }
290
291        model.update(db)
292    }
293}