User endpoints

This commit is contained in:
2024-07-30 20:21:33 +03:00
parent 33356f34e8
commit d4c1cdb582
32 changed files with 551 additions and 94 deletions

View File

@ -1,6 +1,6 @@
use std::collections::HashSet;
use futures::TryStreamExt;
use futures::{Stream, TryStreamExt};
use uuid::Uuid;
use crate::{db::permissions::PermissionRaw, Pool};
@ -101,10 +101,8 @@ pub async fn insert(
.map(|record| record.folder_id)
}
pub async fn delete(folder_id: Uuid, pool: &Pool) -> sqlx::Result<Vec<Uuid>> {
pub fn delete(folder_id: Uuid, pool: &Pool) -> impl Stream<Item = sqlx::Result<Uuid>> + '_ {
sqlx::query_file!("sql/delete_folder.sql", folder_id)
.fetch(pool)
.map_ok(|row| row.file_id)
.try_collect()
.await
}

View File

@ -1,3 +1,4 @@
pub mod file;
pub mod folder;
pub mod permissions;
pub mod users;

73
src/db/users.rs Normal file
View File

@ -0,0 +1,73 @@
use futures::{stream::BoxStream, Stream, TryStreamExt};
use serde::Serialize;
use uuid::Uuid;
use crate::Pool;
/// Creates user and returns its id
pub async fn create_user(user_name: &str, user_email: &str, pool: &Pool) -> sqlx::Result<i32> {
let id = sqlx::query!(
"INSERT INTO users(username, email) VALUES ($1, $2) RETURNING user_id",
user_name,
user_email
)
.fetch_one(pool)
.await?
.user_id;
sqlx::query!(
"INSERT INTO folders(owner_id, folder_name) VALUES ($1, $2)",
id,
"ROOT"
)
.execute(pool)
.await?;
Ok(id)
}
/// Deletes the user and returns the files that must be deleted
pub fn delete_user(user_id: i32, pool: &Pool) -> impl Stream<Item = sqlx::Result<Uuid>> + '_ {
sqlx::query_file!("sql/delete_user.sql", user_id)
.fetch(pool)
.map_ok(|record| record.file_id)
}
#[derive(Serialize, Debug)]
pub struct UserInfo {
user_id: i32,
username: String,
email: String,
}
pub async fn update(
user_id: i32,
username: &str,
email: &str,
pool: &Pool,
) -> sqlx::Result<UserInfo> {
sqlx::query_as!(
UserInfo,
"UPDATE users SET username = $2, email = $3 WHERE user_id = $1 RETURNING *",
user_id,
username,
email
)
.fetch_one(pool)
.await
}
pub async fn get(user_id: i32, pool: &Pool) -> sqlx::Result<UserInfo> {
sqlx::query_as!(
UserInfo,
"SELECT user_id, username, email FROM users WHERE user_id = $1",
user_id
)
.fetch_one(pool)
.await
}
pub fn search_for_user<'a>(
search_string: &str,
pool: &'a Pool,
) -> BoxStream<'a, sqlx::Result<UserInfo>> {
sqlx::query_file_as!(UserInfo, "sql/search_for_user.sql", search_string).fetch(pool)
}

View File

