flabk/flabk/src/database/users.rs

148 lines
3.9 KiB
Rust

use std::sync::Arc;
use tokio::sync::Mutex;
use tokio_postgres::{Client, Row};
use crate::sec;
use super::db;
#[derive(Clone)]
pub struct Users(Arc<Mutex<Client>>);
impl Users {
pub fn new(client: Arc<Mutex<Client>>) -> Self {
Self(client)
}
pub async fn create_user(&self, u: User) -> Result<User, db::DBError> {
let row = self.0.lock().await.query_one(
"insert into users (id, username, host, display_name, password_hash, email) values ($1, $2, $3, $4, $5, $6) returning (id, username, host, display_name, password_hash, email, avatar_uri, bio)",
&[&sec::new_id(), &u.username, &u.host, &u.display_name, &u.password_hash, &u.email],
).await?;
Ok(User::from(&row))
}
pub async fn user_stats(&self, by: UserSelect) -> Result<UserStats, db::DBError> {
let (clause, param) = by.into();
let rows = self
.0
.lock()
.await
.query(
format!(r#"select count(follows.*) as following, count(followed.*) as followers from users
left join follows on follows.user_id = users.id
left join follows followed on followed.follows_id = users.id
where {}"#, clause).as_str(),
&[&param],
)
.await?;
Ok(rows.first().ok_or(db::DBError::NotFound)?.into())
}
pub async fn user(&self, by: UserSelect) -> Result<User, db::DBError> {
let (clause, param) = by.into();
let rows = self
.0
.lock()
.await
.query(
format!(
"select id, username, host, display_name, password_hash, email, avatar_uri, bio from users where {}",
clause,
)
.as_str(),
&[&param],
)
.await?;
if let Some(row) = rows.first() && rows.len() == 1 {
Ok(User::from(row))
} else {
Err(db::DBError::NotFound)
}
}
}
#[derive(Debug, Clone)]
pub struct User {
pub id: String,
pub username: String,
pub host: Option<String>,
pub display_name: Option<String>,
pub password_hash: String,
pub email: String,
pub avatar_uri: Option<String>,
pub bio: Option<String>,
}
impl From<&Row> for User {
fn from(row: &Row) -> Self {
Self {
id: row.get("id"),
username: row.get("username"),
host: row.get("host"),
display_name: row.get("display_name"),
password_hash: row.get("password_hash"),
email: row.get("email"),
avatar_uri: row.get("avatar_uri"),
bio: row.get("bio"),
}
}
}
pub enum UserSelect {
ID(String),
Username(String),
FullUsername(String),
}
impl From<String> for UserSelect {
fn from(username: String) -> Self {
if !username.contains("@") {
Self::Username(username)
} else {
Self::FullUsername(username)
}
}
}
impl Into<(String, String)> for UserSelect {
fn into(self) -> (String, String) {
let where_param: String;
let where_clause = match self {
UserSelect::ID(id) => {
where_param = id;
"users.id = $1"
}
UserSelect::Username(username) => {
where_param = username;
"users.username = $1"
}
UserSelect::FullUsername(full) => {
where_param = full;
"(users.username || '@' || users.host) = $1"
}
};
(where_clause.to_owned(), where_param)
}
}
#[derive(Debug, Clone)]
pub struct UserStats {
pub post_count: i64,
pub following: i64,
pub followers: i64,
}
impl From<&Row> for UserStats {
fn from(row: &Row) -> Self {
Self {
post_count: 100,
following: row.get("following"),
followers: row.get("followers"),
}
}
}