user permissions

This commit is contained in:
2025-07-19 13:38:58 +02:00
parent ec8e73507c
commit 496d5bdb2b
30 changed files with 1384 additions and 195 deletions

View File

@@ -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)
);

View File

@@ -1,8 +1,11 @@
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
use derive_more::Display; use derive_more::Display;
use requests::FetchAuthSessionError;
use thiserror::Error; use thiserror::Error;
use uuid::Uuid; use uuid::Uuid;
use super::user_warren::requests::FetchUserWarrenError;
pub mod requests; pub mod requests;
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, sqlx::FromRow)] #[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<Self, AuthSessionIdWithTypeError> {
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 /// A valid auth session id
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Display, sqlx::Type)] #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Display, sqlx::Type)]
#[sqlx(transparent)] #[sqlx(transparent)]
@@ -67,3 +122,41 @@ pub enum AuthSessionIdError {
#[error("An auth session id must not be empty")] #[error("An auth session id must not be empty")]
Empty, Empty,
} }
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct AuthRequest<T> {
auth: AuthSessionIdWithType,
value: T,
}
impl<T> AuthRequest<T> {
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<T: std::error::Error> {
#[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),
}

View File

@@ -1,4 +1,5 @@
pub mod auth_session; pub mod auth_session;
pub mod file; pub mod file;
pub mod user; pub mod user;
pub mod user_warren;
pub mod warren; pub mod warren;

View File

@@ -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
}
}

View File

@@ -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),
}

View File