@ -1,3 +1,5 @@
use futures::TryStreamExt;
use crate::prelude::*;
#[derive(Deserialize, Debug)]
@ -22,14 +24,12 @@ pub async fn delete(
.handle_internal()?
.can_write_guard()?;
let files_to_delete = db::folder::delete(params.folder_id, &state.pool)
.await
.handle_internal()?;
let storage = &state.storage;
futures::stream::iter(files_to_delete)
.for_each_concurrent(5, |file| async move {
db::folder::delete(params.folder_id, &state.pool)
.try_for_each_concurrent(5, |file| async move {
let _ = storage.delete(file).await;
Ok(())
})
.await;
Ok(())
.await
.handle_internal()
}

View File

@ -1,3 +1,4 @@
pub mod file;
pub mod folder;
pub mod permissions;
pub mod users;

View File

@ -1,4 +1,4 @@
pub mod delete;
pub mod get;
pub mod get_top_level_permitted_folders;
pub mod get_top_level;
pub mod set;

View File

@ -0,0 +1,17 @@
use crate::prelude::*;
#[derive(Deserialize, Debug)]
pub struct Params {
username: String,
email: String,
}
pub async fn create(
State(state): State<AppState>,
Json(params): Json<Params>,
) -> Result<Json<i32>, StatusCode> {
let id = db::users::create_user(&params.username, &params.email, &state.pool)
.await
.handle_internal()?;
Ok(Json(id))
}

View File

@ -0,0 +1,14 @@
use futures::TryStreamExt;
use crate::prelude::*;
pub async fn delete(State(state): State<AppState>, claims: Claims) -> Result<(), StatusCode> {
let storage = &state.storage;
db::users::delete_user(claims.user_id, &state.pool)
.try_for_each_concurrent(5, |file_id| async move {
let _ = storage.delete(file_id).await;
Ok(())
})
.await
.handle_internal()
}

View File

@ -0,0 +1,16 @@
use crate::prelude::*;
#[derive(Deserialize, Debug)]
pub struct Params {
user_id: i32,
}
pub async fn get(
State(state): State<AppState>,
Query(params): Query<Params>,
) -> Result<Json<db::users::UserInfo>, StatusCode> {
let info = db::users::get(params.user_id, &state.pool)
.await
.handle_internal()?;
Ok(Json(info))
}

View File

@ -0,0 +1,5 @@
pub mod create;
pub mod delete;
pub mod get;
pub mod put;
pub mod search;

View File

@ -0,0 +1,18 @@
use crate::prelude::*;
#[derive(Deserialize, Debug)]
pub struct Params {
username: String,
email: String,
}
pub async fn put(
State(state): State<AppState>,
claims: Claims,
Json(params): Json<Params>,
) -> Result<Json<db::users::UserInfo>, StatusCode> {
let info = db::users::update(claims.user_id, &params.username, &params.email, &state.pool)
.await
.handle_internal()?;
Ok(Json(info))
}

View File

@ -0,0 +1,20 @@
use futures::TryStreamExt;
use crate::prelude::*;
#[derive(Deserialize, Debug)]
pub struct Params {
search_string: String,
}
pub async fn search(
State(state): State<AppState>,
Query(params): Query<Params>,
) -> sqlx::Result<Json<Vec<db::users::UserInfo>>, StatusCode> {
let users = db::users::search_for_user(&params.search_string, &state.pool)
.take(5)
.try_collect()
.await
.handle_internal()?;
Ok(Json(users))
}

View File

@ -19,25 +19,6 @@ struct AppState {
storage: FileStorage,
}
async fn create_user(user_name: &str, user_email: &str, pool: &Pool) -> anyhow::Result<i32> {
let id = sqlx::query!(
"INSERT INTO users(username, email) VALUES ($1, $2) RETURNING user_id",
user_name,
user_email
)
.fetch_one(pool)
.await?
.user_id;
sqlx::query!(
"INSERT INTO folders(owner_id, folder_name) VALUES ($1, $2)",
id,
"ROOT"
)
.execute(pool)
.await?;
Ok(id)
}
async fn create_debug_users(pool: &Pool) -> anyhow::Result<()> {
let count = sqlx::query!("SELECT count(user_id) FROM users")
.fetch_one(pool)
@ -49,8 +30,8 @@ async fn create_debug_users(pool: &Pool) -> anyhow::Result<()> {
}
tokio::try_join!(
create_user("Test1", "test1@example.com", pool),
create_user("Test2", "test2@example.com", pool)
db::users::create_user("Test1", "test1@example.com", pool),
db::users::create_user("Test2", "test2@example.com", pool)
)?;
Ok(())
@ -87,7 +68,8 @@ fn app(state: AppState) -> Router {
use axum::{http::header, routing::get};
use endpoints::{
file, folder,
permissions::{self, get_top_level_permitted_folders::get_top_level},
permissions::{self, get_top_level::get_top_level},
users,
};
use tower_http::ServiceBuilderExt as _;
@ -123,6 +105,14 @@ fn app(state: AppState) -> Router {
"/permissions/get_top_level_permitted_folders",
get(get_top_level),
)
.route(
"/users",
get(users::get::get)
.post(users::create::create)
.delete(users::delete::delete)
.put(users::put::put),
)
.route("/users/search", get(users::search::search))
.layer(middleware)
.with_state(state)
}