diff --git a/migrations/0001_initial.up.sql b/migrations/0001_initial.up.sql index d1675a5..0a744e9 100644 --- a/migrations/0001_initial.up.sql +++ b/migrations/0001_initial.up.sql @@ -32,5 +32,6 @@ CREATE TABLE permission_id SERIAL PRIMARY KEY, user_id INT REFERENCES users (user_id) ON DELETE CASCADE NOT NULL, folder_id UUID REFERENCES folders (folder_id) ON DELETE CASCADE, - permission_type permission NOT NULL + permission_type permission NOT NULL, + UNIQUE (user_id, folder_id) ); \ No newline at end of file diff --git a/sql/delete_permissions.sql b/sql/delete_permissions.sql new file mode 100644 index 0000000..bdcf6f2 --- /dev/null +++ b/sql/delete_permissions.sql @@ -0,0 +1,20 @@ +WITH RECURSIVE folder_hierarchy AS ( + -- Start with the given directory + SELECT + folder_id + FROM + folders + WHERE + folder_id = $1 + + UNION ALL + + -- Recursively find all subdirectories + SELECT + f.folder_id + FROM + folders f + INNER JOIN + folder_hierarchy fh ON f.parent_folder_id = fh.folder_id +) +DELETE FROM permissions WHERE user_id = $2 AND folder_id IN (SELECT folder_id FROM folder_hierarchy) \ No newline at end of file diff --git a/src/db/file.rs b/src/db/file.rs index 9a2ee8d..7b7217c 100644 --- a/src/db/file.rs +++ b/src/db/file.rs @@ -36,13 +36,13 @@ pub struct FileWithoutParentId { file_id: Uuid, file_name: String, file_size: i64, - sha512: Vec, + sha512: String, created_at: chrono::NaiveDateTime, updated_at: chrono::NaiveDateTime, } pub async fn get_files(folder_id: Uuid, pool: &Pool) -> sqlx::Result> { - sqlx::query_as!(FileWithoutParentId, "SELECT file_id, file_name, file_size, sha512, created_at, updated_at FROM files WHERE folder_id = $1", folder_id) + sqlx::query_as!(FileWithoutParentId, r#"SELECT file_id, file_name, file_size, encode(sha512, 'base64') as "sha512!", created_at, updated_at FROM files WHERE folder_id = $1"#, folder_id) .fetch_all(pool) .await } diff --git a/src/db/permissions.rs b/src/db/permissions.rs index 1efb22e..497caf6 100644 --- a/src/db/permissions.rs +++ b/src/db/permissions.rs @@ -2,11 +2,12 @@ use std::collections::HashMap; use axum::http::StatusCode; use futures::TryStreamExt as _; +use serde::{Deserialize, Serialize}; use uuid::Uuid; use crate::Pool; -#[derive(sqlx::Type, Debug)] +#[derive(sqlx::Type, Debug, Serialize, Deserialize)] #[sqlx(type_name = "permission")] #[sqlx(rename_all = "lowercase")] pub enum PermissionRaw { @@ -76,13 +77,12 @@ impl PermissionType { } } -pub async fn insert_permissions( +pub async fn insert( user_id: i32, folder_id: Uuid, - permission_type: PermissionType, + permission_type: PermissionRaw, pool: &Pool, ) -> sqlx::Result<()> { - let permission_type = PermissionRaw::from(permission_type); sqlx::query_file!( "sql/insert_permission.sql", folder_id, @@ -94,13 +94,20 @@ pub async fn insert_permissions( .map(|_| ()) } -pub async fn get_permissions_for_folder( +pub async fn get_all_for_folder( folder_id: Uuid, pool: &Pool, -) -> sqlx::Result> { +) -> sqlx::Result> { sqlx::query_file!("sql/get_all_permissions_for_folder.sql", folder_id) .fetch(pool) - .map_ok(|record| (record.username, Some(record.permission_type).into())) + .map_ok(|record| (record.username, record.permission_type)) .try_collect() .await } + +pub async fn delete_for_folder(folder_id: Uuid, user_id: i32, pool: &Pool) -> sqlx::Result<()> { + sqlx::query_file!("sql/delete_permissions.sql", folder_id, user_id) + .execute(pool) + .await + .map(|_| ()) +} diff --git a/src/endpoints/mod.rs b/src/endpoints/mod.rs index 7c1ccfd..a47f6a0 100644 --- a/src/endpoints/mod.rs +++ b/src/endpoints/mod.rs @@ -1,2 +1,3 @@ pub mod file; pub mod folder; +pub mod permissions; diff --git a/src/endpoints/permissions/delete.rs b/src/endpoints/permissions/delete.rs new file mode 100644 index 0000000..567cf74 --- /dev/null +++ b/src/endpoints/permissions/delete.rs @@ -0,0 +1,26 @@ +use crate::prelude::*; + +#[derive(Deserialize, Debug)] +pub struct Params { + folder_id: Uuid, + user_id: i32, +} + +pub async fn delete( + claims: Claims, + State(state): State, + Json(params): Json, +) -> Result { + if params.user_id != claims.user_id { + db::folder::get_permissions(params.folder_id, claims.user_id, &state.pool) + .await + .handle_internal()? + .can_manage_guard()?; + } + + db::permissions::delete_for_folder(params.folder_id, params.user_id, &state.pool) + .await + .handle_internal()?; + + Ok(StatusCode::NO_CONTENT) +} diff --git a/src/endpoints/permissions/get.rs b/src/endpoints/permissions/get.rs new file mode 100644 index 0000000..667dd39 --- /dev/null +++ b/src/endpoints/permissions/get.rs @@ -0,0 +1,26 @@ +use std::collections::HashMap; + +use db::permissions::PermissionRaw; + +use crate::prelude::*; + +#[derive(Deserialize, Debug)] +pub struct Params { + folder_id: Uuid, +} + +pub async fn get( + Query(params): Query, + claims: Claims, + State(state): State, +) -> Result>, StatusCode> { + db::folder::get_permissions(params.folder_id, claims.user_id, &state.pool) + .await + .handle_internal()? + .can_manage_guard()?; + + let permissions = db::permissions::get_all_for_folder(params.folder_id, &state.pool) + .await + .handle_internal()?; + Ok(Json(permissions)) +} diff --git a/src/endpoints/permissions/mod.rs b/src/endpoints/permissions/mod.rs new file mode 100644 index 0000000..2d47823 --- /dev/null +++ b/src/endpoints/permissions/mod.rs @@ -0,0 +1,3 @@ +pub mod delete; +pub mod get; +pub mod set; diff --git a/src/endpoints/permissions/set.rs b/src/endpoints/permissions/set.rs new file mode 100644 index 0000000..9354609 --- /dev/null +++ b/src/endpoints/permissions/set.rs @@ -0,0 +1,32 @@ +use db::permissions::PermissionRaw; + +use crate::prelude::*; + +#[derive(Deserialize, Debug)] +pub struct Params { + folder_id: Uuid, + permission_type: PermissionRaw, + user_id: i32, +} + +pub async fn set( + claims: Claims, + State(state): State, + Json(params): Json, +) -> Result { + db::folder::get_permissions(params.folder_id, claims.user_id, &state.pool) + .await + .handle_internal()? + .can_manage_guard()?; + + db::permissions::insert( + params.user_id, + params.folder_id, + params.permission_type, + &state.pool, + ) + .await + .handle_internal()?; + + Ok(StatusCode::NO_CONTENT) +} diff --git a/src/main.rs b/src/main.rs index 035e824..6f6ad84 100644 --- a/src/main.rs +++ b/src/main.rs @@ -85,7 +85,7 @@ async fn main() -> anyhow::Result<()> { fn app(state: AppState) -> Router { use axum::http::header; - use endpoints::{file, folder}; + use endpoints::{file, folder, permissions}; use tower_http::ServiceBuilderExt as _; let sensitive_headers = [header::AUTHORIZATION, header::COOKIE]; @@ -110,6 +110,12 @@ fn app(state: AppState) -> Router { .post(folder::create::create) .delete(folder::delete::delete), ) + .route( + "/permissions", + get(permissions::get::get) + .post(permissions::set::set) + .delete(permissions::delete::delete), + ) .layer(middleware) .with_state(state) }