2025-03-11 12:15:26 +01:00

167 lines
4.7 KiB
Rust

use deadpool_postgres::GenericClient;
use serde::Serialize;
use tokio_postgres::Row;
use tracing::instrument;
use uuid::{NoContext, Timestamp, Uuid};
use crate::api::{Pagination, PaginationType, RequestPagination};
use super::count;
#[derive(Debug, Serialize)]
#[cfg_attr(feature = "schemas", derive(schemars::JsonSchema))]
pub struct Account {
pub id: Uuid,
pub user: Uuid,
pub name: String,
pub balance: u64,
}
#[derive(Debug, Serialize)]
#[cfg_attr(feature = "schemas", derive(schemars::JsonSchema))]
pub struct AccountInfo {
pub id: Uuid,
pub user: Uuid,
pub name: String,
}
#[derive(Debug, Serialize)]
#[cfg_attr(feature = "schemas", derive(schemars::JsonSchema))]
pub struct UserAccountInfo {
pub id: Uuid,
pub name: String,
pub balance: u64,
}
#[derive(Debug, Serialize)]
#[cfg_attr(feature = "schemas", derive(schemars::JsonSchema))]
pub struct ReducedAccountInfo {
pub id: Uuid,
pub name: String,
}
impl PaginationType for UserAccountInfo {
const NAME: &str = "UserAccounts";
}
impl PaginationType for AccountInfo {
const NAME: &str = "Accounts";
}
impl From<Row> for AccountInfo {
fn from(value: Row) -> Self {
Self {
id: value.get("id"),
user: value.get("user"),
name: value.get("name"),
}
}
}
impl From<Row> for UserAccountInfo {
fn from(value: Row) -> Self {
Self {
id: value.get("id"),
name: value.get("name"),
balance: value.get::<_, i64>("balance") as u64,
}
}
}
impl Account {
#[instrument(skip(client))]
pub async fn create(
client: &mut impl GenericClient,
id: Option<Uuid>,
user: Uuid,
name: &str,
) -> Result<Uuid, tokio_postgres::Error> {
let stmt = client
.prepare_cached(
"insert into accounts(id, \"user\", name, balance) values ($1, $2, $3, $4)",
)
.await?;
let id = id.unwrap_or_else(|| Uuid::new_v7(Timestamp::now(NoContext)));
let balance: i64 = if id == user { 1000 * 100 } else { 0 };
client
.execute(&stmt, &[&id, &user, &name, &balance])
.await?;
Ok(id)
}
#[instrument(skip(client))]
pub async fn list_all(
client: &impl GenericClient,
pagination: RequestPagination,
) -> Result<Pagination<AccountInfo>, tokio_postgres::Error> {
let stmt = client
.prepare_cached("select id,\"user\",name from accounts limit $1 offset $2")
.await?;
let stmt_count = client
.prepare_cached("select count(*) from accounts")
.await?;
let users = client
.query(&stmt, &[&pagination.limit(), &pagination.offset()])
.await?
.into_iter()
.map(AccountInfo::from)
.collect();
let total = count(client, &stmt_count, &[]).await?;
Ok(Pagination::new(users, total, pagination))
}
#[instrument(skip(client))]
pub async fn list_for_user(
client: &impl GenericClient,
user: Uuid,
) -> Result<Vec<UserAccountInfo>, tokio_postgres::Error> {
let stmt = client
.prepare_cached("select id,name,balance from accounts where \"user\" = $1")
.await?;
let users = client
.query(&stmt, &[&user])
.await?
.into_iter()
.map(UserAccountInfo::from)
.collect();
Ok(users)
}
pub async fn by_id(
client: &impl GenericClient,
id: Uuid,
) -> Result<Option<UserAccountInfo>, tokio_postgres::Error> {
let stmt = client
.prepare_cached("select id,name,balance from accounts where id =$1")
.await?;
let res = client.query_opt(&stmt, &[&id]).await?;
Ok(res.map(UserAccountInfo::from))
}
pub async fn owned_by(
client: &impl GenericClient,
account: Uuid,
user: Uuid,
) -> Result<Option<bool>, tokio_postgres::Error> {
let stmt = client
.prepare_cached("select user from accounts where id = $1")
.await?;
let Some(res) = client.query_opt(&stmt, &[&account]).await? else {
return Ok(None);
};
Ok(Some(res.get::<_, Uuid>(0) == user))
}
pub async fn get_for_user(
client: &impl GenericClient,
user: Uuid,
name: &str,
) -> Result<Option<UserAccountInfo>, tokio_postgres::Error> {
let stmt = client
.prepare_cached(
"select id,name,balance from accounts where \"user\" = $1 and name = $2",
)
.await?;
let res = client.query_opt(&stmt, &[&user, &name]).await?;
Ok(res.map(UserAccountInfo::from))
}
}