nil_server_database/model/
user.rs1use crate::Database;
5use crate::error::{Error, Result};
6use crate::sql_types::hashed_password::HashedPassword;
7use crate::sql_types::id::UserId;
8use crate::sql_types::player_id::db_PlayerId;
9use crate::sql_types::zoned::db_Zoned;
10use diesel::prelude::*;
11use nil_crypto::password::Password;
12use std::fmt;
13use tokio::task::spawn_blocking;
14
15#[derive(Identifiable, Queryable, Selectable, Clone)]
16#[diesel(table_name = crate::schema::user)]
17#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
18pub struct User {
19 pub id: UserId,
20 pub player_id: db_PlayerId,
21 pub password: HashedPassword,
22 pub created_at: db_Zoned,
23 pub updated_at: db_Zoned,
24}
25
26impl User {
27 #[inline]
28 pub async fn verify_password(&self, password: Password) -> Result<bool> {
29 let hashed = self.password.clone();
30 spawn_blocking(move || hashed.verify(&password))
31 .await?
32 .map_err(Into::into)
33 }
34}
35
36impl fmt::Debug for User {
37 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38 f.debug_struct("User")
39 .field("id", &self.id.to_string())
40 .field("player_id", &self.player_id)
41 .field("created_at", &self.created_at.to_string())
42 .field("updated_at", &self.updated_at.to_string())
43 .finish_non_exhaustive()
44 }
45}
46
47#[derive(Insertable, Clone)]
48#[diesel(table_name = crate::schema::user)]
49pub struct NewUser {
50 player_id: db_PlayerId,
51 password: HashedPassword,
52 created_at: db_Zoned,
53 updated_at: db_Zoned,
54}
55
56impl NewUser {
57 pub async fn new(player_id: impl Into<db_PlayerId>, password: Password) -> Result<Self> {
58 let player_id: db_PlayerId = player_id.into();
59 let id_len = player_id.trim().chars().count();
60
61 if !(1..=20).contains(&id_len) {
62 return Err(Error::InvalidUsername(player_id));
63 }
64
65 let now = db_Zoned::now();
66
67 Ok(Self {
68 player_id,
69 password: hash_password(password).await?,
70 created_at: now.clone(),
71 updated_at: now,
72 })
73 }
74
75 pub fn player_id(&self) -> db_PlayerId {
76 self.player_id.clone()
77 }
78
79 pub async fn create(self, database: &Database) -> Result<usize> {
80 database.create_user(self).await
81 }
82}
83
84impl fmt::Debug for NewUser {
85 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86 f.debug_struct("NewUser")
87 .field("player_id", &self.player_id)
88 .field("created_at", &self.created_at.to_string())
89 .field("updated_at", &self.updated_at.to_string())
90 .finish_non_exhaustive()
91 }
92}
93
94async fn hash_password(password: Password) -> Result<HashedPassword> {
95 let pass_len = password.trim().chars().count();
96 if !(3..=50).contains(&pass_len) {
97 return Err(Error::InvalidPassword);
98 }
99
100 spawn_blocking(move || HashedPassword::new(&password))
101 .await?
102 .map_err(Into::into)
103}