1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
use std::fmt::Debug;
use std::str::FromStr;
use anyhow::Context;
use argon2::{password_hash::SaltString, Algorithm, Argon2, Params, PasswordHasher, Version};
use serde::{Deserialize, Serialize};
use sqlx::PgPool;
use uuid::Uuid;
#[derive(sqlx::FromRow, Serialize, Deserialize, Clone)]
pub struct User {
pub id: Uuid,
pub username: String,
pub password: String,
}
impl Debug for User {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("User")
.field("user_id", &self.id)
.field("username", &self.username)
.finish()
}
}
pub struct NewUser {
pub id: Uuid,
pub username: String,
pub password: String,
}
impl NewUser {
pub fn default() -> Self {
Self {
id: Uuid::new_v4(),
username: Uuid::new_v4().to_string(),
password: Uuid::new_v4().to_string(),
}
}
pub fn new(username: String, password: String) -> Self {
Self {
username: username.to_ascii_lowercase(),
password,
..Self::default()
}
}
pub async fn store(&self, pool: &PgPool) -> Result<(), anyhow::Error> {
let salt = SaltString::generate(&mut rand::thread_rng());
let password_hash = Argon2::new(
Algorithm::Argon2id,
Version::V0x13,
Params::new(15000, 2, 1, None)?,
)
.hash_password(self.password.as_bytes(), &salt)?
.to_string();
sqlx::query!(
"INSERT INTO account (id, username, password)
VALUES ($1, $2, $3)",
self.id,
self.username.to_lowercase(),
password_hash,
)
.execute(pool)
.await?;
Ok(())
}
}
pub async fn get_user_by_id(user_id: String, db_pool: &PgPool) -> Result<User, anyhow::Error> {
let user_id = Uuid::from_str(&user_id)?;
let user = sqlx::query_as!(User, "SELECT * FROM account WHERE id=$1", user_id)
.fetch_one(db_pool)
.await
.context(format!(
"Failed to fetch user with user_id {}",
user_id.to_string()
))?;
Ok(user)
}