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