mirror of
https://git.dirksys.ovh/dirk/bankserver.git
synced 2025-12-20 02:59:20 +01:00
200 lines
6.2 KiB
Rust
200 lines
6.2 KiB
Rust
use bank_core::{
|
|
ApiError,
|
|
pagination::{Pagination, RequestPagination},
|
|
user::User,
|
|
};
|
|
use deadpool_postgres::GenericClient;
|
|
use tokio_postgres::{Row, error::SqlState};
|
|
use tracing::instrument;
|
|
use uuid::{NoContext, Timestamp, Uuid};
|
|
|
|
use crate::api::Error;
|
|
|
|
use super::{Accounts, count};
|
|
|
|
pub struct Users;
|
|
|
|
pub fn user_from_row(row: Row) -> User {
|
|
User {
|
|
id: row.get("id"),
|
|
name: row.get("name"),
|
|
}
|
|
}
|
|
|
|
fn unique_violation(error: tokio_postgres::Error, api_error: fn() -> ApiError<'static>) -> Error {
|
|
let Some(code) = error.code() else {
|
|
return error.into();
|
|
};
|
|
if *code == SqlState::UNIQUE_VIOLATION {
|
|
return api_error().into();
|
|
}
|
|
return error.into();
|
|
}
|
|
|
|
impl Users {
|
|
pub async fn exists(
|
|
client: &impl GenericClient,
|
|
id: Uuid,
|
|
) -> Result<bool, tokio_postgres::Error> {
|
|
let stmt = client
|
|
.prepare_cached("select exists(select 1 from users where id = $1)")
|
|
.await?;
|
|
let result = client.query_one(&stmt, &[&id]).await?;
|
|
Ok(result.get(0))
|
|
}
|
|
|
|
#[instrument(skip(client, password))]
|
|
pub async fn create(
|
|
client: &mut impl GenericClient,
|
|
name: &str,
|
|
password: &str,
|
|
) -> Result<Uuid, Error> {
|
|
let hash = password_auth::generate_hash(password);
|
|
let stmt = client
|
|
.prepare_cached("insert into users(id, name, password) values ($1, $2, $3)")
|
|
.await?;
|
|
let mut tx = client.transaction().await?;
|
|
let id = Uuid::new_v7(Timestamp::now(NoContext));
|
|
tx.execute(&stmt, &[&id, &name, &hash])
|
|
.await
|
|
.map_err(|err| unique_violation(err, || ApiError::CONFLICT))?;
|
|
Accounts::create(&mut tx, Some(id), id, "personal")
|
|
.await
|
|
.map_err(|err| unique_violation(err, || ApiError::CONFLICT))?;
|
|
tx.commit().await?;
|
|
Ok(id)
|
|
}
|
|
|
|
#[instrument(skip(client, password))]
|
|
pub async fn change_password(
|
|
client: &mut impl GenericClient,
|
|
user: Uuid,
|
|
password: &str,
|
|
) -> Result<(), tokio_postgres::Error> {
|
|
let hash = password_auth::generate_hash(password);
|
|
let stmt = client
|
|
.prepare_cached("update users set password = $2 where id = $1")
|
|
.await?;
|
|
client.execute(&stmt, &[&user, &hash]).await?;
|
|
Ok(())
|
|
}
|
|
|
|
#[instrument(skip(client))]
|
|
pub async fn get_password_and_info_by_username(
|
|
client: &impl GenericClient,
|
|
name: &str,
|
|
) -> Result<Option<(String, User)>, tokio_postgres::Error> {
|
|
let stmt = client
|
|
.prepare_cached("select id,name,password from users where name = $1")
|
|
.await?;
|
|
let res = client.query_opt(&stmt, &[&name]).await?;
|
|
Ok(res.map(|res| {
|
|
let password = res.get("password");
|
|
(password, user_from_row(res))
|
|
}))
|
|
}
|
|
#[instrument(skip(client))]
|
|
pub async fn info_by_name(
|
|
client: &impl GenericClient,
|
|
name: &str,
|
|
) -> Result<Option<User>, tokio_postgres::Error> {
|
|
let stmt = client
|
|
.prepare_cached("select id,name from users where name = $1")
|
|
.await?;
|
|
let res = client.query_opt(&stmt, &[&name]).await?;
|
|
Ok(res.map(user_from_row))
|
|
}
|
|
|
|
#[instrument(skip(client))]
|
|
pub async fn list(
|
|
client: &impl GenericClient,
|
|
pagination: RequestPagination,
|
|
) -> Result<Pagination<User>, tokio_postgres::Error> {
|
|
let stmt = client
|
|
.prepare_cached("select id,name from users limit $1 offset $2")
|
|
.await?;
|
|
let stmt_count = client.prepare_cached("select count(*) from users").await?;
|
|
let users = client
|
|
.query(&stmt, &[&pagination.limit(), &pagination.offset()])
|
|
.await?
|
|
.into_iter()
|
|
.map(user_from_row)
|
|
.collect();
|
|
let count = count(client, &stmt_count, &[]).await?;
|
|
Ok(Pagination::new(users, count, pagination))
|
|
}
|
|
|
|
#[instrument(skip(client))]
|
|
pub async fn info(
|
|
client: &impl GenericClient,
|
|
id: Uuid,
|
|
) -> Result<Option<User>, tokio_postgres::Error> {
|
|
let stmt = client
|
|
.prepare_cached("select id,name from users where id = $1")
|
|
.await?;
|
|
let info = client.query_opt(&stmt, &[&id]).await?.map(user_from_row);
|
|
Ok(info)
|
|
}
|
|
|
|
#[instrument(skip(client))]
|
|
pub async fn list_data(
|
|
client: &impl GenericClient,
|
|
user: Uuid,
|
|
) -> Result<Vec<String>, tokio_postgres::Error> {
|
|
let stmt = client
|
|
.prepare_cached("select \"key\" from user_data where owner = $1 and \"user\" = $1")
|
|
.await?;
|
|
let res = client
|
|
.query(&stmt, &[&user])
|
|
.await?
|
|
.into_iter()
|
|
.map(|row| row.get(0))
|
|
.collect();
|
|
Ok(res)
|
|
}
|
|
|
|
#[instrument(skip(client))]
|
|
pub async fn get_data(
|
|
client: &impl GenericClient,
|
|
user: Uuid,
|
|
key: &str,
|
|
) -> Result<Option<serde_json::Value>, tokio_postgres::Error> {
|
|
let stmt = client
|
|
.prepare_cached(
|
|
"select data from user_data where owner = $1 and \"user\" = $1 and \"key\" = $2 ",
|
|
)
|
|
.await?;
|
|
let res = client.query_opt(&stmt, &[&user, &key]).await?;
|
|
Ok(res.map(|row| row.get(0)))
|
|
}
|
|
|
|
#[instrument(skip(client, data))]
|
|
pub async fn set_data(
|
|
client: &mut impl GenericClient,
|
|
user: Uuid,
|
|
key: &str,
|
|
data: serde_json::Value,
|
|
) -> Result<(), tokio_postgres::Error> {
|
|
let stmt = client
|
|
.prepare_cached("insert into user_data(owner, \"user\", \"key\", data) values ($1, $1, $2, $3) on conflict (owner, \"user\") do update set data = $3")
|
|
.await?;
|
|
client.execute(&stmt, &[&user, &key, &data]).await?;
|
|
Ok(())
|
|
}
|
|
|
|
#[instrument(skip(client))]
|
|
pub async fn delete_data(
|
|
client: &mut impl GenericClient,
|
|
user: Uuid,
|
|
key: &str,
|
|
) -> Result<(), tokio_postgres::Error> {
|
|
let stmt = client
|
|
.prepare_cached(
|
|
"delete from user_data where \"owner\" = $1 and \"user\" = $1 and \"key\" = $2",
|
|
)
|
|
.await?;
|
|
client.execute(&stmt, &[&user, &key]).await?;
|
|
Ok(())
|
|
}
|
|
}
|