User endpoints
This commit is contained in:
@ -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
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
pub mod file;
|
||||
pub mod folder;
|
||||
pub mod permissions;
|
||||
pub mod users;
|
||||
|
73
src/db/users.rs
Normal file
73
src/db/users.rs
Normal 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)
|
||||
}
|
@ -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()
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
pub mod file;
|
||||
pub mod folder;
|
||||
pub mod permissions;
|
||||
pub mod users;
|
||||
|
@ -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;
|
||||
|
17
src/endpoints/users/create.rs
Normal file
17
src/endpoints/users/create.rs
Normal 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(¶ms.username, ¶ms.email, &state.pool)
|
||||
.await
|
||||
.handle_internal()?;
|
||||
Ok(Json(id))
|
||||
}
|
14
src/endpoints/users/delete.rs
Normal file
14
src/endpoints/users/delete.rs
Normal 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()
|
||||
}
|
16
src/endpoints/users/get.rs
Normal file
16
src/endpoints/users/get.rs
Normal 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))
|
||||
}
|
5
src/endpoints/users/mod.rs
Normal file
5
src/endpoints/users/mod.rs
Normal file
@ -0,0 +1,5 @@
|
||||
pub mod create;
|
||||
pub mod delete;
|
||||
pub mod get;
|
||||
pub mod put;
|
||||
pub mod search;
|
18
src/endpoints/users/put.rs
Normal file
18
src/endpoints/users/put.rs
Normal 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, ¶ms.username, ¶ms.email, &state.pool)
|
||||
.await
|
||||
.handle_internal()?;
|
||||
Ok(Json(info))
|
||||
}
|
20
src/endpoints/users/search.rs
Normal file
20
src/endpoints/users/search.rs
Normal 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(¶ms.search_string, &state.pool)
|
||||
.take(5)
|
||||
.try_collect()
|
||||
.await
|
||||
.handle_internal()?;
|
||||
Ok(Json(users))
|
||||
}
|
34
src/main.rs
34
src/main.rs
@ -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)
|
||||
}
|
||||
|
Reference in New Issue
Block a user