@@ -4,8 +4,8 @@ use uuid::Uuid;
use crate::domain::warren::models::file::{ use crate::domain::warren::models::file::{
AbsoluteFilePath, CreateDirectoryError, CreateDirectoryRequest, CreateFileError, AbsoluteFilePath, CreateDirectoryError, CreateDirectoryRequest, CreateFileError,
CreateFileRequest, DeleteDirectoryError, DeleteDirectoryRequest, DeleteFileError, CreateFileRequest, DeleteDirectoryError, DeleteDirectoryRequest, DeleteFileError,
DeleteFileRequest, FileName, FilePath, ListFilesError, ListFilesRequest, RelativeFilePath, DeleteFileRequest, File, FileName, FilePath, ListFilesError, ListFilesRequest,
RenameEntryError, RenameEntryRequest, RelativeFilePath, RenameEntryError, RenameEntryRequest,
}; };
use super::Warren; use super::Warren;
@@ -34,16 +34,22 @@ pub enum FetchWarrenError {
} }
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ListWarrensRequest; pub struct FetchWarrensRequest {
ids: Vec<Uuid>,
}
impl ListWarrensRequest { impl FetchWarrensRequest {
pub fn new() -> Self { pub fn new(ids: Vec<Uuid>) -> Self {
Self {} Self { ids }
}
pub fn ids(&self) -> &Vec<Uuid> {
&self.ids
} }
} }
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum ListWarrensError { pub enum FetchWarrensError {
#[error(transparent)] #[error(transparent)]
Unknown(#[from] anyhow::Error), Unknown(#[from] anyhow::Error),
} }
@@ -66,6 +72,11 @@ impl ListWarrenFilesRequest {
pub fn path(&self) -> &AbsoluteFilePath { pub fn path(&self) -> &AbsoluteFilePath {
&self.path &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<FetchWarrenRequest> for ListWarrenFilesRequest { impl Into<FetchWarrenRequest> for ListWarrenFilesRequest {
@@ -74,10 +85,23 @@ impl Into<FetchWarrenRequest> for ListWarrenFilesRequest {
} }
} }
impl ListWarrenFilesRequest { #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub fn to_fs_request(self, warren: &Warren) -> ListFilesRequest { pub struct ListWarrenFilesResponse {
let path = warren.path().clone().join(&self.path.to_relative()); warren: Warren,
ListFilesRequest::new(path) files: Vec<File>,
}
impl ListWarrenFilesResponse {
pub fn new(warren: Warren, files: Vec<File>) -> Self {
Self { warren, files }
}
pub fn warren(&self) -> &Warren {
&self.warren
}
pub fn files(&self) -> &Vec<File> {
&self.files
} }
} }
@@ -86,6 +110,8 @@ pub enum ListWarrenFilesError {
#[error(transparent)] #[error(transparent)]
FileSystem(#[from] ListFilesError), FileSystem(#[from] ListFilesError),
#[error(transparent)] #[error(transparent)]
FetchWarren(#[from] FetchWarrenError),
#[error(transparent)]
Unknown(#[from] anyhow::Error), Unknown(#[from] anyhow::Error),
} }
@@ -107,6 +133,11 @@ impl CreateWarrenDirectoryRequest {
pub fn path(&self) -> &AbsoluteFilePath { pub fn path(&self) -> &AbsoluteFilePath {
&self.path &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<FetchWarrenRequest> for CreateWarrenDirectoryRequest { impl Into<FetchWarrenRequest> for CreateWarrenDirectoryRequest {
@@ -115,10 +146,23 @@ impl Into<FetchWarrenRequest> for CreateWarrenDirectoryRequest {
} }
} }
impl CreateWarrenDirectoryRequest { #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub fn to_fs_request(self, warren: &Warren) -> CreateDirectoryRequest { pub struct CreateWarrenDirectoryResponse {
let path = warren.path().clone().join(&self.path.to_relative()); warren: Warren,
CreateDirectoryRequest::new(path) 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)] #[error(transparent)]
FileSystem(#[from] CreateDirectoryError), FileSystem(#[from] CreateDirectoryError),
#[error(transparent)] #[error(transparent)]
FetchWarren(#[from] FetchWarrenError),
#[error(transparent)]
Unknown(#[from] anyhow::Error), Unknown(#[from] anyhow::Error),
} }
@@ -170,11 +216,33 @@ impl Into<FetchWarrenRequest> 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)] #[derive(Debug, Error)]
pub enum DeleteWarrenDirectoryError { pub enum DeleteWarrenDirectoryError {
#[error(transparent)] #[error(transparent)]
FileSystem(#[from] DeleteDirectoryError), FileSystem(#[from] DeleteDirectoryError),
#[error(transparent)] #[error(transparent)]
FetchWarren(#[from] FetchWarrenError),
#[error(transparent)]
Unknown(#[from] anyhow::Error), Unknown(#[from] anyhow::Error),
} }
@@ -209,11 +277,33 @@ impl Into<FetchWarrenRequest> 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)] #[derive(Debug, Error)]
pub enum DeleteWarrenFileError { pub enum DeleteWarrenFileError {
#[error(transparent)] #[error(transparent)]
FileSystem(#[from] DeleteFileError), FileSystem(#[from] DeleteFileError),
#[error(transparent)] #[error(transparent)]
FetchWarren(#[from] FetchWarrenError),
#[error(transparent)]
Unknown(#[from] anyhow::Error), Unknown(#[from] anyhow::Error),
} }
@@ -263,6 +353,26 @@ impl Into<FetchWarrenRequest> for &UploadWarrenFilesRequest {
} }
} }
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct UploadWarrenFilesResponse {
warren: Warren,
paths: Vec<FilePath>,
}
impl UploadWarrenFilesResponse {
pub fn new(warren: Warren, paths: Vec<FilePath>) -> Self {
Self { warren, paths }
}
pub fn warren(&self) -> &Warren {
&self.warren
}
pub fn paths(&self) -> &Vec<FilePath> {
&self.paths
}
}
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum UploadWarrenFilesError { pub enum UploadWarrenFilesError {
#[error("Failed to upload the file at index {fail_index}")] #[error("Failed to upload the file at index {fail_index}")]
@@ -270,6 +380,8 @@ pub enum UploadWarrenFilesError {
#[error(transparent)] #[error(transparent)]
FileSystem(#[from] CreateFileError), FileSystem(#[from] CreateFileError),
#[error(transparent)] #[error(transparent)]
FetchWarren(#[from] FetchWarrenError),
#[error(transparent)]
Unknown(#[from] anyhow::Error), Unknown(#[from] anyhow::Error),
} }
@@ -352,10 +464,39 @@ impl Into<FetchWarrenRequest> 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)] #[derive(Debug, Error)]
pub enum RenameWarrenEntryError { pub enum RenameWarrenEntryError {
#[error(transparent)] #[error(transparent)]
Fetch(#[from] FetchWarrenError), FetchWarren(#[from] FetchWarrenError),
#[error(transparent)] #[error(transparent)]
Rename(#[from] RenameEntryError), Rename(#[from] RenameEntryError),
#[error(transparent)] #[error(transparent)]

View File

@@ -61,4 +61,33 @@ pub trait AuthMetrics: Clone + Send + Sync + 'static {
fn record_auth_session_fetch_success(&self) -> impl Future<Output = ()> + Send; fn record_auth_session_fetch_success(&self) -> impl Future<Output = ()> + Send;
fn record_auth_session_fetch_failure(&self) -> impl Future<Output = ()> + Send; fn record_auth_session_fetch_failure(&self) -> impl Future<Output = ()> + Send;
fn record_auth_fetch_user_warrens_success(&self) -> impl Future<Output = ()> + Send;
fn record_auth_fetch_user_warrens_failure(&self) -> impl Future<Output = ()> + Send;
fn record_auth_fetch_user_warren_list_success(&self) -> impl Future<Output = ()> + Send;
fn record_auth_fetch_user_warren_list_failure(&self) -> impl Future<Output = ()> + Send;
fn record_auth_warren_fetch_success(&self) -> impl Future<Output = ()> + Send;
fn record_auth_warren_fetch_failure(&self) -> impl Future<Output = ()> + Send;
fn record_auth_warren_file_list_success(&self) -> impl Future<Output = ()> + Send;
fn record_auth_warren_file_list_failure(&self) -> impl Future<Output = ()> + Send;
fn record_auth_warren_directory_creation_success(&self) -> impl Future<Output = ()> + Send;
fn record_auth_warren_directory_creation_failure(&self) -> impl Future<Output = ()> + Send;
fn record_auth_warren_directory_deletion_success(&self) -> impl Future<Output = ()> + Send;
fn record_auth_warren_directory_deletion_failure(&self) -> impl Future<Output = ()> + Send;
/// An upload succeeded fully
fn record_auth_warren_files_upload_success(&self) -> impl Future<Output = ()> + Send;
/// An upload failed at least partially
fn record_auth_warren_files_upload_failure(&self) -> impl Future<Output = ()> + Send;
fn record_auth_warren_file_deletion_success(&self) -> impl Future<Output = ()> + Send;
fn record_auth_warren_file_deletion_failure(&self) -> impl Future<Output = ()> + Send;
fn record_auth_warren_entry_rename_success(&self) -> impl Future<Output = ()> + Send;
fn record_auth_warren_entry_rename_failure(&self) -> impl Future<Output = ()> + Send;
} }

View File

@@ -8,7 +8,7 @@ pub use repository::*;
use super::models::{ use super::models::{
auth_session::{ auth_session::{
AuthSession, AuthError, AuthRequest, AuthSession,
requests::{ requests::{
CreateAuthSessionError, CreateAuthSessionRequest, FetchAuthSessionError, CreateAuthSessionError, CreateAuthSessionRequest, FetchAuthSessionError,
FetchAuthSessionRequest, FetchAuthSessionResponse, FetchAuthSessionRequest, FetchAuthSessionResponse,
@@ -23,54 +23,62 @@ use super::models::{
LoginUserError, LoginUserRequest, LoginUserResponse, RegisterUserError, LoginUserError, LoginUserRequest, LoginUserResponse, RegisterUserError,
RegisterUserRequest, User, RegisterUserRequest, User,
}, },
user_warren::{
UserWarren,
requests::{
FetchUserWarrensError, FetchUserWarrensRequest, ListWarrensError, ListWarrensRequest,
},
},
warren::{ warren::{
CreateWarrenDirectoryError, CreateWarrenDirectoryRequest, DeleteWarrenDirectoryError, CreateWarrenDirectoryError, CreateWarrenDirectoryRequest, CreateWarrenDirectoryResponse,
DeleteWarrenDirectoryRequest, DeleteWarrenFileError, DeleteWarrenFileRequest, DeleteWarrenDirectoryError, DeleteWarrenDirectoryRequest, DeleteWarrenDirectoryResponse,
FetchWarrenError, FetchWarrenRequest, ListWarrenFilesError, ListWarrenFilesRequest, DeleteWarrenFileError, DeleteWarrenFileRequest, DeleteWarrenFileResponse, FetchWarrenError,
ListWarrensError, ListWarrensRequest, RenameWarrenEntryError, RenameWarrenEntryRequest, FetchWarrenRequest, FetchWarrensError, FetchWarrensRequest, ListWarrenFilesError,
UploadWarrenFilesError, UploadWarrenFilesRequest, Warren, ListWarrenFilesRequest, ListWarrenFilesResponse, RenameWarrenEntryError,
RenameWarrenEntryRequest, RenameWarrenEntryResponse, UploadWarrenFilesError,
UploadWarrenFilesRequest, UploadWarrenFilesResponse, Warren,
}, },
}; };
pub trait WarrenService: Clone + Send + Sync + 'static { pub trait WarrenService: Clone + Send + Sync + 'static {
fn list_warrens( fn list_warrens(
&self, &self,
request: ListWarrensRequest, request: FetchWarrensRequest,
) -> impl Future<Output = Result<Vec<Warren>, ListWarrensError>> + Send; ) -> impl Future<Output = Result<Vec<Warren>, FetchWarrensError>> + Send;
fn fetch_warren( fn fetch_warren(
&self, &self,
request: FetchWarrenRequest, request: FetchWarrenRequest,
) -> impl Future<Output = Result<Warren, FetchWarrenError>> + Send; ) -> impl Future<Output = Result<Warren, FetchWarrenError>> + Send;
fn list_files( fn list_warren_files(
&self, &self,
request: ListWarrenFilesRequest, request: ListWarrenFilesRequest,
) -> impl Future<Output = Result<Vec<File>, ListWarrenFilesError>> + Send; ) -> impl Future<Output = Result<ListWarrenFilesResponse, ListWarrenFilesError>> + Send;
fn create_warren_directory( fn create_warren_directory(
&self, &self,
request: CreateWarrenDirectoryRequest, request: CreateWarrenDirectoryRequest,
) -> impl Future<Output = Result<FilePath, CreateWarrenDirectoryError>> + Send; ) -> impl Future<Output = Result<CreateWarrenDirectoryResponse, CreateWarrenDirectoryError>> + Send;
fn delete_warren_directory( fn delete_warren_directory(
&self, &self,
request: DeleteWarrenDirectoryRequest, request: DeleteWarrenDirectoryRequest,
) -> impl Future<Output = Result<FilePath, DeleteWarrenDirectoryError>> + Send; ) -> impl Future<Output = Result<DeleteWarrenDirectoryResponse, DeleteWarrenDirectoryError>> + Send;
fn upload_warren_files( fn upload_warren_files(
&self, &self,
request: UploadWarrenFilesRequest, request: UploadWarrenFilesRequest,
) -> impl Future<Output = Result<Vec<FilePath>, UploadWarrenFilesError>> + Send; ) -> impl Future<Output = Result<UploadWarrenFilesResponse, UploadWarrenFilesError>> + Send;
fn delete_warren_file( fn delete_warren_file(
&self, &self,
request: DeleteWarrenFileRequest, request: DeleteWarrenFileRequest,
) -> impl Future<Output = Result<FilePath, DeleteWarrenFileError>> + Send; ) -> impl Future<Output = Result<DeleteWarrenFileResponse, DeleteWarrenFileError>> + Send;
fn rename_warren_entry( fn rename_warren_entry(
&self, &self,
request: RenameWarrenEntryRequest, request: RenameWarrenEntryRequest,
) -> impl Future<Output = Result<FilePath, RenameWarrenEntryError>> + Send; ) -> impl Future<Output = Result<RenameWarrenEntryResponse, RenameWarrenEntryError>> + Send;
} }
pub trait FileSystemService: Clone + Send + Sync + 'static { pub trait FileSystemService: Clone + Send + Sync + 'static {
@@ -112,6 +120,7 @@ pub trait AuthService: Clone + Send + Sync + 'static {
&self, &self,
request: LoginUserRequest, request: LoginUserRequest,
) -> impl Future<Output = Result<LoginUserResponse, LoginUserError>> + Send; ) -> impl Future<Output = Result<LoginUserResponse, LoginUserError>> + Send;
fn create_auth_session( fn create_auth_session(
&self, &self,
request: CreateAuthSessionRequest, request: CreateAuthSessionRequest,
@@ -120,4 +129,60 @@ pub trait AuthService: Clone + Send + Sync + 'static {
&self, &self,
request: FetchAuthSessionRequest, request: FetchAuthSessionRequest,
) -> impl Future<Output = Result<FetchAuthSessionResponse, FetchAuthSessionError>> + Send; ) -> impl Future<Output = Result<FetchAuthSessionResponse, FetchAuthSessionError>> + Send;
fn list_warrens<WS: WarrenService>(
&self,
request: AuthRequest<ListWarrensRequest>,
warren_service: &WS,
) -> impl Future<Output = Result<Vec<Warren>, AuthError<ListWarrensError>>> + Send;
fn fetch_user_warrens(
&self,
request: FetchUserWarrensRequest,
) -> impl Future<Output = Result<Vec<UserWarren>, FetchUserWarrensError>> + Send;
fn fetch_warren<WS: WarrenService>(
&self,
request: AuthRequest<FetchWarrenRequest>,
warren_service: &WS,
) -> impl Future<Output = Result<Warren, AuthError<FetchWarrenError>>> + Send;
fn list_warren_files<WS: WarrenService>(
&self,
request: AuthRequest<ListWarrenFilesRequest>,
warren_service: &WS,
) -> impl Future<Output = Result<ListWarrenFilesResponse, AuthError<ListWarrenFilesError>>> + Send;
fn create_warren_directory<WS: WarrenService>(
&self,
request: AuthRequest<CreateWarrenDirectoryRequest>,
warren_service: &WS,
) -> impl Future<
Output = Result<CreateWarrenDirectoryResponse, AuthError<CreateWarrenDirectoryError>>,
> + Send;
fn delete_warren_directory<WS: WarrenService>(
&self,
request: AuthRequest<DeleteWarrenDirectoryRequest>,
warren_service: &WS,
) -> impl Future<
Output = Result<DeleteWarrenDirectoryResponse, AuthError<DeleteWarrenDirectoryError>>,
> + Send;
fn upload_warren_files<WS: WarrenService>(
&self,
request: AuthRequest<UploadWarrenFilesRequest>,
warren_service: &WS,
) -> impl Future<Output = Result<UploadWarrenFilesResponse, AuthError<UploadWarrenFilesError>>> + Send;
fn delete_warren_file<WS: WarrenService>(
&self,
request: AuthRequest<DeleteWarrenFileRequest>,
warren_service: &WS,
) -> impl Future<Output = Result<DeleteWarrenFileResponse, AuthError<DeleteWarrenFileError>>> + Send;
fn rename_warren_entry<WS: WarrenService>(
&self,
request: AuthRequest<RenameWarrenEntryRequest>,
warren_service: &WS,
) -> impl Future<Output = Result<RenameWarrenEntryResponse, AuthError<RenameWarrenEntryError>>> + Send;
} }

View File

@@ -2,9 +2,13 @@ use uuid::Uuid;
use crate::domain::warren::models::{ use crate::domain::warren::models::{
auth_session::requests::FetchAuthSessionResponse, auth_session::requests::FetchAuthSessionResponse,
file::{AbsoluteFilePath, File, FilePath}, file::{File, FilePath},
user::{LoginUserResponse, User}, user::{LoginUserResponse, User},
warren::Warren, user_warren::UserWarren,
warren::{
CreateWarrenDirectoryResponse, DeleteWarrenDirectoryResponse, DeleteWarrenFileResponse,
ListWarrenFilesResponse, RenameWarrenEntryResponse, UploadWarrenFilesResponse, Warren,
},
}; };
pub trait WarrenNotifier: Clone + Send + Sync + 'static { 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<Output = ()> + Send; fn warren_fetched(&self, warren: &Warren) -> impl Future<Output = ()> + Send;
fn warren_files_listed( fn warren_files_listed(
&self, &self,
warren: &Warren, response: &ListWarrenFilesResponse,
files: &Vec<File>,
) -> impl Future<Output = ()> + Send; ) -> impl Future<Output = ()> + Send;
fn warren_directory_created( fn warren_directory_created(
&self, &self,
warren: &Warren, response: &CreateWarrenDirectoryResponse,
path: &FilePath,
) -> impl Future<Output = ()> + Send; ) -> impl Future<Output = ()> + Send;
fn warren_directory_deleted( fn warren_directory_deleted(
&self, &self,
warren: &Warren, response: &DeleteWarrenDirectoryResponse,
path: &FilePath,
) -> impl Future<Output = ()> + Send; ) -> impl Future<Output = ()> + Send;
/// A single file was uploaded /// A single file was uploaded
/// ///
@@ -40,20 +41,16 @@ pub trait WarrenNotifier: Clone + Send + Sync + 'static {
/// * `files`: The files' paths /// * `files`: The files' paths
fn warren_files_uploaded( fn warren_files_uploaded(
&self, &self,
warren: &Warren, response: &UploadWarrenFilesResponse,
files: &[FilePath],
) -> impl Future<Output = ()> + Send; ) -> impl Future<Output = ()> + Send;
fn warren_file_deleted( fn warren_file_deleted(
&self, &self,
warren: &Warren, response: &DeleteWarrenFileResponse,
path: &FilePath,
) -> impl Future<Output = ()> + Send; ) -> impl Future<Output = ()> + Send;
fn warren_entry_renamed( fn warren_entry_renamed(
&self, &self,
warren: &Warren, response: &RenameWarrenEntryResponse,
old_path: &AbsoluteFilePath,
new_path: &FilePath,
) -> impl Future<Output = ()> + Send; ) -> impl Future<Output = ()> + Send;
} }
@@ -81,4 +78,52 @@ pub trait AuthNotifier: Clone + Send + Sync + 'static {
&self, &self,
response: &FetchAuthSessionResponse, response: &FetchAuthSessionResponse,
) -> impl Future<Output = ()> + Send; ) -> impl Future<Output = ()> + Send;
fn auth_user_warrens_listed(
&self,
user: &User,
warrens: &Vec<Warren>,
) -> impl Future<Output = ()> + Send;
fn auth_user_warrens_fetched(
&self,
user_id: &Uuid,
user_warrens: &Vec<UserWarren>,
) -> impl Future<Output = ()> + Send;
fn auth_warren_fetched(&self, user: &User, warren: &Warren) -> impl Future<Output = ()> + Send;
fn auth_warren_files_listed(
&self,
user: &User,
response: &ListWarrenFilesResponse,
) -> impl Future<Output = ()> + Send;
fn auth_warren_directory_created(
&self,
user: &User,
response: &CreateWarrenDirectoryResponse,
) -> impl Future<Output = ()> + Send;
fn auth_warren_directory_deleted(
&self,
user: &User,
response: &DeleteWarrenDirectoryResponse,
) -> impl Future<Output = ()> + 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<Output = ()> + Send;
fn auth_warren_file_deleted(
&self,
user: &User,
response: &DeleteWarrenFileResponse,
) -> impl Future<Output = ()> + Send;
fn auth_warren_entry_renamed(
&self,
user: &User,
response: &RenameWarrenEntryResponse,
) -> impl Future<Output = ()> + Send;
} }

View File

@@ -15,14 +15,23 @@ use crate::domain::warren::models::{
RegisterUserError, RegisterUserRequest, User, VerifyUserPasswordError, RegisterUserError, RegisterUserRequest, User, VerifyUserPasswordError,
VerifyUserPasswordRequest, 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 { pub trait WarrenRepository: Clone + Send + Sync + 'static {
fn list_warrens( fn list_warrens(
&self, &self,
request: ListWarrensRequest, request: FetchWarrensRequest,
) -> impl Future<Output = Result<Vec<Warren>, ListWarrensError>> + Send; ) -> impl Future<Output = Result<Vec<Warren>, FetchWarrensError>> + Send;
fn fetch_warren( fn fetch_warren(
&self, &self,
@@ -77,4 +86,12 @@ pub trait AuthRepository: Clone + Send + Sync + 'static {
&self, &self,
request: FetchAuthSessionRequest, request: FetchAuthSessionRequest,
) -> impl Future<Output = Result<FetchAuthSessionResponse, FetchAuthSessionError>> + Send; ) -> impl Future<Output = Result<FetchAuthSessionResponse, FetchAuthSessionError>> + Send;
fn fetch_user_warrens(
&self,
request: FetchUserWarrensRequest,
) -> impl Future<Output = Result<Vec<UserWarren>, FetchUserWarrensError>> + Send;
fn fetch_user_warren(
&self,
request: FetchUserWarrenRequest,
) -> impl Future<Output = Result<UserWarren, FetchUserWarrenError>> + Send;
} }

View File

@@ -3,7 +3,7 @@ use crate::{
domain::warren::{ domain::warren::{
models::{ models::{
auth_session::{ auth_session::{
AuthSession, AuthError, AuthRequest, AuthSession,
requests::{ requests::{
CreateAuthSessionError, CreateAuthSessionRequest, FetchAuthSessionError, CreateAuthSessionError, CreateAuthSessionRequest, FetchAuthSessionError,
FetchAuthSessionRequest, FetchAuthSessionResponse, SessionExpirationTime, FetchAuthSessionRequest, FetchAuthSessionResponse, SessionExpirationTime,
@@ -13,8 +13,25 @@ use crate::{
LoginUserError, LoginUserRequest, LoginUserResponse, RegisterUserError, LoginUserError, LoginUserRequest, LoginUserResponse, RegisterUserError,
RegisterUserRequest, User, 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 result
} }
async fn fetch_warren<WS: WarrenService>(
&self,
request: AuthRequest<FetchWarrenRequest>,
warren_service: &WS,
) -> Result<Warren, AuthError<FetchWarrenError>> {
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<Vec<UserWarren>, 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<WS: WarrenService>(
&self,
request: AuthRequest<ListWarrensRequest>,
warren_service: &WS,
) -> Result<Vec<Warren>, AuthError<ListWarrensError>> {
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<WS: WarrenService>(
&self,
request: AuthRequest<ListWarrenFilesRequest>,
warren_service: &WS,
) -> Result<ListWarrenFilesResponse, AuthError<ListWarrenFilesError>> {
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<WS: WarrenService>(
&self,
request: AuthRequest<RenameWarrenEntryRequest>,
warren_service: &WS,
) -> Result<RenameWarrenEntryResponse, AuthError<RenameWarrenEntryError>> {
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<WS: WarrenService>(
&self,
request: AuthRequest<CreateWarrenDirectoryRequest>,
warren_service: &WS,
) -> Result<CreateWarrenDirectoryResponse, AuthError<CreateWarrenDirectoryError>> {
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<WS: WarrenService>(
&self,
request: AuthRequest<DeleteWarrenFileRequest>,
warren_service: &WS,
) -> Result<DeleteWarrenFileResponse, AuthError<DeleteWarrenFileError>> {
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<WS: WarrenService>(
&self,
request: AuthRequest<DeleteWarrenDirectoryRequest>,
warren_service: &WS,
) -> Result<DeleteWarrenDirectoryResponse, AuthError<DeleteWarrenDirectoryError>> {
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<WS: WarrenService>(
&self,
request: AuthRequest<UploadWarrenFilesRequest>,
warren_service: &WS,
) -> Result<UploadWarrenFilesResponse, AuthError<UploadWarrenFilesError>> {
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)
}
} }

View File

@@ -1,11 +1,8 @@
use anyhow::Context;
use crate::domain::warren::{ use crate::domain::warren::{
models::{ models::warren::{
file::{File, FilePath}, CreateWarrenDirectoryResponse, DeleteWarrenDirectoryResponse, DeleteWarrenFileResponse,
warren::{ FetchWarrensError, FetchWarrensRequest, ListWarrenFilesResponse, RenameWarrenEntryError,
ListWarrensError, ListWarrensRequest, RenameWarrenEntryError, RenameWarrenEntryRequest, RenameWarrenEntryRequest, RenameWarrenEntryResponse, UploadWarrenFilesResponse,
},
}, },
ports::FileSystemService, ports::FileSystemService,
}; };
@@ -60,8 +57,8 @@ where
{ {
async fn list_warrens( async fn list_warrens(
&self, &self,
request: ListWarrensRequest, request: FetchWarrensRequest,
) -> Result<Vec<Warren>, ListWarrensError> { ) -> Result<Vec<Warren>, FetchWarrensError> {
let result = self.repository.list_warrens(request).await; let result = self.repository.list_warrens(request).await;
if let Ok(warren) = result.as_ref() { if let Ok(warren) = result.as_ref() {
@@ -87,24 +84,21 @@ where
result result
} }
async fn list_files( async fn list_warren_files(
&self, &self,
request: ListWarrenFilesRequest, request: ListWarrenFilesRequest,
) -> Result<Vec<File>, ListWarrenFilesError> { ) -> Result<ListWarrenFilesResponse, ListWarrenFilesError> {
let warren = self let warren = self.repository.fetch_warren(request.clone().into()).await?;
.repository
.fetch_warren(request.clone().into())
.await
.context("Failed to fetch warren")?;
let result = self let result = self
.fs_service .fs_service
.list_files(request.to_fs_request(&warren)) .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.metrics.record_list_warren_files_success().await;
self.notifier.warren_files_listed(&warren, files).await; self.notifier.warren_files_listed(response).await;
} else { } else {
self.metrics.record_list_warren_files_failure().await; self.metrics.record_list_warren_files_failure().await;
} }
@@ -115,23 +109,20 @@ where
async fn create_warren_directory( async fn create_warren_directory(
&self, &self,
request: CreateWarrenDirectoryRequest, request: CreateWarrenDirectoryRequest,
) -> Result<FilePath, CreateWarrenDirectoryError> { ) -> Result<CreateWarrenDirectoryResponse, CreateWarrenDirectoryError> {
let warren = self let warren = self.repository.fetch_warren(request.clone().into()).await?;
.repository
.fetch_warren(request.clone().into())
.await
.context("Failed to fetch warren")?;
let result = self let result = self
.fs_service .fs_service
.create_directory(request.to_fs_request(&warren)) .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 self.metrics
.record_warren_directory_creation_success() .record_warren_directory_creation_success()
.await; .await;
self.notifier.warren_directory_created(&warren, path).await; self.notifier.warren_directory_created(response).await;
} else { } else {
self.metrics self.metrics
.record_warren_directory_creation_failure() .record_warren_directory_creation_failure()
@@ -144,23 +135,20 @@ where
async fn delete_warren_directory( async fn delete_warren_directory(
&self, &self,
request: DeleteWarrenDirectoryRequest, request: DeleteWarrenDirectoryRequest,
) -> Result<FilePath, DeleteWarrenDirectoryError> { ) -> Result<DeleteWarrenDirectoryResponse, DeleteWarrenDirectoryError> {
let warren = self let warren = self.repository.fetch_warren((&request).into()).await?;
.repository
.fetch_warren((&request).into())
.await
.context("Failed to fetch warren")?;
let result = self let result = self
.fs_service .fs_service
.delete_directory(request.to_fs_request(&warren)) .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 self.metrics
.record_warren_directory_deletion_success() .record_warren_directory_deletion_success()
.await; .await;
self.notifier.warren_directory_deleted(&warren, path).await; self.notifier.warren_directory_deleted(response).await;
} else { } else {
self.metrics self.metrics
.record_warren_directory_deletion_failure() .record_warren_directory_deletion_failure()
@@ -174,12 +162,8 @@ where
async fn upload_warren_files( async fn upload_warren_files(
&self, &self,
request: UploadWarrenFilesRequest, request: UploadWarrenFilesRequest,
) -> Result<Vec<FilePath>, UploadWarrenFilesError> { ) -> Result<UploadWarrenFilesResponse, UploadWarrenFilesError> {
let warren = self let warren = self.repository.fetch_warren((&request).into()).await?;
.repository
.fetch_warren((&request).into())
.await
.context("Failed to fetch warren")?;
let fs_requests = request.to_fs_requests(&warren); let fs_requests = request.to_fs_requests(&warren);
@@ -202,30 +186,29 @@ where
paths.push(file_path); paths.push(file_path);
} }
self.metrics.record_warren_files_upload_success().await; let response = UploadWarrenFilesResponse::new(warren, paths);
self.notifier.warren_files_uploaded(&warren, &paths).await;
Ok(paths) self.metrics.record_warren_files_upload_success().await;
self.notifier.warren_files_uploaded(&response).await;
Ok(response)
} }
async fn delete_warren_file( async fn delete_warren_file(
&self, &self,
request: DeleteWarrenFileRequest, request: DeleteWarrenFileRequest,
) -> Result<FilePath, DeleteWarrenFileError> { ) -> Result<DeleteWarrenFileResponse, DeleteWarrenFileError> {
let warren = self let warren = self.repository.fetch_warren((&request).into()).await?;
.repository
.fetch_warren((&request).into())
.await
.context("Failed to fetch warren")?;
let result = self let result = self
.fs_service .fs_service
.delete_file(request.to_fs_request(&warren)) .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.metrics.record_warren_file_deletion_success().await;
self.notifier.warren_file_deleted(&warren, path).await; self.notifier.warren_file_deleted(response).await;
} else { } else {
self.metrics.record_warren_file_deletion_failure().await; self.metrics.record_warren_file_deletion_failure().await;
} }
@@ -236,20 +219,19 @@ where
async fn rename_warren_entry( async fn rename_warren_entry(
&self, &self,
request: RenameWarrenEntryRequest, request: RenameWarrenEntryRequest,
) -> Result<FilePath, RenameWarrenEntryError> { ) -> Result<RenameWarrenEntryResponse, RenameWarrenEntryError> {
let warren = self.repository.fetch_warren((&request).into()).await?; let warren = self.repository.fetch_warren((&request).into()).await?;
let old_path = request.path().clone(); let old_path = request.path().clone();
let result = self let result = self
.fs_service .fs_service
.rename_entry(request.to_fs_request(&warren)) .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.metrics.record_warren_entry_rename_success().await;
self.notifier self.notifier.warren_entry_renamed(response).await;
.warren_entry_renamed(&warren, &old_path, new_path)
.await;
} else { } else {
self.metrics.record_warren_entry_rename_failure().await; self.metrics.record_warren_entry_rename_failure().await;
} }

View File

@@ -1,11 +1,12 @@
use crate::{ use crate::{
domain::warren::models::{ domain::warren::models::{
auth_session::requests::FetchAuthSessionError, auth_session::{AuthError, requests::FetchAuthSessionError},
file::{CreateDirectoryError, DeleteDirectoryError, DeleteFileError, ListFilesError}, file::{CreateDirectoryError, DeleteDirectoryError, DeleteFileError, ListFilesError},
user::{LoginUserError, RegisterUserError, VerifyUserPasswordError}, user::{LoginUserError, RegisterUserError, VerifyUserPasswordError},
user_warren::requests::FetchUserWarrenError,
warren::{ warren::{
CreateWarrenDirectoryError, DeleteWarrenDirectoryError, DeleteWarrenFileError, CreateWarrenDirectoryError, DeleteWarrenDirectoryError, DeleteWarrenFileError,
FetchWarrenError, ListWarrenFilesError, ListWarrensError, RenameWarrenEntryError, FetchWarrenError, FetchWarrensError, ListWarrenFilesError, RenameWarrenEntryError,
UploadWarrenFilesError, UploadWarrenFilesError,
}, },
}, },
@@ -32,6 +33,7 @@ impl From<CreateWarrenDirectoryError> for ApiError {
fn from(value: CreateWarrenDirectoryError) -> Self { fn from(value: CreateWarrenDirectoryError) -> Self {
match value { match value {
CreateWarrenDirectoryError::FileSystem(fs) => fs.into(), CreateWarrenDirectoryError::FileSystem(fs) => fs.into(),
CreateWarrenDirectoryError::FetchWarren(err) => err.into(),
CreateWarrenDirectoryError::Unknown(error) => { CreateWarrenDirectoryError::Unknown(error) => {
Self::InternalServerError(error.to_string()) Self::InternalServerError(error.to_string())
} }
@@ -57,6 +59,7 @@ impl From<DeleteWarrenDirectoryError> for ApiError {
fn from(value: DeleteWarrenDirectoryError) -> Self { fn from(value: DeleteWarrenDirectoryError) -> Self {
match value { match value {
DeleteWarrenDirectoryError::FileSystem(fs) => fs.into(), DeleteWarrenDirectoryError::FileSystem(fs) => fs.into(),
DeleteWarrenDirectoryError::FetchWarren(err) => err.into(),
DeleteWarrenDirectoryError::Unknown(error) => { DeleteWarrenDirectoryError::Unknown(error) => {
Self::InternalServerError(error.to_string()) Self::InternalServerError(error.to_string())
} }
@@ -74,6 +77,7 @@ impl From<DeleteWarrenFileError> for ApiError {
fn from(value: DeleteWarrenFileError) -> Self { fn from(value: DeleteWarrenFileError) -> Self {
match value { match value {
DeleteWarrenFileError::FileSystem(fs) => fs.into(), DeleteWarrenFileError::FileSystem(fs) => fs.into(),
DeleteWarrenFileError::FetchWarren(err) => err.into(),
DeleteWarrenFileError::Unknown(error) => Self::InternalServerError(error.to_string()), DeleteWarrenFileError::Unknown(error) => Self::InternalServerError(error.to_string()),
} }
} }
@@ -105,13 +109,14 @@ impl From<ListWarrenFilesError> for ApiError {
fn from(value: ListWarrenFilesError) -> Self { fn from(value: ListWarrenFilesError) -> Self {
match value { match value {
ListWarrenFilesError::FileSystem(fs_error) => fs_error.into(), ListWarrenFilesError::FileSystem(fs_error) => fs_error.into(),
ListWarrenFilesError::FetchWarren(err) => err.into(),
ListWarrenFilesError::Unknown(error) => Self::InternalServerError(error.to_string()), ListWarrenFilesError::Unknown(error) => Self::InternalServerError(error.to_string()),
} }
} }
} }
impl From<ListWarrensError> for ApiError { impl From<FetchWarrensError> for ApiError {
fn from(error: ListWarrensError) -> Self { fn from(error: FetchWarrensError) -> Self {
Self::InternalServerError(error.to_string()) Self::InternalServerError(error.to_string())
} }
} }
@@ -167,3 +172,28 @@ impl From<FetchAuthSessionError> for ApiError {
} }
} }
} }
impl From<FetchUserWarrenError> 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<T: std::error::Error> From<AuthError<T>> for ApiError {
fn from(value: AuthError<T>) -> 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()),
}
}
}

View File

@@ -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<S> FromRequestParts<S> for SessionIdHeader
where
S: Send + Sync,
{
type Rejection = ApiError;
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
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()))
}
}

View File

@@ -4,6 +4,7 @@ use uuid::Uuid;
use crate::domain::warren::models::user::User; use crate::domain::warren::models::user::User;
pub mod auth; pub mod auth;
pub mod extractors;
pub mod warrens; pub mod warrens;
#[derive(Debug, Clone, Serialize, PartialEq)] #[derive(Debug, Clone, Serialize, PartialEq)]

View File

@@ -6,6 +6,7 @@ use uuid::Uuid;
use crate::{ use crate::{
domain::warren::{ domain::warren::{
models::{ models::{
auth_session::AuthRequest,
file::{AbsoluteFilePathError, FilePath, FilePathError}, file::{AbsoluteFilePathError, FilePath, FilePathError},
warren::CreateWarrenDirectoryRequest, warren::CreateWarrenDirectoryRequest,
}, },
@@ -13,6 +14,7 @@ use crate::{
}, },
inbound::http::{ inbound::http::{
AppState, AppState,
handlers::extractors::SessionIdHeader,
responses::{ApiError, ApiSuccess}, responses::{ApiError, ApiSuccess},
}, },
}; };
@@ -64,13 +66,14 @@ impl CreateWarrenDirectoryHttpRequestBody {
pub async fn create_warren_directory<WS: WarrenService, AS: AuthService>( pub async fn create_warren_directory<WS: WarrenService, AS: AuthService>(
State(state): State<AppState<WS, AS>>, State(state): State<AppState<WS, AS>>,
SessionIdHeader(session): SessionIdHeader,
Json(request): Json<CreateWarrenDirectoryHttpRequestBody>, Json(request): Json<CreateWarrenDirectoryHttpRequestBody>,
) -> Result<ApiSuccess<()>, ApiError> { ) -> Result<ApiSuccess<()>, ApiError> {
let domain_request = request.try_into_domain()?; let domain_request = AuthRequest::new(session, request.try_into_domain()?);
state state
.warren_service .auth_service
.create_warren_directory(domain_request) .create_warren_directory(domain_request, state.warren_service.as_ref())
.await .await
.map(|_| ApiSuccess::new(StatusCode::CREATED, ())) .map(|_| ApiSuccess::new(StatusCode::CREATED, ()))
.map_err(ApiError::from) .map_err(ApiError::from)

View File

@@ -6,6 +6,7 @@ use uuid::Uuid;
use crate::{ use crate::{
domain::warren::{ domain::warren::{
models::{ models::{
auth_session::AuthRequest,
file::{AbsoluteFilePathError, FilePath, FilePathError}, file::{AbsoluteFilePathError, FilePath, FilePathError},
warren::DeleteWarrenDirectoryRequest, warren::DeleteWarrenDirectoryRequest,
}, },
@@ -13,6 +14,7 @@ use crate::{
}, },
inbound::http::{ inbound::http::{
AppState, AppState,
handlers::extractors::SessionIdHeader,
responses::{ApiError, ApiSuccess}, responses::{ApiError, ApiSuccess},
}, },
}; };
@@ -66,13 +68,14 @@ impl DeleteWarrenDirectoryHttpRequestBody {
pub async fn delete_warren_directory<WS: WarrenService, AS: AuthService>( pub async fn delete_warren_directory<WS: WarrenService, AS: AuthService>(
State(state): State<AppState<WS, AS>>, State(state): State<AppState<WS, AS>>,
SessionIdHeader(session): SessionIdHeader,
Json(request): Json<DeleteWarrenDirectoryHttpRequestBody>, Json(request): Json<DeleteWarrenDirectoryHttpRequestBody>,
) -> Result<ApiSuccess<()>, ApiError> { ) -> Result<ApiSuccess<()>, ApiError> {
let domain_request = request.try_into_domain()?; let domain_request = AuthRequest::new(session, request.try_into_domain()?);
state state
.warren_service .auth_service
.delete_warren_directory(domain_request) .delete_warren_directory(domain_request, state.warren_service.as_ref())
.await .await
.map(|_| ApiSuccess::new(StatusCode::CREATED, ())) .map(|_| ApiSuccess::new(StatusCode::CREATED, ()))
.map_err(ApiError::from) .map_err(ApiError::from)

View File

@@ -6,6 +6,7 @@ use uuid::Uuid;
use crate::{ use crate::{
domain::warren::{ domain::warren::{
models::{ models::{
auth_session::AuthRequest,
file::{AbsoluteFilePathError, FilePath, FilePathError}, file::{AbsoluteFilePathError, FilePath, FilePathError},
warren::DeleteWarrenFileRequest, warren::DeleteWarrenFileRequest,
}, },
@@ -13,6 +14,7 @@ use crate::{
}, },
inbound::http::{ inbound::http::{
AppState, AppState,
handlers::extractors::SessionIdHeader,
responses::{ApiError, ApiSuccess}, responses::{ApiError, ApiSuccess},
}, },
}; };
@@ -64,13 +66,14 @@ impl DeleteWarrenFileHttpRequestBody {
pub async fn delete_warren_file<WS: WarrenService, AS: AuthService>( pub async fn delete_warren_file<WS: WarrenService, AS: AuthService>(
State(state): State<AppState<WS, AS>>, State(state): State<AppState<WS, AS>>,
SessionIdHeader(session): SessionIdHeader,
Json(request): Json<DeleteWarrenFileHttpRequestBody>, Json(request): Json<DeleteWarrenFileHttpRequestBody>,
) -> Result<ApiSuccess<()>, ApiError> { ) -> Result<ApiSuccess<()>, ApiError> {
let domain_request = request.try_into_domain()?; let domain_request = AuthRequest::new(session, request.try_into_domain()?);
state state
.warren_service .auth_service
.delete_warren_file(domain_request) .delete_warren_file(domain_request, state.warren_service.as_ref())
.await .await
.map(|_| ApiSuccess::new(StatusCode::CREATED, ())) .map(|_| ApiSuccess::new(StatusCode::CREATED, ()))
.map_err(ApiError::from) .map_err(ApiError::from)

View File

@@ -5,11 +5,15 @@ use uuid::Uuid;
use crate::{ use crate::{
domain::warren::{ domain::warren::{
models::warren::{FetchWarrenRequest, Warren}, models::{
auth_session::AuthRequest,
warren::{FetchWarrenRequest, Warren},
},
ports::{AuthService, WarrenService}, ports::{AuthService, WarrenService},
}, },
inbound::http::{ inbound::http::{
AppState, AppState,
handlers::extractors::SessionIdHeader,
responses::{ApiError, ApiSuccess}, responses::{ApiError, ApiSuccess},
}, },
}; };
@@ -51,13 +55,14 @@ impl FetchWarrenHttpRequestBody {
pub async fn fetch_warren<WS: WarrenService, AS: AuthService>( pub async fn fetch_warren<WS: WarrenService, AS: AuthService>(
State(state): State<AppState<WS, AS>>, State(state): State<AppState<WS, AS>>,
SessionIdHeader(session): SessionIdHeader,
Json(request): Json<FetchWarrenHttpRequestBody>, Json(request): Json<FetchWarrenHttpRequestBody>,
) -> Result<ApiSuccess<FetchWarrenResponseData>, ApiError> { ) -> Result<ApiSuccess<FetchWarrenResponseData>, ApiError> {
let domain_request = request.try_into_domain()?; let domain_request = AuthRequest::new(session, request.try_into_domain()?);
state state
.warren_service .auth_service
.fetch_warren(domain_request) .fetch_warren(domain_request, state.warren_service.as_ref())
.await .await
.map(|ref warren| ApiSuccess::new(StatusCode::OK, warren.into())) .map(|ref warren| ApiSuccess::new(StatusCode::OK, warren.into()))
.map_err(ApiError::from) .map_err(ApiError::from)

View File

@@ -6,6 +6,7 @@ use uuid::Uuid;
use crate::{ use crate::{
domain::warren::{ domain::warren::{
models::{ models::{
auth_session::AuthRequest,
file::{AbsoluteFilePathError, File, FileMimeType, FilePath, FilePathError, FileType}, file::{AbsoluteFilePathError, File, FileMimeType, FilePath, FilePathError, FileType},
warren::ListWarrenFilesRequest, warren::ListWarrenFilesRequest,
}, },
@@ -13,6 +14,7 @@ use crate::{
}, },
inbound::http::{ inbound::http::{
AppState, AppState,
handlers::extractors::SessionIdHeader,
responses::{ApiError, ApiSuccess}, responses::{ApiError, ApiSuccess},
}, },
}; };
@@ -75,8 +77,8 @@ pub struct ListWarrenFilesResponseData {
files: Vec<WarrenFileElement>, files: Vec<WarrenFileElement>,
} }
impl From<File> for WarrenFileElement { impl From<&File> for WarrenFileElement {
fn from(value: File) -> Self { fn from(value: &File) -> Self {
Self { Self {
name: value.name().to_string(), name: value.name().to_string(),
file_type: value.file_type().to_owned(), file_type: value.file_type().to_owned(),
@@ -86,24 +88,25 @@ impl From<File> for WarrenFileElement {
} }
} }
impl From<Vec<File>> for ListWarrenFilesResponseData { impl From<&Vec<File>> for ListWarrenFilesResponseData {
fn from(value: Vec<File>) -> Self { fn from(value: &Vec<File>) -> Self {
Self { Self {
files: value.into_iter().map(WarrenFileElement::from).collect(), files: value.iter().map(WarrenFileElement::from).collect(),
} }
} }
} }
pub async fn list_warren_files<WS: WarrenService, AS: AuthService>( pub async fn list_warren_files<WS: WarrenService, AS: AuthService>(
State(state): State<AppState<WS, AS>>, State(state): State<AppState<WS, AS>>,
SessionIdHeader(session): SessionIdHeader,
Json(request): Json<ListWarrenFilesHttpRequestBody>, Json(request): Json<ListWarrenFilesHttpRequestBody>,
) -> Result<ApiSuccess<ListWarrenFilesResponseData>, ApiError> { ) -> Result<ApiSuccess<ListWarrenFilesResponseData>, ApiError> {
let domain_request = request.try_into_domain()?; let domain_request = AuthRequest::new(session, request.try_into_domain()?);
state state
.warren_service .auth_service
.list_files(domain_request) .list_warren_files(domain_request, state.warren_service.as_ref())
.await .await
.map(|files| ApiSuccess::new(StatusCode::OK, files.into())) .map(|response| ApiSuccess::new(StatusCode::OK, response.files().into()))
.map_err(ApiError::from) .map_err(ApiError::from)
} }

View File

@@ -4,11 +4,14 @@ use uuid::Uuid;
use crate::{ use crate::{
domain::warren::{ domain::warren::{
models::warren::{ListWarrensRequest, Warren}, models::{
auth_session::AuthRequest, user_warren::requests::ListWarrensRequest, warren::Warren,
},
ports::{AuthService, WarrenService}, ports::{AuthService, WarrenService},
}, },
inbound::http::{ inbound::http::{
AppState, AppState,
handlers::extractors::SessionIdHeader,
responses::{ApiError, ApiSuccess}, responses::{ApiError, ApiSuccess},
}, },
}; };
@@ -43,12 +46,13 @@ impl From<&Vec<Warren>> for ListWarrensResponseData {
pub async fn list_warrens<WS: WarrenService, AS: AuthService>( pub async fn list_warrens<WS: WarrenService, AS: AuthService>(
State(state): State<AppState<WS, AS>>, State(state): State<AppState<WS, AS>>,
SessionIdHeader(session): SessionIdHeader,
) -> Result<ApiSuccess<ListWarrensResponseData>, ApiError> { ) -> Result<ApiSuccess<ListWarrensResponseData>, ApiError> {
let domain_request = ListWarrensRequest::new(); let domain_request = AuthRequest::new(session, ListWarrensRequest::new());
state state
.warren_service .auth_service
.list_warrens(domain_request) .list_warrens(domain_request, state.warren_service.as_ref())
.await .await
.map_err(ApiError::from) .map_err(ApiError::from)
.map(|ref warrens| ApiSuccess::new(StatusCode::OK, warrens.into())) .map(|ref warrens| ApiSuccess::new(StatusCode::OK, warrens.into()))

View File

@@ -6,6 +6,7 @@ use uuid::Uuid;
use crate::{ use crate::{
domain::warren::{ domain::warren::{
models::{ models::{
auth_session::AuthRequest,
file::{ file::{
AbsoluteFilePath, AbsoluteFilePathError, FileName, FileNameError, FilePath, AbsoluteFilePath, AbsoluteFilePathError, FileName, FileNameError, FilePath,
FilePathError, FilePathError,
@@ -16,6 +17,7 @@ use crate::{
}, },
inbound::http::{ inbound::http::{
AppState, AppState,
handlers::extractors::SessionIdHeader,
responses::{ApiError, ApiSuccess}, responses::{ApiError, ApiSuccess},
}, },
}; };
@@ -80,13 +82,14 @@ impl From<ParseRenameWarrenEntryHttpRequestError> for ApiError {
pub async fn rename_warren_entry<WS: WarrenService, AS: AuthService>( pub async fn rename_warren_entry<WS: WarrenService, AS: AuthService>(
State(state): State<AppState<WS, AS>>, State(state): State<AppState<WS, AS>>,
SessionIdHeader(session): SessionIdHeader,
Json(request): Json<RenameWarrenEntryHttpRequestBody>, Json(request): Json<RenameWarrenEntryHttpRequestBody>,
) -> Result<ApiSuccess<()>, ApiError> { ) -> Result<ApiSuccess<()>, ApiError> {
let domain_request = request.try_into_domain()?; let domain_request = AuthRequest::new(session, request.try_into_domain()?);
state state
.warren_service .auth_service
.rename_warren_entry(domain_request) .rename_warren_entry(domain_request, state.warren_service.as_ref())
.await .await
.map(|_| ApiSuccess::new(StatusCode::OK, ())) .map(|_| ApiSuccess::new(StatusCode::OK, ()))
.map_err(ApiError::from) .map_err(ApiError::from)

View File

@@ -6,6 +6,7 @@ use uuid::Uuid;
use crate::{ use crate::{
domain::warren::{ domain::warren::{
models::{ models::{
auth_session::AuthRequest,
file::{AbsoluteFilePathError, FileName, FileNameError, FilePath, FilePathError}, file::{AbsoluteFilePathError, FileName, FileNameError, FilePath, FilePathError},
warren::{UploadFile, UploadFileList, UploadFileListError, UploadWarrenFilesRequest}, warren::{UploadFile, UploadFileList, UploadFileListError, UploadWarrenFilesRequest},
}, },
@@ -13,6 +14,7 @@ use crate::{
}, },
inbound::http::{ inbound::http::{
AppState, AppState,
handlers::extractors::SessionIdHeader,
responses::{ApiError, ApiSuccess}, responses::{ApiError, ApiSuccess},
}, },
}; };
@@ -94,13 +96,14 @@ impl From<ParseUploadWarrenFilesHttpRequestError> for ApiError {
pub async fn upload_warren_files<WS: WarrenService, AS: AuthService>( pub async fn upload_warren_files<WS: WarrenService, AS: AuthService>(
State(state): State<AppState<WS, AS>>, State(state): State<AppState<WS, AS>>,
SessionIdHeader(session): SessionIdHeader,
TypedMultipart(multipart): TypedMultipart<UploadWarrenFilesHttpRequestBody>, TypedMultipart(multipart): TypedMultipart<UploadWarrenFilesHttpRequestBody>,
) -> Result<ApiSuccess<()>, ApiError> { ) -> Result<ApiSuccess<()>, ApiError> {
let domain_request = multipart.try_into_domain()?; let domain_request = AuthRequest::new(session, multipart.try_into_domain()?);
state state
.warren_service .auth_service
.upload_warren_files(domain_request) .upload_warren_files(domain_request, state.warren_service.as_ref())
.await .await
.map(|_| ApiSuccess::new(StatusCode::CREATED, ())) .map(|_| ApiSuccess::new(StatusCode::CREATED, ()))
.map_err(ApiError::from) .map_err(ApiError::from)

View File

@@ -50,6 +50,7 @@ impl<T: Serialize + PartialEq> IntoResponse for ApiSuccess<T> {
pub enum ApiError { pub enum ApiError {
BadRequest(String), BadRequest(String),
NotFound(String), NotFound(String),
Unauthorized(String),
InternalServerError(String), InternalServerError(String),
} }
@@ -66,6 +67,7 @@ impl IntoResponse for ApiError {
match self { match self {
BadRequest(e) => (StatusCode::BAD_REQUEST, e).into_response(), BadRequest(e) => (StatusCode::BAD_REQUEST, e).into_response(),
NotFound(e) => (StatusCode::NOT_FOUND, e).into_response(), NotFound(e) => (StatusCode::NOT_FOUND, e).into_response(),
Unauthorized(e) => (StatusCode::UNAUTHORIZED, e).into_response(),
InternalServerError(e) => { InternalServerError(e) => {
tracing::error!("{}", e); tracing::error!("{}", e);
( (

View File

@@ -151,4 +151,67 @@ impl AuthMetrics for MetricsDebugLogger {
async fn record_auth_session_fetch_failure(&self) { async fn record_auth_session_fetch_failure(&self) {
tracing::debug!("[Metrics] Auth session fetch failed"); 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");
}
} }

View File

@@ -5,7 +5,11 @@ use crate::domain::warren::{
auth_session::requests::FetchAuthSessionResponse, auth_session::requests::FetchAuthSessionResponse,
file::{File, FilePath}, file::{File, FilePath},
user::{LoginUserResponse, User}, user::{LoginUserResponse, User},
warren::Warren, user_warren::UserWarren,
warren::{
CreateWarrenDirectoryResponse, DeleteWarrenDirectoryResponse, DeleteWarrenFileResponse,
ListWarrenFilesResponse, RenameWarrenEntryResponse, UploadWarrenFilesResponse, Warren,
},
}, },
ports::{AuthNotifier, FileSystemNotifier, WarrenNotifier}, ports::{AuthNotifier, FileSystemNotifier, WarrenNotifier},
}; };
@@ -28,27 +32,27 @@ impl WarrenNotifier for NotifierDebugLogger {
tracing::debug!("[Notifier] Fetched warren {}", warren.name()); tracing::debug!("[Notifier] Fetched warren {}", warren.name());
} }
async fn warren_files_listed(&self, warren: &Warren, files: &Vec<File>) { async fn warren_files_listed(&self, response: &ListWarrenFilesResponse) {
tracing::debug!( tracing::debug!(
"[Notifier] Listed {} file(s) in warren {}", "[Notifier] Listed {} file(s) in warren {}",
files.len(), response.files().len(),
warren.name() response.warren().name()
); );
} }
async fn warren_directory_created(&self, warren: &Warren, path: &FilePath) { async fn warren_directory_created(&self, response: &CreateWarrenDirectoryResponse) {
tracing::debug!( tracing::debug!(
"[Notifier] Created directory {} in warren {}", "[Notifier] Created directory {} in warren {}",
path, response.path(),
warren.name() response.warren().name()
); );
} }
async fn warren_directory_deleted(&self, warren: &Warren, path: &FilePath) { async fn warren_directory_deleted(&self, response: &DeleteWarrenDirectoryResponse) {
tracing::debug!( tracing::debug!(
"[Notifier] Deleted directory {} in warren {}", "[Notifier] Deleted directory {} in warren {}",
path, response.path(),
warren.name() 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!( tracing::debug!(
"[Notifier] Uploaded {} file(s) to warren {}", "[Notifier] Uploaded {} file(s) to warren {}",
paths.len(), response.paths().len(),
warren.name() response.warren().name()
); );
} }
async fn warren_file_deleted(&self, warren: &Warren, path: &FilePath) { async fn warren_file_deleted(&self, response: &DeleteWarrenFileResponse) {
tracing::debug!( tracing::debug!(
"[Notifier] Deleted file {} from warren {}", "[Notifier] Deleted file {} from warren {}",
path, response.path(),
warren.name(), response.warren().name(),
); );
} }
async fn warren_entry_renamed( async fn warren_entry_renamed(&self, response: &RenameWarrenEntryResponse) {
&self,
warren: &Warren,
old_path: &crate::domain::warren::models::file::AbsoluteFilePath,
new_path: &FilePath,
) {
tracing::debug!( tracing::debug!(
"[Notifier] Renamed file {} to {} in warren {}", "[Notifier] Renamed file {} to {} in warren {}",
old_path, response.old_path(),
new_path, response.path(),
warren.name(), response.warren().name(),
); );
} }
} }
@@ -136,4 +135,91 @@ impl AuthNotifier for NotifierDebugLogger {
response.user().id() response.user().id()
); );
} }
async fn auth_user_warrens_fetched(&self, user_id: &Uuid, warren_ids: &Vec<UserWarren>) {
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<Warren>) {
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(),
);
}
} }

View File

@@ -28,8 +28,15 @@ use crate::domain::warren::{
RegisterUserError, RegisterUserRequest, User, UserEmail, UserName, UserPassword, RegisterUserError, RegisterUserRequest, User, UserEmail, UserName, UserPassword,
VerifyUserPasswordError, VerifyUserPasswordRequest, VerifyUserPasswordError, VerifyUserPasswordRequest,
}, },
user_warren::{
UserWarren,
requests::{
FetchUserWarrenError, FetchUserWarrenRequest, FetchUserWarrensError,
FetchUserWarrensRequest,
},
},
warren::{ warren::{
FetchWarrenError, FetchWarrenRequest, ListWarrensError, ListWarrensRequest, Warren, FetchWarrenError, FetchWarrenRequest, FetchWarrensError, FetchWarrensRequest, Warren,
}, },
}, },
ports::{AuthRepository, WarrenRepository}, ports::{AuthRepository, WarrenRepository},
@@ -103,6 +110,7 @@ impl Postgres {
async fn list_warrens( async fn list_warrens(
&self, &self,
connection: &mut PgConnection, connection: &mut PgConnection,
ids: &[Uuid],
) -> Result<Vec<Warren>, sqlx::Error> { ) -> Result<Vec<Warren>, sqlx::Error> {
let warrens: Vec<Warren> = sqlx::query_as::<sqlx::Postgres, Warren>( let warrens: Vec<Warren> = sqlx::query_as::<sqlx::Postgres, Warren>(
" "
@@ -110,10 +118,11 @@ impl Postgres {
* *
FROM FROM
warrens warrens
LIMIT WHERE
50 id = ANY($1)
", ",
) )
.bind(ids)
.fetch_all(&mut *connection) .fetch_all(&mut *connection)
.await?; .await?;
@@ -292,13 +301,60 @@ impl Postgres {
Ok(session) Ok(session)
} }
async fn get_user_warrens(
&self,
connection: &mut PgConnection,
user_id: &Uuid,
) -> Result<Vec<UserWarren>, sqlx::Error> {
let ids: Vec<UserWarren> = 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<UserWarren, sqlx::Error> {
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 { impl WarrenRepository for Postgres {
async fn list_warrens( async fn list_warrens(
&self, &self,
_request: ListWarrensRequest, request: FetchWarrensRequest,
) -> Result<Vec<Warren>, ListWarrensError> { ) -> Result<Vec<Warren>, FetchWarrensError> {
let mut connection = self let mut connection = self
.pool .pool
.acquire() .acquire()
@@ -306,7 +362,7 @@ impl WarrenRepository for Postgres {
.context("Failed to get a PostgreSQL connection")?; .context("Failed to get a PostgreSQL connection")?;
let warrens = self let warrens = self
.list_warrens(&mut connection) .list_warrens(&mut connection, request.ids())
.await .await
.map_err(|err| anyhow!(err).context("Failed to list warrens"))?; .map_err(|err| anyhow!(err).context("Failed to list warrens"))?;
@@ -424,6 +480,45 @@ impl AuthRepository for Postgres {
Ok(FetchAuthSessionResponse::new(session, user)) Ok(FetchAuthSessionResponse::new(session, user))
} }
async fn fetch_user_warrens(
&self,
request: FetchUserWarrensRequest,
) -> Result<Vec<UserWarren>, 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<UserWarren, FetchUserWarrenError> {
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 { fn is_not_found_error(err: &sqlx::Error) -> bool {

View File

@@ -11,5 +11,7 @@ export function getApiHeaders(
} }
} }
headers['content-type'] = 'application/json';
return headers; return headers;
} }

View File

@@ -34,9 +34,7 @@ export async function getWarrenDirectory(
ApiResponse<{ files: DirectoryEntry[] }> ApiResponse<{ files: DirectoryEntry[] }>
>(getApiUrl(`warrens/files`), { >(getApiUrl(`warrens/files`), {
method: 'POST', method: 'POST',
headers: { headers: getApiHeaders(),
'content-type': 'application/json',
},
body: JSON.stringify({ body: JSON.stringify({
warrenId, warrenId,
path, path,
@@ -65,9 +63,7 @@ export async function createDirectory(
const { status } = await useFetch(getApiUrl(`warrens/files/directory`), { const { status } = await useFetch(getApiUrl(`warrens/files/directory`), {
method: 'POST', method: 'POST',
headers: { headers: getApiHeaders(),
'content-type': 'application/json',
},
body: JSON.stringify({ body: JSON.stringify({
warrenId, warrenId,
path, path,
@@ -106,9 +102,7 @@ export async function deleteWarrenDirectory(
const { status } = await useFetch(getApiUrl(`warrens/files/directory`), { const { status } = await useFetch(getApiUrl(`warrens/files/directory`), {
method: 'DELETE', method: 'DELETE',
headers: { headers: getApiHeaders(),
'content-type': 'application/json',
},
body: JSON.stringify({ body: JSON.stringify({
warrenId, warrenId,
path, path,
@@ -148,9 +142,7 @@ export async function deleteWarrenFile(
const { status } = await useFetch(getApiUrl(`warrens/files/file`), { const { status } = await useFetch(getApiUrl(`warrens/files/file`), {
method: 'DELETE', method: 'DELETE',
headers: { headers: getApiHeaders(),
'content-type': 'application/json',
},
body: JSON.stringify({ body: JSON.stringify({
warrenId, warrenId,
path, path,
@@ -209,6 +201,11 @@ export async function uploadToWarren(
body.append('files', file); body.append('files', file);
} }
const headers = getApiHeaders();
for (const [key, value] of Object.entries(headers)) {
xhr.setRequestHeader(key, value);
}
xhr.send(body); xhr.send(body);
try { try {
@@ -244,9 +241,7 @@ export async function renameWarrenEntry(
const { status } = await useFetch(getApiUrl(`warrens/files/rename`), { const { status } = await useFetch(getApiUrl(`warrens/files/rename`), {
method: 'PATCH', method: 'PATCH',
headers: { headers: getApiHeaders(),
'content-type': 'application/json',
},
body: JSON.stringify({ body: JSON.stringify({
warrenId, warrenId,
path, path,

View File

@@ -21,7 +21,7 @@ export const useWarrenStore = defineStore('warrens', {
} }
if (!this.current.path.endsWith('/')) { if (!this.current.path.endsWith('/')) {
this.current.path += '/'; path = '/' + path;
} }
this.current.path += path; this.current.path += path;