diff --git a/backend/migrations/20250718180903_create_user_warren_table.sql b/backend/migrations/20250718180903_create_user_warren_table.sql new file mode 100644 index 0000000..e56f0f8 --- /dev/null +++ b/backend/migrations/20250718180903_create_user_warren_table.sql @@ -0,0 +1,11 @@ +CREATE TABLE user_warrens ( + user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + warren_id UUID NOT NULL REFERENCES warrens(id) ON DELETE CASCADE, + can_create_children BOOLEAN NOT NULL, + can_list_files BOOLEAN NOT NULL, + can_read_files BOOLEAN NOT NULL, + can_modify_files BOOLEAN NOT NULL, + can_delete_files BOOLEAN NOT NULL, + can_delete_warren BOOLEAN NOT NULL, + PRIMARY KEY(user_id, warren_id) +); diff --git a/backend/src/lib/domain/warren/models/auth_session/mod.rs b/backend/src/lib/domain/warren/models/auth_session/mod.rs index 3912873..efcf518 100644 --- a/backend/src/lib/domain/warren/models/auth_session/mod.rs +++ b/backend/src/lib/domain/warren/models/auth_session/mod.rs @@ -1,8 +1,11 @@ use chrono::NaiveDateTime; use derive_more::Display; +use requests::FetchAuthSessionError; use thiserror::Error; use uuid::Uuid; +use super::user_warren::requests::FetchUserWarrenError; + pub mod requests; #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, sqlx::FromRow)] @@ -41,6 +44,58 @@ impl AuthSession { } } +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum AuthSessionType { + WarrenAuth, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct AuthSessionIdWithType { + session_type: AuthSessionType, + session_id: AuthSessionId, +} + +impl AuthSessionIdWithType { + pub fn from_str(raw: &str) -> Result { + let trimmed = raw.trim(); + + if trimmed.len() < 1 { + return Err(AuthSessionIdWithTypeError::Empty); + } + + let (prefix_length, session_type) = if trimmed.starts_with("WarrenAuth ") { + ("WarrenAuth ".len(), AuthSessionType::WarrenAuth) + } else { + return Err(AuthSessionIdWithTypeError::InvalidType); + }; + + let session_id = AuthSessionId::new(&trimmed[prefix_length..])?; + + Ok(Self { + session_type, + session_id, + }) + } + + pub fn session_type(&self) -> AuthSessionType { + self.session_type + } + + pub fn session_id(&self) -> &AuthSessionId { + &self.session_id + } +} + +#[derive(Debug, Error)] +pub enum AuthSessionIdWithTypeError { + #[error("Invalid auth session type")] + InvalidType, + #[error(transparent)] + AuthSessionId(#[from] AuthSessionIdError), + #[error("The auth session id is empty")] + Empty, +} + /// A valid auth session id #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Display, sqlx::Type)] #[sqlx(transparent)] @@ -67,3 +122,41 @@ pub enum AuthSessionIdError { #[error("An auth session id must not be empty")] Empty, } + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct AuthRequest { + auth: AuthSessionIdWithType, + value: T, +} + +impl AuthRequest { + pub fn new(auth: AuthSessionIdWithType, value: T) -> Self { + Self { auth, value } + } + + pub fn auth(&self) -> &AuthSessionIdWithType { + &self.auth + } + + pub fn value(&self) -> &T { + &self.value + } + + pub fn unpack(self) -> (AuthSessionIdWithType, T) { + (self.auth, self.value) + } +} + +#[derive(Debug, Error)] +pub enum AuthError { + #[error("The requested operation is not permitted")] + InsufficientPermissions, + #[error(transparent)] + FetchAuthSession(#[from] FetchAuthSessionError), + #[error(transparent)] + FetchUserWarren(#[from] FetchUserWarrenError), + #[error(transparent)] + Custom(T), + #[error(transparent)] + Unknown(#[from] anyhow::Error), +} diff --git a/backend/src/lib/domain/warren/models/mod.rs b/backend/src/lib/domain/warren/models/mod.rs index b5ea86a..028de8b 100644 --- a/backend/src/lib/domain/warren/models/mod.rs +++ b/backend/src/lib/domain/warren/models/mod.rs @@ -1,4 +1,5 @@ pub mod auth_session; pub mod file; pub mod user; +pub mod user_warren; pub mod warren; diff --git a/backend/src/lib/domain/warren/models/user_warren/mod.rs b/backend/src/lib/domain/warren/models/user_warren/mod.rs new file mode 100644 index 0000000..c67bad0 --- /dev/null +++ b/backend/src/lib/domain/warren/models/user_warren/mod.rs @@ -0,0 +1,74 @@ +pub mod requests; + +use uuid::Uuid; + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, sqlx::FromRow)] +pub struct UserWarren { + user_id: Uuid, + warren_id: Uuid, + can_create_children: bool, + can_list_files: bool, + can_read_files: bool, + can_modify_files: bool, + can_delete_files: bool, + can_delete_warren: bool, +} + +impl UserWarren { + pub fn new( + user_id: Uuid, + warren_id: Uuid, + can_create_children: bool, + can_list_files: bool, + can_read_files: bool, + can_modify_files: bool, + can_delete_files: bool, + can_delete_warren: bool, + ) -> Self { + Self { + user_id, + warren_id, + can_create_children, + can_list_files, + can_read_files, + can_modify_files, + can_delete_files, + can_delete_warren, + } + } + + pub fn user_id(&self) -> &Uuid { + &self.user_id + } + + pub fn warren_id(&self) -> &Uuid { + &self.warren_id + } + pub fn into_warren_id(self) -> Uuid { + self.warren_id + } + + pub fn can_create_children(&self) -> bool { + self.can_create_children + } + + pub fn can_list_files(&self) -> bool { + self.can_list_files + } + + pub fn can_read_files(&self) -> bool { + self.can_read_files + } + + pub fn can_modify_files(&self) -> bool { + self.can_modify_files + } + + pub fn can_delete_files(&self) -> bool { + self.can_delete_files + } + + pub fn can_delete_warren(&self) -> bool { + self.can_delete_warren + } +} diff --git a/backend/src/lib/domain/warren/models/user_warren/requests.rs b/backend/src/lib/domain/warren/models/user_warren/requests.rs new file mode 100644 index 0000000..c601c01 --- /dev/null +++ b/backend/src/lib/domain/warren/models/user_warren/requests.rs @@ -0,0 +1,72 @@ +use thiserror::Error; +use uuid::Uuid; + +use crate::domain::warren::models::warren::FetchWarrensError; + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct FetchUserWarrensRequest { + user_id: Uuid, +} + +impl FetchUserWarrensRequest { + pub fn new(user_id: Uuid) -> Self { + Self { user_id } + } + + pub fn user_id(&self) -> &Uuid { + &self.user_id + } +} + +#[derive(Debug, Error)] +pub enum FetchUserWarrensError { + #[error(transparent)] + Unknown(#[from] anyhow::Error), +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct ListWarrensRequest {} + +impl ListWarrensRequest { + pub fn new() -> Self { + Self {} + } +} + +#[derive(Debug, Error)] +pub enum ListWarrensError { + #[error(transparent)] + FetchUserWarrenIds(#[from] FetchUserWarrensError), + #[error(transparent)] + ListWarrens(#[from] FetchWarrensError), + #[error(transparent)] + Unknown(#[from] anyhow::Error), +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct FetchUserWarrenRequest { + user_id: Uuid, + warren_id: Uuid, +} + +impl FetchUserWarrenRequest { + pub fn new(user_id: Uuid, warren_id: Uuid) -> Self { + Self { user_id, warren_id } + } + + pub fn user_id(&self) -> &Uuid { + &self.user_id + } + + pub fn warren_id(&self) -> &Uuid { + &self.warren_id + } +} + +#[derive(Debug, Error)] +pub enum FetchUserWarrenError { + #[error("This user warren does not exist")] + NotFound, + #[error(transparent)] + Unknown(#[from] anyhow::Error), +} diff --git a/backend/src/lib/domain/warren/models/warren/requests.rs b/backend/src/lib/domain/warren/models/warren/requests.rs index 5a354c3..2da449e 100644 --- a/backend/src/lib/domain/warren/models/warren/requests.rs +++ b/backend/src/lib/domain/warren/models/warren/requests.rs @@ -4,8 +4,8 @@ use uuid::Uuid; use crate::domain::warren::models::file::{ AbsoluteFilePath, CreateDirectoryError, CreateDirectoryRequest, CreateFileError, CreateFileRequest, DeleteDirectoryError, DeleteDirectoryRequest, DeleteFileError, - DeleteFileRequest, FileName, FilePath, ListFilesError, ListFilesRequest, RelativeFilePath, - RenameEntryError, RenameEntryRequest, + DeleteFileRequest, File, FileName, FilePath, ListFilesError, ListFilesRequest, + RelativeFilePath, RenameEntryError, RenameEntryRequest, }; use super::Warren; @@ -34,16 +34,22 @@ pub enum FetchWarrenError { } #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct ListWarrensRequest; +pub struct FetchWarrensRequest { + ids: Vec, +} -impl ListWarrensRequest { - pub fn new() -> Self { - Self {} +impl FetchWarrensRequest { + pub fn new(ids: Vec) -> Self { + Self { ids } + } + + pub fn ids(&self) -> &Vec { + &self.ids } } #[derive(Debug, Error)] -pub enum ListWarrensError { +pub enum FetchWarrensError { #[error(transparent)] Unknown(#[from] anyhow::Error), } @@ -66,6 +72,11 @@ impl ListWarrenFilesRequest { pub fn path(&self) -> &AbsoluteFilePath { &self.path } + + pub fn to_fs_request(self, warren: &Warren) -> ListFilesRequest { + let path = warren.path().clone().join(&self.path.to_relative()); + ListFilesRequest::new(path) + } } impl Into for ListWarrenFilesRequest { @@ -74,10 +85,23 @@ impl Into for ListWarrenFilesRequest { } } -impl ListWarrenFilesRequest { - pub fn to_fs_request(self, warren: &Warren) -> ListFilesRequest { - let path = warren.path().clone().join(&self.path.to_relative()); - ListFilesRequest::new(path) +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct ListWarrenFilesResponse { + warren: Warren, + files: Vec, +} + +impl ListWarrenFilesResponse { + pub fn new(warren: Warren, files: Vec) -> Self { + Self { warren, files } + } + + pub fn warren(&self) -> &Warren { + &self.warren + } + + pub fn files(&self) -> &Vec { + &self.files } } @@ -86,6 +110,8 @@ pub enum ListWarrenFilesError { #[error(transparent)] FileSystem(#[from] ListFilesError), #[error(transparent)] + FetchWarren(#[from] FetchWarrenError), + #[error(transparent)] Unknown(#[from] anyhow::Error), } @@ -107,6 +133,11 @@ impl CreateWarrenDirectoryRequest { pub fn path(&self) -> &AbsoluteFilePath { &self.path } + + pub fn to_fs_request(self, warren: &Warren) -> CreateDirectoryRequest { + let path = warren.path().clone().join(&self.path.to_relative()); + CreateDirectoryRequest::new(path) + } } impl Into for CreateWarrenDirectoryRequest { @@ -115,10 +146,23 @@ impl Into for CreateWarrenDirectoryRequest { } } -impl CreateWarrenDirectoryRequest { - pub fn to_fs_request(self, warren: &Warren) -> CreateDirectoryRequest { - let path = warren.path().clone().join(&self.path.to_relative()); - CreateDirectoryRequest::new(path) +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct CreateWarrenDirectoryResponse { + warren: Warren, + path: FilePath, +} + +impl CreateWarrenDirectoryResponse { + pub fn new(warren: Warren, path: FilePath) -> Self { + Self { warren, path } + } + + pub fn warren(&self) -> &Warren { + &self.warren + } + + pub fn path(&self) -> &FilePath { + &self.path } } @@ -127,6 +171,8 @@ pub enum CreateWarrenDirectoryError { #[error(transparent)] FileSystem(#[from] CreateDirectoryError), #[error(transparent)] + FetchWarren(#[from] FetchWarrenError), + #[error(transparent)] Unknown(#[from] anyhow::Error), } @@ -170,11 +216,33 @@ impl Into for &DeleteWarrenDirectoryRequest { } } +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct DeleteWarrenDirectoryResponse { + warren: Warren, + path: FilePath, +} + +impl DeleteWarrenDirectoryResponse { + pub fn new(warren: Warren, path: FilePath) -> Self { + Self { warren, path } + } + + pub fn warren(&self) -> &Warren { + &self.warren + } + + pub fn path(&self) -> &FilePath { + &self.path + } +} + #[derive(Debug, Error)] pub enum DeleteWarrenDirectoryError { #[error(transparent)] FileSystem(#[from] DeleteDirectoryError), #[error(transparent)] + FetchWarren(#[from] FetchWarrenError), + #[error(transparent)] Unknown(#[from] anyhow::Error), } @@ -209,11 +277,33 @@ impl Into for &DeleteWarrenFileRequest { } } +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct DeleteWarrenFileResponse { + warren: Warren, + path: FilePath, +} + +impl DeleteWarrenFileResponse { + pub fn new(warren: Warren, path: FilePath) -> Self { + Self { warren, path } + } + + pub fn warren(&self) -> &Warren { + &self.warren + } + + pub fn path(&self) -> &FilePath { + &self.path + } +} + #[derive(Debug, Error)] pub enum DeleteWarrenFileError { #[error(transparent)] FileSystem(#[from] DeleteFileError), #[error(transparent)] + FetchWarren(#[from] FetchWarrenError), + #[error(transparent)] Unknown(#[from] anyhow::Error), } @@ -263,6 +353,26 @@ impl Into for &UploadWarrenFilesRequest { } } +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct UploadWarrenFilesResponse { + warren: Warren, + paths: Vec, +} + +impl UploadWarrenFilesResponse { + pub fn new(warren: Warren, paths: Vec) -> Self { + Self { warren, paths } + } + + pub fn warren(&self) -> &Warren { + &self.warren + } + + pub fn paths(&self) -> &Vec { + &self.paths + } +} + #[derive(Debug, Error)] pub enum UploadWarrenFilesError { #[error("Failed to upload the file at index {fail_index}")] @@ -270,6 +380,8 @@ pub enum UploadWarrenFilesError { #[error(transparent)] FileSystem(#[from] CreateFileError), #[error(transparent)] + FetchWarren(#[from] FetchWarrenError), + #[error(transparent)] Unknown(#[from] anyhow::Error), } @@ -352,10 +464,39 @@ impl Into for &RenameWarrenEntryRequest { } } +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct RenameWarrenEntryResponse { + warren: Warren, + old_path: AbsoluteFilePath, + path: FilePath, +} + +impl RenameWarrenEntryResponse { + pub fn new(warren: Warren, old_path: AbsoluteFilePath, path: FilePath) -> Self { + Self { + warren, + old_path, + path, + } + } + + pub fn warren(&self) -> &Warren { + &self.warren + } + + pub fn old_path(&self) -> &AbsoluteFilePath { + &self.old_path + } + + pub fn path(&self) -> &FilePath { + &self.path + } +} + #[derive(Debug, Error)] pub enum RenameWarrenEntryError { #[error(transparent)] - Fetch(#[from] FetchWarrenError), + FetchWarren(#[from] FetchWarrenError), #[error(transparent)] Rename(#[from] RenameEntryError), #[error(transparent)] diff --git a/backend/src/lib/domain/warren/ports/metrics.rs b/backend/src/lib/domain/warren/ports/metrics.rs index d6782f5..2fa28f0 100644 --- a/backend/src/lib/domain/warren/ports/metrics.rs +++ b/backend/src/lib/domain/warren/ports/metrics.rs @@ -61,4 +61,33 @@ pub trait AuthMetrics: Clone + Send + Sync + 'static { fn record_auth_session_fetch_success(&self) -> impl Future + Send; fn record_auth_session_fetch_failure(&self) -> impl Future + Send; + + fn record_auth_fetch_user_warrens_success(&self) -> impl Future + Send; + fn record_auth_fetch_user_warrens_failure(&self) -> impl Future + Send; + + fn record_auth_fetch_user_warren_list_success(&self) -> impl Future + Send; + fn record_auth_fetch_user_warren_list_failure(&self) -> impl Future + Send; + + fn record_auth_warren_fetch_success(&self) -> impl Future + Send; + fn record_auth_warren_fetch_failure(&self) -> impl Future + Send; + + fn record_auth_warren_file_list_success(&self) -> impl Future + Send; + fn record_auth_warren_file_list_failure(&self) -> impl Future + Send; + + fn record_auth_warren_directory_creation_success(&self) -> impl Future + Send; + fn record_auth_warren_directory_creation_failure(&self) -> impl Future + Send; + + fn record_auth_warren_directory_deletion_success(&self) -> impl Future + Send; + fn record_auth_warren_directory_deletion_failure(&self) -> impl Future + Send; + + /// An upload succeeded fully + fn record_auth_warren_files_upload_success(&self) -> impl Future + Send; + /// An upload failed at least partially + fn record_auth_warren_files_upload_failure(&self) -> impl Future + Send; + + fn record_auth_warren_file_deletion_success(&self) -> impl Future + Send; + fn record_auth_warren_file_deletion_failure(&self) -> impl Future + Send; + + fn record_auth_warren_entry_rename_success(&self) -> impl Future + Send; + fn record_auth_warren_entry_rename_failure(&self) -> impl Future + Send; } diff --git a/backend/src/lib/domain/warren/ports/mod.rs b/backend/src/lib/domain/warren/ports/mod.rs index 040112d..6372254 100644 --- a/backend/src/lib/domain/warren/ports/mod.rs +++ b/backend/src/lib/domain/warren/ports/mod.rs @@ -8,7 +8,7 @@ pub use repository::*; use super::models::{ auth_session::{ - AuthSession, + AuthError, AuthRequest, AuthSession, requests::{ CreateAuthSessionError, CreateAuthSessionRequest, FetchAuthSessionError, FetchAuthSessionRequest, FetchAuthSessionResponse, @@ -23,54 +23,62 @@ use super::models::{ LoginUserError, LoginUserRequest, LoginUserResponse, RegisterUserError, RegisterUserRequest, User, }, + user_warren::{ + UserWarren, + requests::{ + FetchUserWarrensError, FetchUserWarrensRequest, ListWarrensError, ListWarrensRequest, + }, + }, warren::{ - CreateWarrenDirectoryError, CreateWarrenDirectoryRequest, DeleteWarrenDirectoryError, - DeleteWarrenDirectoryRequest, DeleteWarrenFileError, DeleteWarrenFileRequest, - FetchWarrenError, FetchWarrenRequest, ListWarrenFilesError, ListWarrenFilesRequest, - ListWarrensError, ListWarrensRequest, RenameWarrenEntryError, RenameWarrenEntryRequest, - UploadWarrenFilesError, UploadWarrenFilesRequest, Warren, + CreateWarrenDirectoryError, CreateWarrenDirectoryRequest, CreateWarrenDirectoryResponse, + DeleteWarrenDirectoryError, DeleteWarrenDirectoryRequest, DeleteWarrenDirectoryResponse, + DeleteWarrenFileError, DeleteWarrenFileRequest, DeleteWarrenFileResponse, FetchWarrenError, + FetchWarrenRequest, FetchWarrensError, FetchWarrensRequest, ListWarrenFilesError, + ListWarrenFilesRequest, ListWarrenFilesResponse, RenameWarrenEntryError, + RenameWarrenEntryRequest, RenameWarrenEntryResponse, UploadWarrenFilesError, + UploadWarrenFilesRequest, UploadWarrenFilesResponse, Warren, }, }; pub trait WarrenService: Clone + Send + Sync + 'static { fn list_warrens( &self, - request: ListWarrensRequest, - ) -> impl Future, ListWarrensError>> + Send; + request: FetchWarrensRequest, + ) -> impl Future, FetchWarrensError>> + Send; fn fetch_warren( &self, request: FetchWarrenRequest, ) -> impl Future> + Send; - fn list_files( + fn list_warren_files( &self, request: ListWarrenFilesRequest, - ) -> impl Future, ListWarrenFilesError>> + Send; + ) -> impl Future> + Send; fn create_warren_directory( &self, request: CreateWarrenDirectoryRequest, - ) -> impl Future> + Send; + ) -> impl Future> + Send; fn delete_warren_directory( &self, request: DeleteWarrenDirectoryRequest, - ) -> impl Future> + Send; + ) -> impl Future> + Send; fn upload_warren_files( &self, request: UploadWarrenFilesRequest, - ) -> impl Future, UploadWarrenFilesError>> + Send; + ) -> impl Future> + Send; fn delete_warren_file( &self, request: DeleteWarrenFileRequest, - ) -> impl Future> + Send; + ) -> impl Future> + Send; fn rename_warren_entry( &self, request: RenameWarrenEntryRequest, - ) -> impl Future> + Send; + ) -> impl Future> + Send; } pub trait FileSystemService: Clone + Send + Sync + 'static { @@ -112,6 +120,7 @@ pub trait AuthService: Clone + Send + Sync + 'static { &self, request: LoginUserRequest, ) -> impl Future> + Send; + fn create_auth_session( &self, request: CreateAuthSessionRequest, @@ -120,4 +129,60 @@ pub trait AuthService: Clone + Send + Sync + 'static { &self, request: FetchAuthSessionRequest, ) -> impl Future> + Send; + + fn list_warrens( + &self, + request: AuthRequest, + warren_service: &WS, + ) -> impl Future, AuthError>> + Send; + + fn fetch_user_warrens( + &self, + request: FetchUserWarrensRequest, + ) -> impl Future, FetchUserWarrensError>> + Send; + + fn fetch_warren( + &self, + request: AuthRequest, + warren_service: &WS, + ) -> impl Future>> + Send; + + fn list_warren_files( + &self, + request: AuthRequest, + warren_service: &WS, + ) -> impl Future>> + Send; + + fn create_warren_directory( + &self, + request: AuthRequest, + warren_service: &WS, + ) -> impl Future< + Output = Result>, + > + Send; + + fn delete_warren_directory( + &self, + request: AuthRequest, + warren_service: &WS, + ) -> impl Future< + Output = Result>, + > + Send; + + fn upload_warren_files( + &self, + request: AuthRequest, + warren_service: &WS, + ) -> impl Future>> + Send; + fn delete_warren_file( + &self, + request: AuthRequest, + warren_service: &WS, + ) -> impl Future>> + Send; + + fn rename_warren_entry( + &self, + request: AuthRequest, + warren_service: &WS, + ) -> impl Future>> + Send; } diff --git a/backend/src/lib/domain/warren/ports/notifier.rs b/backend/src/lib/domain/warren/ports/notifier.rs index 2518dd2..aab309f 100644 --- a/backend/src/lib/domain/warren/ports/notifier.rs +++ b/backend/src/lib/domain/warren/ports/notifier.rs @@ -2,9 +2,13 @@ use uuid::Uuid; use crate::domain::warren::models::{ auth_session::requests::FetchAuthSessionResponse, - file::{AbsoluteFilePath, File, FilePath}, + file::{File, FilePath}, user::{LoginUserResponse, User}, - warren::Warren, + user_warren::UserWarren, + warren::{ + CreateWarrenDirectoryResponse, DeleteWarrenDirectoryResponse, DeleteWarrenFileResponse, + ListWarrenFilesResponse, RenameWarrenEntryResponse, UploadWarrenFilesResponse, Warren, + }, }; pub trait WarrenNotifier: Clone + Send + Sync + 'static { @@ -12,18 +16,15 @@ pub trait WarrenNotifier: Clone + Send + Sync + 'static { fn warren_fetched(&self, warren: &Warren) -> impl Future + Send; fn warren_files_listed( &self, - warren: &Warren, - files: &Vec, + response: &ListWarrenFilesResponse, ) -> impl Future + Send; fn warren_directory_created( &self, - warren: &Warren, - path: &FilePath, + response: &CreateWarrenDirectoryResponse, ) -> impl Future + Send; fn warren_directory_deleted( &self, - warren: &Warren, - path: &FilePath, + response: &DeleteWarrenDirectoryResponse, ) -> impl Future + Send; /// A single file was uploaded /// @@ -40,20 +41,16 @@ pub trait WarrenNotifier: Clone + Send + Sync + 'static { /// * `files`: The files' paths fn warren_files_uploaded( &self, - warren: &Warren, - files: &[FilePath], + response: &UploadWarrenFilesResponse, ) -> impl Future + Send; fn warren_file_deleted( &self, - warren: &Warren, - path: &FilePath, + response: &DeleteWarrenFileResponse, ) -> impl Future + Send; fn warren_entry_renamed( &self, - warren: &Warren, - old_path: &AbsoluteFilePath, - new_path: &FilePath, + response: &RenameWarrenEntryResponse, ) -> impl Future + Send; } @@ -81,4 +78,52 @@ pub trait AuthNotifier: Clone + Send + Sync + 'static { &self, response: &FetchAuthSessionResponse, ) -> impl Future + Send; + + fn auth_user_warrens_listed( + &self, + user: &User, + warrens: &Vec, + ) -> impl Future + Send; + fn auth_user_warrens_fetched( + &self, + user_id: &Uuid, + user_warrens: &Vec, + ) -> impl Future + Send; + + fn auth_warren_fetched(&self, user: &User, warren: &Warren) -> impl Future + Send; + fn auth_warren_files_listed( + &self, + user: &User, + response: &ListWarrenFilesResponse, + ) -> impl Future + Send; + fn auth_warren_directory_created( + &self, + user: &User, + response: &CreateWarrenDirectoryResponse, + ) -> impl Future + Send; + fn auth_warren_directory_deleted( + &self, + user: &User, + response: &DeleteWarrenDirectoryResponse, + ) -> impl Future + Send; + /// A collection of files was uploaded + /// + /// * `warren`: The warren the file was uploaded to + /// * `files`: The files' paths + fn auth_warren_files_uploaded( + &self, + user: &User, + response: &UploadWarrenFilesResponse, + ) -> impl Future + Send; + fn auth_warren_file_deleted( + &self, + user: &User, + response: &DeleteWarrenFileResponse, + ) -> impl Future + Send; + + fn auth_warren_entry_renamed( + &self, + user: &User, + response: &RenameWarrenEntryResponse, + ) -> impl Future + Send; } diff --git a/backend/src/lib/domain/warren/ports/repository.rs b/backend/src/lib/domain/warren/ports/repository.rs index 6bb3149..fec8fb8 100644 --- a/backend/src/lib/domain/warren/ports/repository.rs +++ b/backend/src/lib/domain/warren/ports/repository.rs @@ -15,14 +15,23 @@ use crate::domain::warren::models::{ RegisterUserError, RegisterUserRequest, User, VerifyUserPasswordError, VerifyUserPasswordRequest, }, - warren::{FetchWarrenError, FetchWarrenRequest, ListWarrensError, ListWarrensRequest, Warren}, + user_warren::{ + UserWarren, + requests::{ + FetchUserWarrenError, FetchUserWarrenRequest, FetchUserWarrensError, + FetchUserWarrensRequest, + }, + }, + warren::{ + FetchWarrenError, FetchWarrenRequest, FetchWarrensError, FetchWarrensRequest, Warren, + }, }; pub trait WarrenRepository: Clone + Send + Sync + 'static { fn list_warrens( &self, - request: ListWarrensRequest, - ) -> impl Future, ListWarrensError>> + Send; + request: FetchWarrensRequest, + ) -> impl Future, FetchWarrensError>> + Send; fn fetch_warren( &self, @@ -77,4 +86,12 @@ pub trait AuthRepository: Clone + Send + Sync + 'static { &self, request: FetchAuthSessionRequest, ) -> impl Future> + Send; + fn fetch_user_warrens( + &self, + request: FetchUserWarrensRequest, + ) -> impl Future, FetchUserWarrensError>> + Send; + fn fetch_user_warren( + &self, + request: FetchUserWarrenRequest, + ) -> impl Future> + Send; } diff --git a/backend/src/lib/domain/warren/service/auth.rs b/backend/src/lib/domain/warren/service/auth.rs index 71152ea..622455f 100644 --- a/backend/src/lib/domain/warren/service/auth.rs +++ b/backend/src/lib/domain/warren/service/auth.rs @@ -3,7 +3,7 @@ use crate::{ domain::warren::{ models::{ auth_session::{ - AuthSession, + AuthError, AuthRequest, AuthSession, requests::{ CreateAuthSessionError, CreateAuthSessionRequest, FetchAuthSessionError, FetchAuthSessionRequest, FetchAuthSessionResponse, SessionExpirationTime, @@ -13,8 +13,25 @@ use crate::{ LoginUserError, LoginUserRequest, LoginUserResponse, RegisterUserError, RegisterUserRequest, User, }, + user_warren::{ + UserWarren, + requests::{ + FetchUserWarrenRequest, FetchUserWarrensError, FetchUserWarrensRequest, + ListWarrensError, ListWarrensRequest, + }, + }, + warren::{ + CreateWarrenDirectoryError, CreateWarrenDirectoryRequest, + CreateWarrenDirectoryResponse, DeleteWarrenDirectoryError, + DeleteWarrenDirectoryRequest, DeleteWarrenDirectoryResponse, DeleteWarrenFileError, + DeleteWarrenFileRequest, DeleteWarrenFileResponse, FetchWarrenError, + FetchWarrenRequest, FetchWarrensRequest, ListWarrenFilesError, + ListWarrenFilesRequest, ListWarrenFilesResponse, RenameWarrenEntryError, + RenameWarrenEntryRequest, RenameWarrenEntryResponse, UploadWarrenFilesError, + UploadWarrenFilesRequest, UploadWarrenFilesResponse, Warren, + }, }, - ports::{AuthMetrics, AuthNotifier, AuthRepository, AuthService}, + ports::{AuthMetrics, AuthNotifier, AuthRepository, AuthService, WarrenService}, }, }; @@ -153,4 +170,310 @@ where result } + + async fn fetch_warren( + &self, + request: AuthRequest, + warren_service: &WS, + ) -> Result> { + let (session, request) = request.unpack(); + + let response = self + .fetch_auth_session(FetchAuthSessionRequest::new(session.session_id().clone())) + .await?; + + let result = warren_service.fetch_warren(request).await; + + if let Ok(result) = result.as_ref() { + self.metrics.record_auth_warren_fetch_success().await; + self.notifier + .auth_warren_fetched(response.user(), result) + .await; + } else { + self.metrics.record_auth_warren_fetch_failure().await; + } + + result.map_err(AuthError::Custom) + } + + async fn fetch_user_warrens( + &self, + request: FetchUserWarrensRequest, + ) -> Result, FetchUserWarrensError> { + let user_id = request.user_id().clone(); + let result = self.repository.fetch_user_warrens(request).await; + + if let Ok(warren_ids) = result.as_ref() { + self.metrics.record_auth_fetch_user_warrens_success().await; + self.notifier + .auth_user_warrens_fetched(&user_id, warren_ids) + .await; + } else { + self.metrics.record_auth_fetch_user_warrens_failure().await; + } + + result + } + + async fn list_warrens( + &self, + request: AuthRequest, + warren_service: &WS, + ) -> Result, AuthError> { + let (session, _) = request.unpack(); + + let session_response = self + .fetch_auth_session(FetchAuthSessionRequest::new(session.session_id().clone())) + .await?; + + let ids = self + .repository + .fetch_user_warrens(FetchUserWarrensRequest::new(*session_response.user().id())) + .await + .map_err(|e| AuthError::Custom(e.into()))?; + + let result = warren_service + .list_warrens(FetchWarrensRequest::new( + ids.into_iter().map(|uw| uw.into_warren_id()).collect(), + )) + .await; + + result.map_err(|e| AuthError::Custom(e.into())) + } + + async fn list_warren_files( + &self, + request: AuthRequest, + warren_service: &WS, + ) -> Result> { + let (session, request) = request.unpack(); + + let session_response = self + .fetch_auth_session(FetchAuthSessionRequest::new(session.session_id().clone())) + .await?; + + let user_warren = self + .repository + .fetch_user_warren(FetchUserWarrenRequest::new( + session_response.user().id().clone(), + request.warren_id().clone(), + )) + .await?; + + if !user_warren.can_list_files() { + return Err(AuthError::InsufficientPermissions); + } + + let result = warren_service.list_warren_files(request).await; + + if let Ok(response) = result.as_ref() { + self.metrics.record_auth_warren_file_list_success().await; + self.notifier + .auth_warren_files_listed(session_response.user(), response) + .await; + } else { + self.metrics.record_auth_warren_file_list_failure().await; + } + + result.map_err(AuthError::Custom) + } + + async fn rename_warren_entry( + &self, + request: AuthRequest, + warren_service: &WS, + ) -> Result> { + let (session, request) = request.unpack(); + + let session_response = self + .fetch_auth_session(FetchAuthSessionRequest::new(session.session_id().clone())) + .await?; + + let user_warren = self + .repository + .fetch_user_warren(FetchUserWarrenRequest::new( + session_response.user().id().clone(), + request.warren_id().clone(), + )) + .await?; + + if !user_warren.can_modify_files() { + return Err(AuthError::InsufficientPermissions); + } + + let result = warren_service.rename_warren_entry(request).await; + + if let Ok(response) = result.as_ref() { + self.metrics.record_auth_warren_entry_rename_success().await; + self.notifier + .auth_warren_entry_renamed(session_response.user(), response) + .await; + } else { + self.metrics.record_auth_warren_entry_rename_failure().await; + } + + result.map_err(AuthError::Custom) + } + + async fn create_warren_directory( + &self, + request: AuthRequest, + warren_service: &WS, + ) -> Result> { + let (session, request) = request.unpack(); + + let session_response = self + .fetch_auth_session(FetchAuthSessionRequest::new(session.session_id().clone())) + .await?; + + let user_warren = self + .repository + .fetch_user_warren(FetchUserWarrenRequest::new( + session_response.user().id().clone(), + request.warren_id().clone(), + )) + .await?; + + // TODO: Maybe create a separate permission for this + if !user_warren.can_modify_files() { + return Err(AuthError::InsufficientPermissions); + } + + let result = warren_service.create_warren_directory(request).await; + + if let Ok(response) = result.as_ref() { + self.metrics + .record_auth_warren_directory_creation_success() + .await; + self.notifier + .auth_warren_directory_created(session_response.user(), response) + .await; + } else { + self.metrics + .record_auth_warren_directory_creation_failure() + .await; + } + + result.map_err(AuthError::Custom) + } + + async fn delete_warren_file( + &self, + request: AuthRequest, + warren_service: &WS, + ) -> Result> { + let (session, request) = request.unpack(); + + let session_response = self + .fetch_auth_session(FetchAuthSessionRequest::new(session.session_id().clone())) + .await?; + + let user_warren = self + .repository + .fetch_user_warren(FetchUserWarrenRequest::new( + session_response.user().id().clone(), + request.warren_id().clone(), + )) + .await?; + + if !user_warren.can_delete_files() { + return Err(AuthError::InsufficientPermissions); + } + + let result = warren_service.delete_warren_file(request).await; + + if let Ok(response) = result.as_ref() { + self.metrics + .record_auth_warren_file_deletion_success() + .await; + self.notifier + .auth_warren_file_deleted(session_response.user(), response) + .await; + } else { + self.metrics + .record_auth_warren_file_deletion_failure() + .await; + } + + result.map_err(AuthError::Custom) + } + + async fn delete_warren_directory( + &self, + request: AuthRequest, + warren_service: &WS, + ) -> Result> { + let (session, request) = request.unpack(); + + let session_response = self + .fetch_auth_session(FetchAuthSessionRequest::new(session.session_id().clone())) + .await?; + + let user_warren = self + .repository + .fetch_user_warren(FetchUserWarrenRequest::new( + session_response.user().id().clone(), + request.warren_id().clone(), + )) + .await?; + + if !user_warren.can_delete_files() { + return Err(AuthError::InsufficientPermissions); + } + + let result = warren_service.delete_warren_directory(request).await; + + if let Ok(response) = result.as_ref() { + self.metrics + .record_auth_warren_directory_deletion_success() + .await; + self.notifier + .auth_warren_directory_deleted(session_response.user(), response) + .await; + } else { + self.metrics + .record_auth_warren_directory_deletion_failure() + .await; + } + + result.map_err(AuthError::Custom) + } + + async fn upload_warren_files( + &self, + request: AuthRequest, + warren_service: &WS, + ) -> Result> { + let (session, request) = request.unpack(); + + let session_response = self + .fetch_auth_session(FetchAuthSessionRequest::new(session.session_id().clone())) + .await?; + + let user_warren = self + .repository + .fetch_user_warren(FetchUserWarrenRequest::new( + session_response.user().id().clone(), + request.warren_id().clone(), + )) + .await?; + + // TODO: Maybe create a separate permission for this + if !user_warren.can_modify_files() { + return Err(AuthError::InsufficientPermissions); + } + + let result = warren_service.upload_warren_files(request).await; + + if let Ok(response) = result.as_ref() { + self.metrics.record_auth_warren_files_upload_success().await; + self.notifier + .auth_warren_files_uploaded(session_response.user(), response) + .await; + } else { + self.metrics.record_auth_warren_files_upload_failure().await; + } + + result.map_err(AuthError::Custom) + } } diff --git a/backend/src/lib/domain/warren/service/warren.rs b/backend/src/lib/domain/warren/service/warren.rs index 268b5b2..54f8e0c 100644 --- a/backend/src/lib/domain/warren/service/warren.rs +++ b/backend/src/lib/domain/warren/service/warren.rs @@ -1,11 +1,8 @@ -use anyhow::Context; - use crate::domain::warren::{ - models::{ - file::{File, FilePath}, - warren::{ - ListWarrensError, ListWarrensRequest, RenameWarrenEntryError, RenameWarrenEntryRequest, - }, + models::warren::{ + CreateWarrenDirectoryResponse, DeleteWarrenDirectoryResponse, DeleteWarrenFileResponse, + FetchWarrensError, FetchWarrensRequest, ListWarrenFilesResponse, RenameWarrenEntryError, + RenameWarrenEntryRequest, RenameWarrenEntryResponse, UploadWarrenFilesResponse, }, ports::FileSystemService, }; @@ -60,8 +57,8 @@ where { async fn list_warrens( &self, - request: ListWarrensRequest, - ) -> Result, ListWarrensError> { + request: FetchWarrensRequest, + ) -> Result, FetchWarrensError> { let result = self.repository.list_warrens(request).await; if let Ok(warren) = result.as_ref() { @@ -87,24 +84,21 @@ where result } - async fn list_files( + async fn list_warren_files( &self, request: ListWarrenFilesRequest, - ) -> Result, ListWarrenFilesError> { - let warren = self - .repository - .fetch_warren(request.clone().into()) - .await - .context("Failed to fetch warren")?; + ) -> Result { + let warren = self.repository.fetch_warren(request.clone().into()).await?; let result = self .fs_service .list_files(request.to_fs_request(&warren)) - .await; + .await + .map(|files| ListWarrenFilesResponse::new(warren, files)); - if let Ok(files) = result.as_ref() { + if let Ok(response) = result.as_ref() { self.metrics.record_list_warren_files_success().await; - self.notifier.warren_files_listed(&warren, files).await; + self.notifier.warren_files_listed(response).await; } else { self.metrics.record_list_warren_files_failure().await; } @@ -115,23 +109,20 @@ where async fn create_warren_directory( &self, request: CreateWarrenDirectoryRequest, - ) -> Result { - let warren = self - .repository - .fetch_warren(request.clone().into()) - .await - .context("Failed to fetch warren")?; + ) -> Result { + let warren = self.repository.fetch_warren(request.clone().into()).await?; let result = self .fs_service .create_directory(request.to_fs_request(&warren)) - .await; + .await + .map(|path| CreateWarrenDirectoryResponse::new(warren, path)); - if let Ok(path) = result.as_ref() { + if let Ok(response) = result.as_ref() { self.metrics .record_warren_directory_creation_success() .await; - self.notifier.warren_directory_created(&warren, path).await; + self.notifier.warren_directory_created(response).await; } else { self.metrics .record_warren_directory_creation_failure() @@ -144,23 +135,20 @@ where async fn delete_warren_directory( &self, request: DeleteWarrenDirectoryRequest, - ) -> Result { - let warren = self - .repository - .fetch_warren((&request).into()) - .await - .context("Failed to fetch warren")?; + ) -> Result { + let warren = self.repository.fetch_warren((&request).into()).await?; let result = self .fs_service .delete_directory(request.to_fs_request(&warren)) - .await; + .await + .map(|path| DeleteWarrenDirectoryResponse::new(warren, path)); - if let Ok(path) = result.as_ref() { + if let Ok(response) = result.as_ref() { self.metrics .record_warren_directory_deletion_success() .await; - self.notifier.warren_directory_deleted(&warren, path).await; + self.notifier.warren_directory_deleted(response).await; } else { self.metrics .record_warren_directory_deletion_failure() @@ -174,12 +162,8 @@ where async fn upload_warren_files( &self, request: UploadWarrenFilesRequest, - ) -> Result, UploadWarrenFilesError> { - let warren = self - .repository - .fetch_warren((&request).into()) - .await - .context("Failed to fetch warren")?; + ) -> Result { + let warren = self.repository.fetch_warren((&request).into()).await?; let fs_requests = request.to_fs_requests(&warren); @@ -202,30 +186,29 @@ where paths.push(file_path); } - self.metrics.record_warren_files_upload_success().await; - self.notifier.warren_files_uploaded(&warren, &paths).await; + let response = UploadWarrenFilesResponse::new(warren, paths); - Ok(paths) + self.metrics.record_warren_files_upload_success().await; + self.notifier.warren_files_uploaded(&response).await; + + Ok(response) } async fn delete_warren_file( &self, request: DeleteWarrenFileRequest, - ) -> Result { - let warren = self - .repository - .fetch_warren((&request).into()) - .await - .context("Failed to fetch warren")?; + ) -> Result { + let warren = self.repository.fetch_warren((&request).into()).await?; let result = self .fs_service .delete_file(request.to_fs_request(&warren)) - .await; + .await + .map(|path| DeleteWarrenFileResponse::new(warren, path)); - if let Ok(path) = result.as_ref() { + if let Ok(response) = result.as_ref() { self.metrics.record_warren_file_deletion_success().await; - self.notifier.warren_file_deleted(&warren, path).await; + self.notifier.warren_file_deleted(response).await; } else { self.metrics.record_warren_file_deletion_failure().await; } @@ -236,20 +219,19 @@ where async fn rename_warren_entry( &self, request: RenameWarrenEntryRequest, - ) -> Result { + ) -> Result { let warren = self.repository.fetch_warren((&request).into()).await?; let old_path = request.path().clone(); let result = self .fs_service .rename_entry(request.to_fs_request(&warren)) - .await; + .await + .map(|new_path| RenameWarrenEntryResponse::new(warren, old_path, new_path)); - if let Ok(new_path) = result.as_ref() { + if let Ok(response) = result.as_ref() { self.metrics.record_warren_entry_rename_success().await; - self.notifier - .warren_entry_renamed(&warren, &old_path, new_path) - .await; + self.notifier.warren_entry_renamed(response).await; } else { self.metrics.record_warren_entry_rename_failure().await; } diff --git a/backend/src/lib/inbound/http/errors.rs b/backend/src/lib/inbound/http/errors.rs index 67a53b5..b8cab09 100644 --- a/backend/src/lib/inbound/http/errors.rs +++ b/backend/src/lib/inbound/http/errors.rs @@ -1,11 +1,12 @@ use crate::{ domain::warren::models::{ - auth_session::requests::FetchAuthSessionError, + auth_session::{AuthError, requests::FetchAuthSessionError}, file::{CreateDirectoryError, DeleteDirectoryError, DeleteFileError, ListFilesError}, user::{LoginUserError, RegisterUserError, VerifyUserPasswordError}, + user_warren::requests::FetchUserWarrenError, warren::{ CreateWarrenDirectoryError, DeleteWarrenDirectoryError, DeleteWarrenFileError, - FetchWarrenError, ListWarrenFilesError, ListWarrensError, RenameWarrenEntryError, + FetchWarrenError, FetchWarrensError, ListWarrenFilesError, RenameWarrenEntryError, UploadWarrenFilesError, }, }, @@ -32,6 +33,7 @@ impl From for ApiError { fn from(value: CreateWarrenDirectoryError) -> Self { match value { CreateWarrenDirectoryError::FileSystem(fs) => fs.into(), + CreateWarrenDirectoryError::FetchWarren(err) => err.into(), CreateWarrenDirectoryError::Unknown(error) => { Self::InternalServerError(error.to_string()) } @@ -57,6 +59,7 @@ impl From for ApiError { fn from(value: DeleteWarrenDirectoryError) -> Self { match value { DeleteWarrenDirectoryError::FileSystem(fs) => fs.into(), + DeleteWarrenDirectoryError::FetchWarren(err) => err.into(), DeleteWarrenDirectoryError::Unknown(error) => { Self::InternalServerError(error.to_string()) } @@ -74,6 +77,7 @@ impl From for ApiError { fn from(value: DeleteWarrenFileError) -> Self { match value { DeleteWarrenFileError::FileSystem(fs) => fs.into(), + DeleteWarrenFileError::FetchWarren(err) => err.into(), DeleteWarrenFileError::Unknown(error) => Self::InternalServerError(error.to_string()), } } @@ -105,13 +109,14 @@ impl From for ApiError { fn from(value: ListWarrenFilesError) -> Self { match value { ListWarrenFilesError::FileSystem(fs_error) => fs_error.into(), + ListWarrenFilesError::FetchWarren(err) => err.into(), ListWarrenFilesError::Unknown(error) => Self::InternalServerError(error.to_string()), } } } -impl From for ApiError { - fn from(error: ListWarrensError) -> Self { +impl From for ApiError { + fn from(error: FetchWarrensError) -> Self { Self::InternalServerError(error.to_string()) } } @@ -167,3 +172,28 @@ impl From for ApiError { } } } + +impl From for ApiError { + fn from(value: FetchUserWarrenError) -> Self { + match value { + FetchUserWarrenError::NotFound => { + Self::NotFound("Could not find the requested warren".to_string()) + } + FetchUserWarrenError::Unknown(err) => Self::InternalServerError(err.to_string()), + } + } +} + +impl From> for ApiError { + fn from(value: AuthError) -> Self { + match value { + AuthError::InsufficientPermissions => { + Self::Unauthorized("Insufficient permissions".to_string()) + } + AuthError::FetchAuthSession(err) => err.into(), + AuthError::FetchUserWarren(err) => err.into(), + AuthError::Custom(err) => Self::InternalServerError(err.to_string()), + AuthError::Unknown(err) => Self::InternalServerError(err.to_string()), + } + } +} diff --git a/backend/src/lib/inbound/http/handlers/extractors.rs b/backend/src/lib/inbound/http/handlers/extractors.rs new file mode 100644 index 0000000..938520d --- /dev/null +++ b/backend/src/lib/inbound/http/handlers/extractors.rs @@ -0,0 +1,35 @@ +use axum::{ + extract::FromRequestParts, + http::{header::AUTHORIZATION, request::Parts}, +}; + +use crate::{ + domain::warren::models::auth_session::AuthSessionIdWithType, inbound::http::responses::ApiError, +}; + +pub struct SessionIdHeader(pub AuthSessionIdWithType); + +impl FromRequestParts for SessionIdHeader +where + S: Send + Sync, +{ + type Rejection = ApiError; + + async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result { + let Some(header) = parts.headers.get(AUTHORIZATION) else { + return Err(ApiError::Unauthorized( + "Missing authorization header".to_string(), + )); + }; + + let header_str = header.to_str().map_err(|_| { + ApiError::InternalServerError( + "Failed to get authorization header as string".to_string(), + ) + })?; + + AuthSessionIdWithType::from_str(header_str) + .map(|session_id| SessionIdHeader(session_id)) + .map_err(|_| ApiError::BadRequest("Invalid session id".to_string())) + } +} diff --git a/backend/src/lib/inbound/http/handlers/mod.rs b/backend/src/lib/inbound/http/handlers/mod.rs index 47b72be..42fccd0 100644 --- a/backend/src/lib/inbound/http/handlers/mod.rs +++ b/backend/src/lib/inbound/http/handlers/mod.rs @@ -4,6 +4,7 @@ use uuid::Uuid; use crate::domain::warren::models::user::User; pub mod auth; +pub mod extractors; pub mod warrens; #[derive(Debug, Clone, Serialize, PartialEq)] diff --git a/backend/src/lib/inbound/http/handlers/warrens/create_warren_directory.rs b/backend/src/lib/inbound/http/handlers/warrens/create_warren_directory.rs index d1e6217..375d6fc 100644 --- a/backend/src/lib/inbound/http/handlers/warrens/create_warren_directory.rs +++ b/backend/src/lib/inbound/http/handlers/warrens/create_warren_directory.rs @@ -6,6 +6,7 @@ use uuid::Uuid; use crate::{ domain::warren::{ models::{ + auth_session::AuthRequest, file::{AbsoluteFilePathError, FilePath, FilePathError}, warren::CreateWarrenDirectoryRequest, }, @@ -13,6 +14,7 @@ use crate::{ }, inbound::http::{ AppState, + handlers::extractors::SessionIdHeader, responses::{ApiError, ApiSuccess}, }, }; @@ -64,13 +66,14 @@ impl CreateWarrenDirectoryHttpRequestBody { pub async fn create_warren_directory( State(state): State>, + SessionIdHeader(session): SessionIdHeader, Json(request): Json, ) -> Result, ApiError> { - let domain_request = request.try_into_domain()?; + let domain_request = AuthRequest::new(session, request.try_into_domain()?); state - .warren_service - .create_warren_directory(domain_request) + .auth_service + .create_warren_directory(domain_request, state.warren_service.as_ref()) .await .map(|_| ApiSuccess::new(StatusCode::CREATED, ())) .map_err(ApiError::from) diff --git a/backend/src/lib/inbound/http/handlers/warrens/delete_warren_directory.rs b/backend/src/lib/inbound/http/handlers/warrens/delete_warren_directory.rs index b223088..965bdd6 100644 --- a/backend/src/lib/inbound/http/handlers/warrens/delete_warren_directory.rs +++ b/backend/src/lib/inbound/http/handlers/warrens/delete_warren_directory.rs @@ -6,6 +6,7 @@ use uuid::Uuid; use crate::{ domain::warren::{ models::{ + auth_session::AuthRequest, file::{AbsoluteFilePathError, FilePath, FilePathError}, warren::DeleteWarrenDirectoryRequest, }, @@ -13,6 +14,7 @@ use crate::{ }, inbound::http::{ AppState, + handlers::extractors::SessionIdHeader, responses::{ApiError, ApiSuccess}, }, }; @@ -66,13 +68,14 @@ impl DeleteWarrenDirectoryHttpRequestBody { pub async fn delete_warren_directory( State(state): State>, + SessionIdHeader(session): SessionIdHeader, Json(request): Json, ) -> Result, ApiError> { - let domain_request = request.try_into_domain()?; + let domain_request = AuthRequest::new(session, request.try_into_domain()?); state - .warren_service - .delete_warren_directory(domain_request) + .auth_service + .delete_warren_directory(domain_request, state.warren_service.as_ref()) .await .map(|_| ApiSuccess::new(StatusCode::CREATED, ())) .map_err(ApiError::from) diff --git a/backend/src/lib/inbound/http/handlers/warrens/delete_warren_file.rs b/backend/src/lib/inbound/http/handlers/warrens/delete_warren_file.rs index f37e138..c232019 100644 --- a/backend/src/lib/inbound/http/handlers/warrens/delete_warren_file.rs +++ b/backend/src/lib/inbound/http/handlers/warrens/delete_warren_file.rs @@ -6,6 +6,7 @@ use uuid::Uuid; use crate::{ domain::warren::{ models::{ + auth_session::AuthRequest, file::{AbsoluteFilePathError, FilePath, FilePathError}, warren::DeleteWarrenFileRequest, }, @@ -13,6 +14,7 @@ use crate::{ }, inbound::http::{ AppState, + handlers::extractors::SessionIdHeader, responses::{ApiError, ApiSuccess}, }, }; @@ -64,13 +66,14 @@ impl DeleteWarrenFileHttpRequestBody { pub async fn delete_warren_file( State(state): State>, + SessionIdHeader(session): SessionIdHeader, Json(request): Json, ) -> Result, ApiError> { - let domain_request = request.try_into_domain()?; + let domain_request = AuthRequest::new(session, request.try_into_domain()?); state - .warren_service - .delete_warren_file(domain_request) + .auth_service + .delete_warren_file(domain_request, state.warren_service.as_ref()) .await .map(|_| ApiSuccess::new(StatusCode::CREATED, ())) .map_err(ApiError::from) diff --git a/backend/src/lib/inbound/http/handlers/warrens/fetch_warren.rs b/backend/src/lib/inbound/http/handlers/warrens/fetch_warren.rs index 5ae4d40..cfe9614 100644 --- a/backend/src/lib/inbound/http/handlers/warrens/fetch_warren.rs +++ b/backend/src/lib/inbound/http/handlers/warrens/fetch_warren.rs @@ -5,11 +5,15 @@ use uuid::Uuid; use crate::{ domain::warren::{ - models::warren::{FetchWarrenRequest, Warren}, + models::{ + auth_session::AuthRequest, + warren::{FetchWarrenRequest, Warren}, + }, ports::{AuthService, WarrenService}, }, inbound::http::{ AppState, + handlers::extractors::SessionIdHeader, responses::{ApiError, ApiSuccess}, }, }; @@ -51,13 +55,14 @@ impl FetchWarrenHttpRequestBody { pub async fn fetch_warren( State(state): State>, + SessionIdHeader(session): SessionIdHeader, Json(request): Json, ) -> Result, ApiError> { - let domain_request = request.try_into_domain()?; + let domain_request = AuthRequest::new(session, request.try_into_domain()?); state - .warren_service - .fetch_warren(domain_request) + .auth_service + .fetch_warren(domain_request, state.warren_service.as_ref()) .await .map(|ref warren| ApiSuccess::new(StatusCode::OK, warren.into())) .map_err(ApiError::from) diff --git a/backend/src/lib/inbound/http/handlers/warrens/list_warren_files.rs b/backend/src/lib/inbound/http/handlers/warrens/list_warren_files.rs index 3a8bd4b..7779516 100644 --- a/backend/src/lib/inbound/http/handlers/warrens/list_warren_files.rs +++ b/backend/src/lib/inbound/http/handlers/warrens/list_warren_files.rs @@ -6,6 +6,7 @@ use uuid::Uuid; use crate::{ domain::warren::{ models::{ + auth_session::AuthRequest, file::{AbsoluteFilePathError, File, FileMimeType, FilePath, FilePathError, FileType}, warren::ListWarrenFilesRequest, }, @@ -13,6 +14,7 @@ use crate::{ }, inbound::http::{ AppState, + handlers::extractors::SessionIdHeader, responses::{ApiError, ApiSuccess}, }, }; @@ -75,8 +77,8 @@ pub struct ListWarrenFilesResponseData { files: Vec, } -impl From for WarrenFileElement { - fn from(value: File) -> Self { +impl From<&File> for WarrenFileElement { + fn from(value: &File) -> Self { Self { name: value.name().to_string(), file_type: value.file_type().to_owned(), @@ -86,24 +88,25 @@ impl From for WarrenFileElement { } } -impl From> for ListWarrenFilesResponseData { - fn from(value: Vec) -> Self { +impl From<&Vec> for ListWarrenFilesResponseData { + fn from(value: &Vec) -> Self { Self { - files: value.into_iter().map(WarrenFileElement::from).collect(), + files: value.iter().map(WarrenFileElement::from).collect(), } } } pub async fn list_warren_files( State(state): State>, + SessionIdHeader(session): SessionIdHeader, Json(request): Json, ) -> Result, ApiError> { - let domain_request = request.try_into_domain()?; + let domain_request = AuthRequest::new(session, request.try_into_domain()?); state - .warren_service - .list_files(domain_request) + .auth_service + .list_warren_files(domain_request, state.warren_service.as_ref()) .await - .map(|files| ApiSuccess::new(StatusCode::OK, files.into())) + .map(|response| ApiSuccess::new(StatusCode::OK, response.files().into())) .map_err(ApiError::from) } diff --git a/backend/src/lib/inbound/http/handlers/warrens/list_warrens.rs b/backend/src/lib/inbound/http/handlers/warrens/list_warrens.rs index f09c1f5..1140a5c 100644 --- a/backend/src/lib/inbound/http/handlers/warrens/list_warrens.rs +++ b/backend/src/lib/inbound/http/handlers/warrens/list_warrens.rs @@ -4,11 +4,14 @@ use uuid::Uuid; use crate::{ domain::warren::{ - models::warren::{ListWarrensRequest, Warren}, + models::{ + auth_session::AuthRequest, user_warren::requests::ListWarrensRequest, warren::Warren, + }, ports::{AuthService, WarrenService}, }, inbound::http::{ AppState, + handlers::extractors::SessionIdHeader, responses::{ApiError, ApiSuccess}, }, }; @@ -43,12 +46,13 @@ impl From<&Vec> for ListWarrensResponseData { pub async fn list_warrens( State(state): State>, + SessionIdHeader(session): SessionIdHeader, ) -> Result, ApiError> { - let domain_request = ListWarrensRequest::new(); + let domain_request = AuthRequest::new(session, ListWarrensRequest::new()); state - .warren_service - .list_warrens(domain_request) + .auth_service + .list_warrens(domain_request, state.warren_service.as_ref()) .await .map_err(ApiError::from) .map(|ref warrens| ApiSuccess::new(StatusCode::OK, warrens.into())) diff --git a/backend/src/lib/inbound/http/handlers/warrens/rename_warren_entry.rs b/backend/src/lib/inbound/http/handlers/warrens/rename_warren_entry.rs index 3424743..328d2eb 100644 --- a/backend/src/lib/inbound/http/handlers/warrens/rename_warren_entry.rs +++ b/backend/src/lib/inbound/http/handlers/warrens/rename_warren_entry.rs @@ -6,6 +6,7 @@ use uuid::Uuid; use crate::{ domain::warren::{ models::{ + auth_session::AuthRequest, file::{ AbsoluteFilePath, AbsoluteFilePathError, FileName, FileNameError, FilePath, FilePathError, @@ -16,6 +17,7 @@ use crate::{ }, inbound::http::{ AppState, + handlers::extractors::SessionIdHeader, responses::{ApiError, ApiSuccess}, }, }; @@ -80,13 +82,14 @@ impl From for ApiError { pub async fn rename_warren_entry( State(state): State>, + SessionIdHeader(session): SessionIdHeader, Json(request): Json, ) -> Result, ApiError> { - let domain_request = request.try_into_domain()?; + let domain_request = AuthRequest::new(session, request.try_into_domain()?); state - .warren_service - .rename_warren_entry(domain_request) + .auth_service + .rename_warren_entry(domain_request, state.warren_service.as_ref()) .await .map(|_| ApiSuccess::new(StatusCode::OK, ())) .map_err(ApiError::from) diff --git a/backend/src/lib/inbound/http/handlers/warrens/upload_warren_files.rs b/backend/src/lib/inbound/http/handlers/warrens/upload_warren_files.rs index bb61a96..d3e60ae 100644 --- a/backend/src/lib/inbound/http/handlers/warrens/upload_warren_files.rs +++ b/backend/src/lib/inbound/http/handlers/warrens/upload_warren_files.rs @@ -6,6 +6,7 @@ use uuid::Uuid; use crate::{ domain::warren::{ models::{ + auth_session::AuthRequest, file::{AbsoluteFilePathError, FileName, FileNameError, FilePath, FilePathError}, warren::{UploadFile, UploadFileList, UploadFileListError, UploadWarrenFilesRequest}, }, @@ -13,6 +14,7 @@ use crate::{ }, inbound::http::{ AppState, + handlers::extractors::SessionIdHeader, responses::{ApiError, ApiSuccess}, }, }; @@ -94,13 +96,14 @@ impl From for ApiError { pub async fn upload_warren_files( State(state): State>, + SessionIdHeader(session): SessionIdHeader, TypedMultipart(multipart): TypedMultipart, ) -> Result, ApiError> { - let domain_request = multipart.try_into_domain()?; + let domain_request = AuthRequest::new(session, multipart.try_into_domain()?); state - .warren_service - .upload_warren_files(domain_request) + .auth_service + .upload_warren_files(domain_request, state.warren_service.as_ref()) .await .map(|_| ApiSuccess::new(StatusCode::CREATED, ())) .map_err(ApiError::from) diff --git a/backend/src/lib/inbound/http/responses.rs b/backend/src/lib/inbound/http/responses.rs index 86ffc73..3cfb8e2 100644 --- a/backend/src/lib/inbound/http/responses.rs +++ b/backend/src/lib/inbound/http/responses.rs @@ -50,6 +50,7 @@ impl IntoResponse for ApiSuccess { pub enum ApiError { BadRequest(String), NotFound(String), + Unauthorized(String), InternalServerError(String), } @@ -66,6 +67,7 @@ impl IntoResponse for ApiError { match self { BadRequest(e) => (StatusCode::BAD_REQUEST, e).into_response(), NotFound(e) => (StatusCode::NOT_FOUND, e).into_response(), + Unauthorized(e) => (StatusCode::UNAUTHORIZED, e).into_response(), InternalServerError(e) => { tracing::error!("{}", e); ( diff --git a/backend/src/lib/outbound/metrics_debug_logger.rs b/backend/src/lib/outbound/metrics_debug_logger.rs index e14ef7c..c94b723 100644 --- a/backend/src/lib/outbound/metrics_debug_logger.rs +++ b/backend/src/lib/outbound/metrics_debug_logger.rs @@ -151,4 +151,67 @@ impl AuthMetrics for MetricsDebugLogger { async fn record_auth_session_fetch_failure(&self) { tracing::debug!("[Metrics] Auth session fetch failed"); } + + async fn record_auth_fetch_user_warren_list_success(&self) { + tracing::debug!("[Metrics] Auth warren list succeeded"); + } + async fn record_auth_fetch_user_warren_list_failure(&self) { + tracing::debug!("[Metrics] Auth warren list failed"); + } + + async fn record_auth_fetch_user_warrens_success(&self) -> () { + tracing::debug!("[Metrics] Auth user warren id fetch succeeded"); + } + async fn record_auth_fetch_user_warrens_failure(&self) -> () { + tracing::debug!("[Metrics] Auth user warren id fetch failed"); + } + + async fn record_auth_warren_fetch_success(&self) { + tracing::debug!("[Metrics] Auth warren fetch succeeded"); + } + async fn record_auth_warren_fetch_failure(&self) { + tracing::debug!("[Metrics] Auth warren fetch failed"); + } + + async fn record_auth_warren_file_list_success(&self) { + tracing::debug!("[Metrics] Auth warren file list succeeded"); + } + async fn record_auth_warren_file_list_failure(&self) { + tracing::debug!("[Metrics] Auth warren file list failed"); + } + + async fn record_auth_warren_directory_creation_success(&self) { + tracing::debug!("[Metrics] Auth warren directory creation succeeded"); + } + async fn record_auth_warren_directory_creation_failure(&self) { + tracing::debug!("[Metrics] Auth warren directory creation failed"); + } + + async fn record_auth_warren_directory_deletion_success(&self) { + tracing::debug!("[Metrics] Auth warren directory deletion succeeded"); + } + async fn record_auth_warren_directory_deletion_failure(&self) { + tracing::debug!("[Metrics] Auth warren directory deletion failed"); + } + + async fn record_auth_warren_file_deletion_success(&self) { + tracing::debug!("[Metrics] Auth warren file deletion succeeded"); + } + async fn record_auth_warren_file_deletion_failure(&self) { + tracing::debug!("[Metrics] Auth warren file deletion failed"); + } + + async fn record_auth_warren_entry_rename_success(&self) { + tracing::debug!("[Metrics] Auth warren entry rename succeeded"); + } + async fn record_auth_warren_entry_rename_failure(&self) { + tracing::debug!("[Metrics] Auth warren entry rename failed"); + } + + async fn record_auth_warren_files_upload_success(&self) { + tracing::debug!("[Metrics] Auth warren files upload succeeded"); + } + async fn record_auth_warren_files_upload_failure(&self) { + tracing::debug!("[Metrics] Auth warren files upload failed"); + } } diff --git a/backend/src/lib/outbound/notifier_debug_logger.rs b/backend/src/lib/outbound/notifier_debug_logger.rs index e6272c8..86a1d7f 100644 --- a/backend/src/lib/outbound/notifier_debug_logger.rs +++ b/backend/src/lib/outbound/notifier_debug_logger.rs @@ -5,7 +5,11 @@ use crate::domain::warren::{ auth_session::requests::FetchAuthSessionResponse, file::{File, FilePath}, user::{LoginUserResponse, User}, - warren::Warren, + user_warren::UserWarren, + warren::{ + CreateWarrenDirectoryResponse, DeleteWarrenDirectoryResponse, DeleteWarrenFileResponse, + ListWarrenFilesResponse, RenameWarrenEntryResponse, UploadWarrenFilesResponse, Warren, + }, }, ports::{AuthNotifier, FileSystemNotifier, WarrenNotifier}, }; @@ -28,27 +32,27 @@ impl WarrenNotifier for NotifierDebugLogger { tracing::debug!("[Notifier] Fetched warren {}", warren.name()); } - async fn warren_files_listed(&self, warren: &Warren, files: &Vec) { + async fn warren_files_listed(&self, response: &ListWarrenFilesResponse) { tracing::debug!( "[Notifier] Listed {} file(s) in warren {}", - files.len(), - warren.name() + response.files().len(), + response.warren().name() ); } - async fn warren_directory_created(&self, warren: &Warren, path: &FilePath) { + async fn warren_directory_created(&self, response: &CreateWarrenDirectoryResponse) { tracing::debug!( "[Notifier] Created directory {} in warren {}", - path, - warren.name() + response.path(), + response.warren().name() ); } - async fn warren_directory_deleted(&self, warren: &Warren, path: &FilePath) { + async fn warren_directory_deleted(&self, response: &DeleteWarrenDirectoryResponse) { tracing::debug!( "[Notifier] Deleted directory {} in warren {}", - path, - warren.name() + response.path(), + response.warren().name() ); } @@ -60,33 +64,28 @@ impl WarrenNotifier for NotifierDebugLogger { ); } - async fn warren_files_uploaded(&self, warren: &Warren, paths: &[FilePath]) { + async fn warren_files_uploaded(&self, response: &UploadWarrenFilesResponse) { tracing::debug!( "[Notifier] Uploaded {} file(s) to warren {}", - paths.len(), - warren.name() + response.paths().len(), + response.warren().name() ); } - async fn warren_file_deleted(&self, warren: &Warren, path: &FilePath) { + async fn warren_file_deleted(&self, response: &DeleteWarrenFileResponse) { tracing::debug!( "[Notifier] Deleted file {} from warren {}", - path, - warren.name(), + response.path(), + response.warren().name(), ); } - async fn warren_entry_renamed( - &self, - warren: &Warren, - old_path: &crate::domain::warren::models::file::AbsoluteFilePath, - new_path: &FilePath, - ) { + async fn warren_entry_renamed(&self, response: &RenameWarrenEntryResponse) { tracing::debug!( "[Notifier] Renamed file {} to {} in warren {}", - old_path, - new_path, - warren.name(), + response.old_path(), + response.path(), + response.warren().name(), ); } } @@ -136,4 +135,91 @@ impl AuthNotifier for NotifierDebugLogger { response.user().id() ); } + + async fn auth_user_warrens_fetched(&self, user_id: &Uuid, warren_ids: &Vec) { + tracing::debug!( + "[Notifier] Fetched {} user warrens for authenticated user {}", + warren_ids.len(), + user_id + ); + } + + async fn auth_user_warrens_listed(&self, user: &User, warrens: &Vec) { + tracing::debug!( + "[Notifier] Fetched {} warren(s) for authenticated user {}", + warrens.len(), + user.id() + ); + } + + async fn auth_warren_fetched(&self, user: &User, warren: &Warren) { + tracing::debug!( + "[Notifier] Fetched warren {} for authenticated user {}", + warren.name(), + user.id(), + ); + } + + async fn auth_warren_files_listed(&self, user: &User, response: &ListWarrenFilesResponse) { + tracing::debug!( + "[Notifier] Listed {} file(s) in warren {} for authenticated user {}", + response.files().len(), + response.warren().name(), + user.id() + ); + } + + async fn auth_warren_directory_created( + &self, + user: &User, + response: &CreateWarrenDirectoryResponse, + ) { + tracing::debug!( + "[Notifier] Created directory {} in warren {} for authenticated user {}", + response.path(), + response.warren().name(), + user.id(), + ); + } + + async fn auth_warren_directory_deleted( + &self, + user: &User, + response: &DeleteWarrenDirectoryResponse, + ) { + tracing::debug!( + "[Notifier] Deleted directory {} in warren {} for authenticated user {}", + response.path(), + response.warren().name(), + user.id(), + ); + } + + async fn auth_warren_files_uploaded(&self, user: &User, response: &UploadWarrenFilesResponse) { + tracing::debug!( + "[Notifier] Uploaded {} file(s) to warren {} for authenticated user {}", + response.paths().len(), + response.warren().name(), + user.id(), + ); + } + + async fn auth_warren_file_deleted(&self, user: &User, response: &DeleteWarrenFileResponse) { + tracing::debug!( + "[Notifier] Deleted file {} from warren {} for authenticated user {}", + response.path(), + response.warren().name(), + user.id(), + ); + } + + async fn auth_warren_entry_renamed(&self, user: &User, response: &RenameWarrenEntryResponse) { + tracing::debug!( + "[Notifier] Renamed file {} to {} in warren {} for authenticated user {}", + response.old_path(), + response.path(), + response.warren().name(), + user.id(), + ); + } } diff --git a/backend/src/lib/outbound/postgres.rs b/backend/src/lib/outbound/postgres.rs index 83eca3f..9fbf489 100644 --- a/backend/src/lib/outbound/postgres.rs +++ b/backend/src/lib/outbound/postgres.rs @@ -28,8 +28,15 @@ use crate::domain::warren::{ RegisterUserError, RegisterUserRequest, User, UserEmail, UserName, UserPassword, VerifyUserPasswordError, VerifyUserPasswordRequest, }, + user_warren::{ + UserWarren, + requests::{ + FetchUserWarrenError, FetchUserWarrenRequest, FetchUserWarrensError, + FetchUserWarrensRequest, + }, + }, warren::{ - FetchWarrenError, FetchWarrenRequest, ListWarrensError, ListWarrensRequest, Warren, + FetchWarrenError, FetchWarrenRequest, FetchWarrensError, FetchWarrensRequest, Warren, }, }, ports::{AuthRepository, WarrenRepository}, @@ -103,6 +110,7 @@ impl Postgres { async fn list_warrens( &self, connection: &mut PgConnection, + ids: &[Uuid], ) -> Result, sqlx::Error> { let warrens: Vec = sqlx::query_as::( " @@ -110,10 +118,11 @@ impl Postgres { * FROM warrens - LIMIT - 50 + WHERE + id = ANY($1) ", ) + .bind(ids) .fetch_all(&mut *connection) .await?; @@ -292,13 +301,60 @@ impl Postgres { Ok(session) } + + async fn get_user_warrens( + &self, + connection: &mut PgConnection, + user_id: &Uuid, + ) -> Result, sqlx::Error> { + let ids: Vec = sqlx::query_as( + " + SELECT + * + FROM + user_warrens + WHERE + user_id = $1 + ", + ) + .bind(user_id) + .fetch_all(connection) + .await?; + + Ok(ids) + } + + async fn get_user_warren( + &self, + connection: &mut PgConnection, + user_id: &Uuid, + warren_id: &Uuid, + ) -> Result { + let ids: UserWarren = sqlx::query_as( + " + SELECT + * + FROM + user_warrens + WHERE + user_id = $1 AND + warren_id = $2 + ", + ) + .bind(user_id) + .bind(warren_id) + .fetch_one(connection) + .await?; + + Ok(ids) + } } impl WarrenRepository for Postgres { async fn list_warrens( &self, - _request: ListWarrensRequest, - ) -> Result, ListWarrensError> { + request: FetchWarrensRequest, + ) -> Result, FetchWarrensError> { let mut connection = self .pool .acquire() @@ -306,7 +362,7 @@ impl WarrenRepository for Postgres { .context("Failed to get a PostgreSQL connection")?; let warrens = self - .list_warrens(&mut connection) + .list_warrens(&mut connection, request.ids()) .await .map_err(|err| anyhow!(err).context("Failed to list warrens"))?; @@ -424,6 +480,45 @@ impl AuthRepository for Postgres { Ok(FetchAuthSessionResponse::new(session, user)) } + + async fn fetch_user_warrens( + &self, + request: FetchUserWarrensRequest, + ) -> Result, FetchUserWarrensError> { + let mut connection = self + .pool + .acquire() + .await + .context("Failed to get a PostgreSQL connection")?; + + let warren_ids = self + .get_user_warrens(&mut connection, request.user_id()) + .await + .context("Failed to get user warrens")?; + + Ok(warren_ids) + } + + async fn fetch_user_warren( + &self, + request: FetchUserWarrenRequest, + ) -> Result { + let mut connection = self + .pool + .acquire() + .await + .context("Failed to get a PostgreSQL connection")?; + + self.get_user_warren(&mut connection, request.user_id(), request.warren_id()) + .await + .map_err(|e| { + if is_not_found_error(&e) { + FetchUserWarrenError::NotFound + } else { + FetchUserWarrenError::Unknown(anyhow!(e)) + } + }) + } } fn is_not_found_error(err: &sqlx::Error) -> bool { diff --git a/frontend/lib/api/index.ts b/frontend/lib/api/index.ts index b6d92fe..f8d5f4a 100644 --- a/frontend/lib/api/index.ts +++ b/frontend/lib/api/index.ts @@ -11,5 +11,7 @@ export function getApiHeaders( } } + headers['content-type'] = 'application/json'; + return headers; } diff --git a/frontend/lib/api/warrens.ts b/frontend/lib/api/warrens.ts index 5db4526..ae23b37 100644 --- a/frontend/lib/api/warrens.ts +++ b/frontend/lib/api/warrens.ts @@ -34,9 +34,7 @@ export async function getWarrenDirectory( ApiResponse<{ files: DirectoryEntry[] }> >(getApiUrl(`warrens/files`), { method: 'POST', - headers: { - 'content-type': 'application/json', - }, + headers: getApiHeaders(), body: JSON.stringify({ warrenId, path, @@ -65,9 +63,7 @@ export async function createDirectory( const { status } = await useFetch(getApiUrl(`warrens/files/directory`), { method: 'POST', - headers: { - 'content-type': 'application/json', - }, + headers: getApiHeaders(), body: JSON.stringify({ warrenId, path, @@ -106,9 +102,7 @@ export async function deleteWarrenDirectory( const { status } = await useFetch(getApiUrl(`warrens/files/directory`), { method: 'DELETE', - headers: { - 'content-type': 'application/json', - }, + headers: getApiHeaders(), body: JSON.stringify({ warrenId, path, @@ -148,9 +142,7 @@ export async function deleteWarrenFile( const { status } = await useFetch(getApiUrl(`warrens/files/file`), { method: 'DELETE', - headers: { - 'content-type': 'application/json', - }, + headers: getApiHeaders(), body: JSON.stringify({ warrenId, path, @@ -209,6 +201,11 @@ export async function uploadToWarren( body.append('files', file); } + const headers = getApiHeaders(); + for (const [key, value] of Object.entries(headers)) { + xhr.setRequestHeader(key, value); + } + xhr.send(body); try { @@ -244,9 +241,7 @@ export async function renameWarrenEntry( const { status } = await useFetch(getApiUrl(`warrens/files/rename`), { method: 'PATCH', - headers: { - 'content-type': 'application/json', - }, + headers: getApiHeaders(), body: JSON.stringify({ warrenId, path, diff --git a/frontend/stores/index.ts b/frontend/stores/index.ts index b3dc8f6..450f34e 100644 --- a/frontend/stores/index.ts +++ b/frontend/stores/index.ts @@ -21,7 +21,7 @@ export const useWarrenStore = defineStore('warrens', { } if (!this.current.path.endsWith('/')) { - this.current.path += '/'; + path = '/' + path; } this.current.path += path;