basic file sharing
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
CREATE TABLE shares (
|
||||
id UUID PRIMARY KEY DEFAULT GEN_RANDOM_UUID(),
|
||||
creator_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
warren_id UUID NOT NULL REFERENCES warrens(id) ON DELETE CASCADE,
|
||||
path VARCHAR NOT NULL,
|
||||
password_hash VARCHAR NOT NULL,
|
||||
expires_at TIMESTAMP,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
21
backend/migrations/20250825115342_share_permissions.sql
Normal file
21
backend/migrations/20250825115342_share_permissions.sql
Normal file
@@ -0,0 +1,21 @@
|
||||
ALTER TABLE
|
||||
user_warrens
|
||||
ADD COLUMN
|
||||
can_list_shares BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN
|
||||
can_create_shares BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN
|
||||
can_modify_shares BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN
|
||||
can_delete_shares BOOLEAN NOT NULL DEFAULT false;
|
||||
|
||||
ALTER TABLE
|
||||
user_warrens
|
||||
ALTER COLUMN
|
||||
can_list_shares DROP DEFAULT,
|
||||
ALTER COLUMN
|
||||
can_create_shares DROP DEFAULT,
|
||||
ALTER COLUMN
|
||||
can_modify_shares DROP DEFAULT,
|
||||
ALTER COLUMN
|
||||
can_delete_shares DROP DEFAULT;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE shares ALTER COLUMN password_hash DROP NOT NULL;
|
||||
1
backend/migrations/20250825150026_shares_path_index.sql
Normal file
1
backend/migrations/20250825150026_shares_path_index.sql
Normal file
@@ -0,0 +1 @@
|
||||
CREATE INDEX idx_shares_path ON shares(path);
|
||||
@@ -69,6 +69,10 @@ impl FetchAuthSessionResponse {
|
||||
pub fn user(&self) -> &User {
|
||||
&self.user
|
||||
}
|
||||
|
||||
pub fn unpack(self) -> (AuthSession, User) {
|
||||
(self.session, self.user)
|
||||
}
|
||||
}
|
||||
|
||||
impl FetchAuthSessionRequest {
|
||||
|
||||
@@ -142,6 +142,10 @@ impl FilePath {
|
||||
self.0.push('/');
|
||||
self.0.push_str(&other.0);
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<Path> for FilePath {
|
||||
@@ -172,6 +176,10 @@ impl RelativeFilePath {
|
||||
self.0.push_str(&other.0);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl AbsoluteFilePath {
|
||||
@@ -199,6 +207,10 @@ impl AbsoluteFilePath {
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Error)]
|
||||
|
||||
@@ -5,6 +5,7 @@ mod mkdir;
|
||||
mod mv;
|
||||
mod rm;
|
||||
mod save;
|
||||
mod stat;
|
||||
mod touch;
|
||||
|
||||
pub use cat::*;
|
||||
@@ -14,4 +15,5 @@ pub use mkdir::*;
|
||||
pub use mv::*;
|
||||
pub use rm::*;
|
||||
pub use save::*;
|
||||
pub use stat::*;
|
||||
pub use touch::*;
|
||||
|
||||
51
backend/src/lib/domain/warren/models/file/requests/stat.rs
Normal file
51
backend/src/lib/domain/warren/models/file/requests/stat.rs
Normal file
@@ -0,0 +1,51 @@
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::domain::warren::models::file::{AbsoluteFilePath, File};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct StatRequest {
|
||||
path: AbsoluteFilePath,
|
||||
}
|
||||
|
||||
impl StatRequest {
|
||||
pub fn new(path: AbsoluteFilePath) -> Self {
|
||||
Self { path }
|
||||
}
|
||||
|
||||
pub fn path(&self) -> &AbsoluteFilePath {
|
||||
&self.path
|
||||
}
|
||||
|
||||
pub fn into_path(self) -> AbsoluteFilePath {
|
||||
self.path
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum StatError {
|
||||
#[error("The file does not exist")]
|
||||
NotFound,
|
||||
#[error(transparent)]
|
||||
Unknown(#[from] anyhow::Error),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct StatResponse {
|
||||
file: File,
|
||||
}
|
||||
|
||||
impl StatResponse {
|
||||
pub fn new(file: File) -> Self {
|
||||
Self { file }
|
||||
}
|
||||
|
||||
pub fn file(&self) -> &File {
|
||||
&self.file
|
||||
}
|
||||
}
|
||||
|
||||
impl From<StatResponse> for File {
|
||||
fn from(value: StatResponse) -> Self {
|
||||
value.file
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
pub mod auth_session;
|
||||
pub mod file;
|
||||
pub mod share;
|
||||
pub mod user;
|
||||
pub mod user_warren;
|
||||
pub mod warren;
|
||||
|
||||
134
backend/src/lib/domain/warren/models/share/mod.rs
Normal file
134
backend/src/lib/domain/warren/models/share/mod.rs
Normal file
@@ -0,0 +1,134 @@
|
||||
mod requests;
|
||||
pub use requests::*;
|
||||
|
||||
use thiserror::Error;
|
||||
use uuid::Uuid;
|
||||
|
||||
use super::{
|
||||
file::{AbsoluteFilePath, StatRequest},
|
||||
warren::Warren,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct Share {
|
||||
id: Uuid,
|
||||
creator_id: Uuid,
|
||||
|
||||
warren_id: Uuid,
|
||||
path: AbsoluteFilePath,
|
||||
|
||||
password_hash: Option<String>,
|
||||
|
||||
expires_at: Option<u64>,
|
||||
|
||||
created_at: u64,
|
||||
}
|
||||
|
||||
impl Share {
|
||||
pub fn new(
|
||||
id: Uuid,
|
||||
creator_id: Uuid,
|
||||
warren_id: Uuid,
|
||||
path: AbsoluteFilePath,
|
||||
password_hash: Option<String>,
|
||||
expires_at: Option<u64>,
|
||||
created_at: u64,
|
||||
) -> Self {
|
||||
Self {
|
||||
id,
|
||||
creator_id,
|
||||
warren_id,
|
||||
path,
|
||||
password_hash,
|
||||
expires_at,
|
||||
created_at,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn id(&self) -> &Uuid {
|
||||
&self.id
|
||||
}
|
||||
|
||||
pub fn creator_id(&self) -> &Uuid {
|
||||
&self.creator_id
|
||||
}
|
||||
|
||||
pub fn warren_id(&self) -> &Uuid {
|
||||
&self.warren_id
|
||||
}
|
||||
|
||||
pub fn path(&self) -> &AbsoluteFilePath {
|
||||
&self.path
|
||||
}
|
||||
|
||||
pub fn password_hash(&self) -> Option<&String> {
|
||||
self.password_hash.as_ref()
|
||||
}
|
||||
|
||||
pub fn expires_at(&self) -> Option<u64> {
|
||||
self.expires_at
|
||||
}
|
||||
|
||||
pub fn created_at(&self) -> u64 {
|
||||
self.created_at
|
||||
}
|
||||
|
||||
pub fn build_fs_stat_request(&self, warren: &Warren) -> StatRequest {
|
||||
let base_path = self.path.clone();
|
||||
|
||||
let path = warren.path().clone().join(&base_path.to_relative());
|
||||
|
||||
StatRequest::new(path)
|
||||
}
|
||||
}
|
||||
|
||||
/// A valid share password
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct SharePassword(String);
|
||||
|
||||
#[derive(Clone, Debug, Error)]
|
||||
pub enum SharePasswordError {
|
||||
#[error("A share's password must not be empty")]
|
||||
Empty,
|
||||
#[error("A share's password must not start with a whitespace")]
|
||||
LeadingWhitespace,
|
||||
#[error("A share's password must not end with a whitespace")]
|
||||
TrailingWhitespace,
|
||||
#[error("A share's password must be longer")]
|
||||
TooShort,
|
||||
#[error("A share's password must be shorter")]
|
||||
TooLong,
|
||||
}
|
||||
|
||||
impl SharePassword {
|
||||
const MIN_LENGTH: usize = 1;
|
||||
const MAX_LENGTH: usize = 64;
|
||||
|
||||
pub fn new(raw: &str) -> Result<Self, SharePasswordError> {
|
||||
if raw.is_empty() {
|
||||
return Err(SharePasswordError::Empty);
|
||||
}
|
||||
|
||||
if raw.trim_start().len() != raw.len() {
|
||||
return Err(SharePasswordError::LeadingWhitespace);
|
||||
}
|
||||
|
||||
if raw.trim_end().len() != raw.len() {
|
||||
return Err(SharePasswordError::TrailingWhitespace);
|
||||
}
|
||||
|
||||
if raw.len() < Self::MIN_LENGTH {
|
||||
return Err(SharePasswordError::TooShort);
|
||||
}
|
||||
|
||||
if raw.len() > Self::MAX_LENGTH {
|
||||
return Err(SharePasswordError::TooLong);
|
||||
}
|
||||
|
||||
Ok(Self(raw.to_string()))
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
104
backend/src/lib/domain/warren/models/share/requests/cat.rs
Normal file
104
backend/src/lib/domain/warren/models/share/requests/cat.rs
Normal file
@@ -0,0 +1,104 @@
|
||||
use thiserror::Error;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::domain::warren::models::{
|
||||
file::{AbsoluteFilePath, CatRequest, FileStream},
|
||||
share::{Share, SharePassword},
|
||||
warren::{FetchWarrenError, Warren, WarrenCatError, WarrenCatRequest},
|
||||
};
|
||||
|
||||
use super::{VerifySharePasswordError, VerifySharePasswordRequest};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct ShareCatRequest {
|
||||
share_id: Uuid,
|
||||
path: AbsoluteFilePath,
|
||||
password: Option<SharePassword>,
|
||||
}
|
||||
|
||||
impl ShareCatRequest {
|
||||
pub fn new(share_id: Uuid, path: AbsoluteFilePath, password: Option<SharePassword>) -> Self {
|
||||
Self {
|
||||
share_id,
|
||||
path,
|
||||
password,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn share_id(&self) -> &Uuid {
|
||||
&self.share_id
|
||||
}
|
||||
|
||||
pub fn path(&self) -> &AbsoluteFilePath {
|
||||
&self.path
|
||||
}
|
||||
|
||||
pub fn password(&self) -> Option<&SharePassword> {
|
||||
self.password.as_ref()
|
||||
}
|
||||
|
||||
pub fn build_warren_cat_request(self, share: &Share, warren: &Warren) -> WarrenCatRequest {
|
||||
let path = share.path().clone().join(&self.path.to_relative());
|
||||
|
||||
WarrenCatRequest::new(*warren.id(), CatRequest::new(path))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ShareCatRequest> for VerifySharePasswordRequest {
|
||||
fn from(value: &ShareCatRequest) -> Self {
|
||||
Self::new(value.share_id, value.password.clone())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ShareCatResponse {
|
||||
share: Share,
|
||||
warren: Warren,
|
||||
path: AbsoluteFilePath,
|
||||
stream: FileStream,
|
||||
}
|
||||
|
||||
impl ShareCatResponse {
|
||||
pub fn new(share: Share, warren: Warren, path: AbsoluteFilePath, stream: FileStream) -> Self {
|
||||
Self {
|
||||
share,
|
||||
warren,
|
||||
path,
|
||||
stream,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn share(&self) -> &Share {
|
||||
&self.share
|
||||
}
|
||||
|
||||
pub fn warren(&self) -> &Warren {
|
||||
&self.warren
|
||||
}
|
||||
|
||||
pub fn path(&self) -> &AbsoluteFilePath {
|
||||
&self.path
|
||||
}
|
||||
|
||||
pub fn stream(&self) -> &FileStream {
|
||||
&self.stream
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ShareCatResponse> for FileStream {
|
||||
fn from(value: ShareCatResponse) -> Self {
|
||||
value.stream
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ShareCatError {
|
||||
#[error(transparent)]
|
||||
VerifySharePassword(#[from] VerifySharePasswordError),
|
||||
#[error(transparent)]
|
||||
FetchWarren(#[from] FetchWarrenError),
|
||||
#[error(transparent)]
|
||||
WarrenCat(#[from] WarrenCatError),
|
||||
#[error(transparent)]
|
||||
Unknown(#[from] anyhow::Error),
|
||||
}
|
||||
121
backend/src/lib/domain/warren/models/share/requests/create.rs
Normal file
121
backend/src/lib/domain/warren/models/share/requests/create.rs
Normal file
@@ -0,0 +1,121 @@
|
||||
use thiserror::Error;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::domain::warren::models::{
|
||||
file::{AbsoluteFilePath, StatError, StatRequest},
|
||||
share::{Share, SharePassword},
|
||||
warren::{FetchWarrenError, FetchWarrenRequest, HasWarrenId, Warren},
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct CreateShareRequest {
|
||||
creator_id: Uuid,
|
||||
base: CreateShareBaseRequest,
|
||||
}
|
||||
|
||||
impl CreateShareRequest {
|
||||
pub fn new(creator_id: Uuid, base: CreateShareBaseRequest) -> Self {
|
||||
Self { creator_id, base }
|
||||
}
|
||||
|
||||
pub fn creator_id(&self) -> &Uuid {
|
||||
&self.creator_id
|
||||
}
|
||||
|
||||
pub fn base(&self) -> &CreateShareBaseRequest {
|
||||
&self.base
|
||||
}
|
||||
|
||||
pub fn build_fs_stat_request(&self, warren: &Warren) -> StatRequest {
|
||||
let base_path = self.base.path().clone();
|
||||
|
||||
let path = warren.path().clone().join(&base_path.to_relative());
|
||||
|
||||
StatRequest::new(path)
|
||||
}
|
||||
}
|
||||
|
||||
impl HasWarrenId for CreateShareRequest {
|
||||
fn warren_id(&self) -> &Uuid {
|
||||
&self.base.warren_id()
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<FetchWarrenRequest> for &CreateShareRequest {
|
||||
fn into(self) -> FetchWarrenRequest {
|
||||
FetchWarrenRequest::new(self.base.warren_id)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct CreateShareBaseRequest {
|
||||
warren_id: Uuid,
|
||||
path: AbsoluteFilePath,
|
||||
|
||||
password: Option<SharePassword>,
|
||||
|
||||
lifetime: Option<u64>,
|
||||
}
|
||||
|
||||
impl CreateShareBaseRequest {
|
||||
pub fn new(
|
||||
warren_id: Uuid,
|
||||
path: AbsoluteFilePath,
|
||||
password: Option<SharePassword>,
|
||||
lifetime: Option<u64>,
|
||||
) -> Self {
|
||||
Self {
|
||||
warren_id,
|
||||
path,
|
||||
password,
|
||||
lifetime,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_share_request(self, creator_id: Uuid) -> CreateShareRequest {
|
||||
CreateShareRequest::new(creator_id, self)
|
||||
}
|
||||
|
||||
pub fn path(&self) -> &AbsoluteFilePath {
|
||||
&self.path
|
||||
}
|
||||
|
||||
pub fn password(&self) -> Option<&SharePassword> {
|
||||
self.password.as_ref()
|
||||
}
|
||||
|
||||
pub fn lifetime(&self) -> Option<u64> {
|
||||
self.lifetime
|
||||
}
|
||||
}
|
||||
|
||||
impl HasWarrenId for CreateShareBaseRequest {
|
||||
fn warren_id(&self) -> &Uuid {
|
||||
&self.warren_id
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct CreateShareResponse {
|
||||
share: Share,
|
||||
}
|
||||
|
||||
impl CreateShareResponse {
|
||||
pub fn new(share: Share) -> Self {
|
||||
Self { share }
|
||||
}
|
||||
|
||||
pub fn share(&self) -> &Share {
|
||||
&self.share
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum CreateShareError {
|
||||
#[error(transparent)]
|
||||
Stat(#[from] StatError),
|
||||
#[error(transparent)]
|
||||
FetchWarren(#[from] FetchWarrenError),
|
||||
#[error(transparent)]
|
||||
Unknown(#[from] anyhow::Error),
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
use thiserror::Error;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::domain::warren::models::{
|
||||
share::Share,
|
||||
warren::{FetchWarrenError, HasWarrenId},
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct DeleteShareRequest {
|
||||
warren_id: Uuid,
|
||||
share_id: Uuid,
|
||||
}
|
||||
|
||||
impl DeleteShareRequest {
|
||||
pub fn new(warren_id: Uuid, share_id: Uuid) -> Self {
|
||||
Self {
|
||||
warren_id,
|
||||
share_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn share_id(&self) -> &Uuid {
|
||||
&self.share_id
|
||||
}
|
||||
}
|
||||
|
||||
impl HasWarrenId for DeleteShareRequest {
|
||||
fn warren_id(&self) -> &Uuid {
|
||||
&self.warren_id
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct DeleteShareResponse {
|
||||
share: Share,
|
||||
}
|
||||
|
||||
impl DeleteShareResponse {
|
||||
pub fn new(share: Share) -> Self {
|
||||
Self { share }
|
||||
}
|
||||
|
||||
pub fn share(&self) -> &Share {
|
||||
&self.share
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DeleteShareResponse> for Share {
|
||||
fn from(value: DeleteShareResponse) -> Self {
|
||||
value.share
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum DeleteShareError {
|
||||
#[error(transparent)]
|
||||
FetchWarren(#[from] FetchWarrenError),
|
||||
#[error(transparent)]
|
||||
Unknown(#[from] anyhow::Error),
|
||||
}
|
||||
59
backend/src/lib/domain/warren/models/share/requests/get.rs
Normal file
59
backend/src/lib/domain/warren/models/share/requests/get.rs
Normal file
@@ -0,0 +1,59 @@
|
||||
use thiserror::Error;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::domain::warren::models::{
|
||||
file::{File, StatError},
|
||||
share::Share,
|
||||
warren::FetchWarrenError,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct GetShareRequest {
|
||||
share_id: Uuid,
|
||||
}
|
||||
|
||||
impl GetShareRequest {
|
||||
pub fn new(share_id: Uuid) -> Self {
|
||||
Self { share_id }
|
||||
}
|
||||
|
||||
pub fn share_id(&self) -> &Uuid {
|
||||
&self.share_id
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct GetShareResponse {
|
||||
share: Share,
|
||||
file: File,
|
||||
}
|
||||
|
||||
impl GetShareResponse {
|
||||
pub fn new(share: Share, file: File) -> Self {
|
||||
Self { share, file }
|
||||
}
|
||||
|
||||
pub fn share(&self) -> &Share {
|
||||
&self.share
|
||||
}
|
||||
|
||||
pub fn file(&self) -> &File {
|
||||
&self.file
|
||||
}
|
||||
}
|
||||
|
||||
impl From<GetShareResponse> for Share {
|
||||
fn from(value: GetShareResponse) -> Self {
|
||||
value.share
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum GetShareError {
|
||||
#[error(transparent)]
|
||||
FsStat(#[from] StatError),
|
||||
#[error(transparent)]
|
||||
FetchWarren(#[from] FetchWarrenError),
|
||||
#[error(transparent)]
|
||||
Unknown(#[from] anyhow::Error),
|
||||
}
|
||||
75
backend/src/lib/domain/warren/models/share/requests/list.rs
Normal file
75
backend/src/lib/domain/warren/models/share/requests/list.rs
Normal file
@@ -0,0 +1,75 @@
|
||||
use thiserror::Error;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::domain::warren::models::{
|
||||
file::{AbsoluteFilePath, StatError},
|
||||
share::Share,
|
||||
warren::{FetchWarrenError, FetchWarrenRequest, HasWarrenId, Warren},
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct ListSharesRequest {
|
||||
warren_id: Uuid,
|
||||
path: AbsoluteFilePath,
|
||||
}
|
||||
|
||||
impl ListSharesRequest {
|
||||
pub fn new(warren_id: Uuid, path: AbsoluteFilePath) -> Self {
|
||||
Self { warren_id, path }
|
||||
}
|
||||
|
||||
pub fn path(&self) -> &AbsoluteFilePath {
|
||||
&self.path
|
||||
}
|
||||
}
|
||||
|
||||
impl HasWarrenId for ListSharesRequest {
|
||||
fn warren_id(&self) -> &Uuid {
|
||||
&self.warren_id
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<FetchWarrenRequest> for &ListSharesRequest {
|
||||
fn into(self) -> FetchWarrenRequest {
|
||||
FetchWarrenRequest::new(self.warren_id)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct ListSharesResponse {
|
||||
warren: Warren,
|
||||
path: AbsoluteFilePath,
|
||||
shares: Vec<Share>,
|
||||
}
|
||||
|
||||
impl ListSharesResponse {
|
||||
pub fn new(warren: Warren, path: AbsoluteFilePath, shares: Vec<Share>) -> Self {
|
||||
Self {
|
||||
warren,
|
||||
path,
|
||||
shares,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn warren(&self) -> &Warren {
|
||||
&self.warren
|
||||
}
|
||||
|
||||
pub fn path(&self) -> &AbsoluteFilePath {
|
||||
&self.path
|
||||
}
|
||||
|
||||
pub fn shares(&self) -> &Vec<Share> {
|
||||
&self.shares
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ListSharesError {
|
||||
#[error(transparent)]
|
||||
Stat(#[from] StatError),
|
||||
#[error(transparent)]
|
||||
FetchWarren(#[from] FetchWarrenError),
|
||||
#[error(transparent)]
|
||||
Unknown(#[from] anyhow::Error),
|
||||
}
|
||||
100
backend/src/lib/domain/warren/models/share/requests/ls.rs
Normal file
100
backend/src/lib/domain/warren/models/share/requests/ls.rs
Normal file
@@ -0,0 +1,100 @@
|
||||
use thiserror::Error;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::domain::warren::models::{
|
||||
file::{AbsoluteFilePath, LsRequest, LsResponse},
|
||||
share::{Share, SharePassword},
|
||||
warren::{FetchWarrenError, Warren, WarrenLsError, WarrenLsRequest},
|
||||
};
|
||||
|
||||
use super::{VerifySharePasswordError, VerifySharePasswordRequest};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct ShareLsRequest {
|
||||
share_id: Uuid,
|
||||
path: AbsoluteFilePath,
|
||||
password: Option<SharePassword>,
|
||||
}
|
||||
|
||||
impl ShareLsRequest {
|
||||
pub fn new(share_id: Uuid, path: AbsoluteFilePath, password: Option<SharePassword>) -> Self {
|
||||
Self {
|
||||
share_id,
|
||||
path,
|
||||
password,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn share_id(&self) -> &Uuid {
|
||||
&self.share_id
|
||||
}
|
||||
|
||||
pub fn path(&self) -> &AbsoluteFilePath {
|
||||
&self.path
|
||||
}
|
||||
|
||||
pub fn password(&self) -> Option<&SharePassword> {
|
||||
self.password.as_ref()
|
||||
}
|
||||
|
||||
pub fn build_warren_ls_request(self, share: &Share, warren: &Warren) -> WarrenLsRequest {
|
||||
let include_parent = self.path.as_str() != "/";
|
||||
|
||||
let path = share.path().clone().join(&self.path.to_relative());
|
||||
|
||||
WarrenLsRequest::new(*warren.id(), LsRequest::new(path, include_parent))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ShareLsRequest> for VerifySharePasswordRequest {
|
||||
fn from(value: &ShareLsRequest) -> Self {
|
||||
Self::new(value.share_id, value.password.clone())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct ShareLsResponse {
|
||||
share: Share,
|
||||
warren: Warren,
|
||||
path: AbsoluteFilePath,
|
||||
base: LsResponse,
|
||||
}
|
||||
|
||||
impl ShareLsResponse {
|
||||
pub fn new(share: Share, warren: Warren, path: AbsoluteFilePath, base: LsResponse) -> Self {
|
||||
Self {
|
||||
share,
|
||||
warren,
|
||||
path,
|
||||
base,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn share(&self) -> &Share {
|
||||
&self.share
|
||||
}
|
||||
|
||||
pub fn warren(&self) -> &Warren {
|
||||
&self.warren
|
||||
}
|
||||
|
||||
pub fn path(&self) -> &AbsoluteFilePath {
|
||||
&self.path
|
||||
}
|
||||
|
||||
pub fn base(&self) -> &LsResponse {
|
||||
&self.base
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ShareLsError {
|
||||
#[error(transparent)]
|
||||
VerifySharePassword(#[from] VerifySharePasswordError),
|
||||
#[error(transparent)]
|
||||
WarrenLs(#[from] WarrenLsError),
|
||||
#[error(transparent)]
|
||||
FetchWarren(#[from] FetchWarrenError),
|
||||
#[error(transparent)]
|
||||
Unknown(#[from] anyhow::Error),
|
||||
}
|
||||
15
backend/src/lib/domain/warren/models/share/requests/mod.rs
Normal file
15
backend/src/lib/domain/warren/models/share/requests/mod.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
mod cat;
|
||||
mod create;
|
||||
mod delete;
|
||||
mod get;
|
||||
mod list;
|
||||
mod ls;
|
||||
mod verify_password;
|
||||
|
||||
pub use cat::*;
|
||||
pub use create::*;
|
||||
pub use delete::*;
|
||||
pub use get::*;
|
||||
pub use list::*;
|
||||
pub use ls::*;
|
||||
pub use verify_password::*;
|
||||
@@ -0,0 +1,55 @@
|
||||
use thiserror::Error;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::domain::warren::models::share::{Share, SharePassword};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct VerifySharePasswordRequest {
|
||||
share_id: Uuid,
|
||||
password: Option<SharePassword>,
|
||||
}
|
||||
|
||||
impl VerifySharePasswordRequest {
|
||||
pub fn new(share_id: Uuid, password: Option<SharePassword>) -> Self {
|
||||
Self { share_id, password }
|
||||
}
|
||||
|
||||
pub fn share_id(&self) -> &Uuid {
|
||||
&self.share_id
|
||||
}
|
||||
|
||||
pub fn password(&self) -> Option<&SharePassword> {
|
||||
self.password.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct VerifySharePasswordResponse {
|
||||
share: Share,
|
||||
}
|
||||
|
||||
impl VerifySharePasswordResponse {
|
||||
pub fn new(share: Share) -> Self {
|
||||
Self { share }
|
||||
}
|
||||
|
||||
pub fn share(&self) -> &Share {
|
||||
&self.share
|
||||
}
|
||||
}
|
||||
|
||||
impl From<VerifySharePasswordResponse> for Share {
|
||||
fn from(value: VerifySharePasswordResponse) -> Self {
|
||||
value.share
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum VerifySharePasswordError {
|
||||
#[error("The specified share does not exist")]
|
||||
NotFound,
|
||||
#[error("The specified password is not correct")]
|
||||
IncorrectPassword,
|
||||
#[error(transparent)]
|
||||
Unknown(#[from] anyhow::Error),
|
||||
}
|
||||
@@ -10,6 +10,10 @@ pub struct UserWarren {
|
||||
can_read_files: bool,
|
||||
can_modify_files: bool,
|
||||
can_delete_files: bool,
|
||||
can_list_shares: bool,
|
||||
can_create_shares: bool,
|
||||
can_modify_shares: bool,
|
||||
can_delete_shares: bool,
|
||||
}
|
||||
|
||||
impl UserWarren {
|
||||
@@ -20,6 +24,10 @@ impl UserWarren {
|
||||
can_read_files: bool,
|
||||
can_modify_files: bool,
|
||||
can_delete_files: bool,
|
||||
can_list_shares: bool,
|
||||
can_create_shares: bool,
|
||||
can_modify_shares: bool,
|
||||
can_delete_shares: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
user_id,
|
||||
@@ -28,6 +36,10 @@ impl UserWarren {
|
||||
can_read_files,
|
||||
can_modify_files,
|
||||
can_delete_files,
|
||||
can_list_shares,
|
||||
can_create_shares,
|
||||
can_modify_shares,
|
||||
can_delete_shares,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,4 +69,20 @@ impl UserWarren {
|
||||
pub fn can_delete_files(&self) -> bool {
|
||||
self.can_delete_files
|
||||
}
|
||||
|
||||
pub fn can_list_shares(&self) -> bool {
|
||||
self.can_list_shares
|
||||
}
|
||||
|
||||
pub fn can_create_shares(&self) -> bool {
|
||||
self.can_create_shares
|
||||
}
|
||||
|
||||
pub fn can_modify_shares(&self) -> bool {
|
||||
self.can_modify_shares
|
||||
}
|
||||
|
||||
pub fn can_delete_shares(&self) -> bool {
|
||||
self.can_delete_shares
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,10 @@ use crate::domain::warren::models::file::{
|
||||
|
||||
use super::{Warren, WarrenName};
|
||||
|
||||
pub trait HasWarrenId {
|
||||
fn warren_id(&self) -> &Uuid;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct FetchWarrenRequest {
|
||||
id: Uuid,
|
||||
@@ -71,10 +75,6 @@ impl WarrenLsRequest {
|
||||
Self { warren_id, base }
|
||||
}
|
||||
|
||||
pub fn warren_id(&self) -> &Uuid {
|
||||
&self.warren_id
|
||||
}
|
||||
|
||||
pub fn base(&self) -> &LsRequest {
|
||||
&self.base
|
||||
}
|
||||
@@ -90,6 +90,12 @@ impl WarrenLsRequest {
|
||||
}
|
||||
}
|
||||
|
||||
impl HasWarrenId for WarrenLsRequest {
|
||||
fn warren_id(&self) -> &Uuid {
|
||||
&self.warren_id
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<FetchWarrenRequest> for WarrenLsRequest {
|
||||
fn into(self) -> FetchWarrenRequest {
|
||||
FetchWarrenRequest::new(self.warren_id)
|
||||
@@ -142,10 +148,6 @@ impl WarrenMkdirRequest {
|
||||
Self { warren_id, base }
|
||||
}
|
||||
|
||||
pub fn warren_id(&self) -> &Uuid {
|
||||
&self.warren_id
|
||||
}
|
||||
|
||||
pub fn base(&self) -> &MkdirRequest {
|
||||
&self.base
|
||||
}
|
||||
@@ -160,6 +162,12 @@ impl WarrenMkdirRequest {
|
||||
}
|
||||
}
|
||||
|
||||
impl HasWarrenId for WarrenMkdirRequest {
|
||||
fn warren_id(&self) -> &Uuid {
|
||||
&self.warren_id
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<FetchWarrenRequest> for WarrenMkdirRequest {
|
||||
fn into(self) -> FetchWarrenRequest {
|
||||
FetchWarrenRequest::new(self.warren_id)
|
||||
@@ -207,10 +215,6 @@ impl WarrenRmRequest {
|
||||
Self { warren_id, base }
|
||||
}
|
||||
|
||||
pub fn warren_id(&self) -> &Uuid {
|
||||
&self.warren_id
|
||||
}
|
||||
|
||||
pub fn base(&self) -> &RmRequest {
|
||||
&self.base
|
||||
}
|
||||
@@ -226,6 +230,12 @@ impl WarrenRmRequest {
|
||||
}
|
||||
}
|
||||
|
||||
impl HasWarrenId for WarrenRmRequest {
|
||||
fn warren_id(&self) -> &Uuid {
|
||||
&self.warren_id
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<FetchWarrenRequest> for &WarrenRmRequest {
|
||||
fn into(self) -> FetchWarrenRequest {
|
||||
FetchWarrenRequest::new(self.warren_id)
|
||||
@@ -272,10 +282,6 @@ impl<'s> WarrenSaveRequest<'s> {
|
||||
Self { warren_id, base }
|
||||
}
|
||||
|
||||
pub fn warren_id(&self) -> &Uuid {
|
||||
&self.warren_id
|
||||
}
|
||||
|
||||
pub fn base(&self) -> &SaveRequest<'s> {
|
||||
&self.base
|
||||
}
|
||||
@@ -292,6 +298,12 @@ impl<'s> WarrenSaveRequest<'s> {
|
||||
}
|
||||
}
|
||||
|
||||
impl HasWarrenId for WarrenSaveRequest<'_> {
|
||||
fn warren_id(&self) -> &Uuid {
|
||||
&self.warren_id
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<FetchWarrenRequest> for &WarrenSaveRequest<'_> {
|
||||
fn into(self) -> FetchWarrenRequest {
|
||||
FetchWarrenRequest::new(self.warren_id)
|
||||
@@ -406,10 +418,6 @@ impl WarrenMvRequest {
|
||||
Self { warren_id, base }
|
||||
}
|
||||
|
||||
pub fn warren_id(&self) -> &Uuid {
|
||||
&self.warren_id
|
||||
}
|
||||
|
||||
pub fn base(&self) -> &MvRequest {
|
||||
&self.base
|
||||
}
|
||||
@@ -423,6 +431,12 @@ impl WarrenMvRequest {
|
||||
}
|
||||
}
|
||||
|
||||
impl HasWarrenId for WarrenMvRequest {
|
||||
fn warren_id(&self) -> &Uuid {
|
||||
&self.warren_id
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<FetchWarrenRequest> for &WarrenMvRequest {
|
||||
fn into(self) -> FetchWarrenRequest {
|
||||
FetchWarrenRequest::new(self.warren_id)
|
||||
@@ -578,10 +592,6 @@ impl WarrenCatRequest {
|
||||
Self { warren_id, base }
|
||||
}
|
||||
|
||||
pub fn warren_id(&self) -> &Uuid {
|
||||
&self.warren_id
|
||||
}
|
||||
|
||||
pub fn base(&self) -> &CatRequest {
|
||||
&self.base
|
||||
}
|
||||
@@ -596,6 +606,12 @@ impl WarrenCatRequest {
|
||||
}
|
||||
}
|
||||
|
||||
impl HasWarrenId for WarrenCatRequest {
|
||||
fn warren_id(&self) -> &Uuid {
|
||||
&self.warren_id
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<FetchWarrenRequest> for &WarrenCatRequest {
|
||||
fn into(self) -> FetchWarrenRequest {
|
||||
FetchWarrenRequest::new(self.warren_id)
|
||||
@@ -623,10 +639,6 @@ impl WarrenTouchRequest {
|
||||
Self { warren_id, base }
|
||||
}
|
||||
|
||||
pub fn warren_id(&self) -> &Uuid {
|
||||
&self.warren_id
|
||||
}
|
||||
|
||||
pub fn base(&self) -> &TouchRequest {
|
||||
&self.base
|
||||
}
|
||||
@@ -641,6 +653,12 @@ impl WarrenTouchRequest {
|
||||
}
|
||||
}
|
||||
|
||||
impl HasWarrenId for WarrenTouchRequest {
|
||||
fn warren_id(&self) -> &Uuid {
|
||||
&self.warren_id
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<FetchWarrenRequest> for &WarrenTouchRequest {
|
||||
fn into(self) -> FetchWarrenRequest {
|
||||
FetchWarrenRequest::new(self.warren_id)
|
||||
@@ -688,10 +706,6 @@ impl WarrenCpRequest {
|
||||
Self { warren_id, base }
|
||||
}
|
||||
|
||||
pub fn warren_id(&self) -> &Uuid {
|
||||
&self.warren_id
|
||||
}
|
||||
|
||||
pub fn base(&self) -> &CpRequest {
|
||||
&self.base
|
||||
}
|
||||
@@ -706,6 +720,12 @@ impl WarrenCpRequest {
|
||||
}
|
||||
}
|
||||
|
||||
impl HasWarrenId for WarrenCpRequest {
|
||||
fn warren_id(&self) -> &Uuid {
|
||||
&self.warren_id
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<FetchWarrenRequest> for &WarrenCpRequest {
|
||||
fn into(self) -> FetchWarrenRequest {
|
||||
FetchWarrenRequest::new(self.warren_id)
|
||||
|
||||
@@ -42,6 +42,24 @@ pub trait WarrenMetrics: Clone + Send + Sync + 'static {
|
||||
|
||||
fn record_warren_cp_success(&self) -> impl Future<Output = ()> + Send;
|
||||
fn record_warren_cp_failure(&self) -> impl Future<Output = ()> + Send;
|
||||
|
||||
fn record_warren_get_share_success(&self) -> impl Future<Output = ()> + Send;
|
||||
fn record_warren_get_share_failure(&self) -> impl Future<Output = ()> + Send;
|
||||
|
||||
fn record_warren_share_creation_success(&self) -> impl Future<Output = ()> + Send;
|
||||
fn record_warren_share_creation_failure(&self) -> impl Future<Output = ()> + Send;
|
||||
|
||||
fn record_warren_share_list_success(&self) -> impl Future<Output = ()> + Send;
|
||||
fn record_warren_share_list_failure(&self) -> impl Future<Output = ()> + Send;
|
||||
|
||||
fn record_warren_share_deletion_success(&self) -> impl Future<Output = ()> + Send;
|
||||
fn record_warren_share_deletion_failure(&self) -> impl Future<Output = ()> + Send;
|
||||
|
||||
fn record_warren_share_ls_success(&self) -> impl Future<Output = ()> + Send;
|
||||
fn record_warren_share_ls_failure(&self) -> impl Future<Output = ()> + Send;
|
||||
|
||||
fn record_warren_share_cat_success(&self) -> impl Future<Output = ()> + Send;
|
||||
fn record_warren_share_cat_failure(&self) -> impl Future<Output = ()> + Send;
|
||||
}
|
||||
|
||||
pub trait FileSystemMetrics: Clone + Send + Sync + 'static {
|
||||
@@ -68,6 +86,9 @@ pub trait FileSystemMetrics: Clone + Send + Sync + 'static {
|
||||
|
||||
fn record_cp_success(&self) -> impl Future<Output = ()> + Send;
|
||||
fn record_cp_failure(&self) -> impl Future<Output = ()> + Send;
|
||||
|
||||
fn record_stat_success(&self) -> impl Future<Output = ()> + Send;
|
||||
fn record_stat_failure(&self) -> impl Future<Output = ()> + Send;
|
||||
}
|
||||
|
||||
pub trait AuthMetrics: Clone + Send + Sync + 'static {
|
||||
@@ -151,4 +172,13 @@ pub trait AuthMetrics: Clone + Send + Sync + 'static {
|
||||
|
||||
fn record_auth_warren_cp_success(&self) -> impl Future<Output = ()> + Send;
|
||||
fn record_auth_warren_cp_failure(&self) -> impl Future<Output = ()> + Send;
|
||||
|
||||
fn record_auth_share_creation_success(&self) -> impl Future<Output = ()> + Send;
|
||||
fn record_auth_share_creation_failure(&self) -> impl Future<Output = ()> + Send;
|
||||
|
||||
fn record_auth_share_list_success(&self) -> impl Future<Output = ()> + Send;
|
||||
fn record_auth_share_list_failure(&self) -> impl Future<Output = ()> + Send;
|
||||
|
||||
fn record_auth_share_deletion_success(&self) -> impl Future<Output = ()> + Send;
|
||||
fn record_auth_share_deletion_failure(&self) -> impl Future<Output = ()> + Send;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,13 @@ use super::models::{
|
||||
file::{
|
||||
CatError, CatRequest, CpError, CpRequest, CpResponse, FileStream, LsError, LsRequest,
|
||||
LsResponse, MkdirError, MkdirRequest, MvError, MvRequest, RmError, RmRequest, SaveError,
|
||||
SaveRequest, SaveResponse, TouchError, TouchRequest,
|
||||
SaveRequest, SaveResponse, StatError, StatRequest, StatResponse, TouchError, TouchRequest,
|
||||
},
|
||||
share::{
|
||||
CreateShareBaseRequest, CreateShareError, CreateShareRequest, CreateShareResponse,
|
||||
DeleteShareError, DeleteShareRequest, DeleteShareResponse, GetShareError, GetShareRequest,
|
||||
GetShareResponse, ListSharesError, ListSharesRequest, ListSharesResponse, ShareCatError,
|
||||
ShareCatRequest, ShareCatResponse, ShareLsError, ShareLsRequest, ShareLsResponse,
|
||||
},
|
||||
user::{
|
||||
CreateUserError, CreateUserRequest, DeleteUserError, DeleteUserRequest, EditUserError,
|
||||
@@ -106,6 +112,31 @@ pub trait WarrenService: Clone + Send + Sync + 'static {
|
||||
&self,
|
||||
request: WarrenCpRequest,
|
||||
) -> impl Future<Output = Result<WarrenCpResponse, WarrenCpError>> + Send;
|
||||
|
||||
fn warren_get_share(
|
||||
&self,
|
||||
request: GetShareRequest,
|
||||
) -> impl Future<Output = Result<GetShareResponse, GetShareError>> + Send;
|
||||
fn warren_create_share(
|
||||
&self,
|
||||
request: CreateShareRequest,
|
||||
) -> impl Future<Output = Result<CreateShareResponse, CreateShareError>> + Send;
|
||||
fn warren_list_shares(
|
||||
&self,
|
||||
request: ListSharesRequest,
|
||||
) -> impl Future<Output = Result<ListSharesResponse, ListSharesError>> + Send;
|
||||
fn warren_delete_share(
|
||||
&self,
|
||||
request: DeleteShareRequest,
|
||||
) -> impl Future<Output = Result<DeleteShareResponse, DeleteShareError>> + Send;
|
||||
fn warren_share_ls(
|
||||
&self,
|
||||
request: ShareLsRequest,
|
||||
) -> impl Future<Output = Result<ShareLsResponse, ShareLsError>> + Send;
|
||||
fn warren_share_cat(
|
||||
&self,
|
||||
request: ShareCatRequest,
|
||||
) -> impl Future<Output = Result<ShareCatResponse, ShareCatError>> + Send;
|
||||
}
|
||||
|
||||
pub trait FileSystemService: Clone + Send + Sync + 'static {
|
||||
@@ -121,6 +152,10 @@ pub trait FileSystemService: Clone + Send + Sync + 'static {
|
||||
) -> impl Future<Output = Result<SaveResponse, SaveError>> + Send;
|
||||
fn touch(&self, request: TouchRequest) -> impl Future<Output = Result<(), TouchError>> + Send;
|
||||
fn cp(&self, request: CpRequest) -> impl Future<Output = Result<CpResponse, CpError>> + Send;
|
||||
fn stat(
|
||||
&self,
|
||||
request: StatRequest,
|
||||
) -> impl Future<Output = Result<StatResponse, StatError>> + Send;
|
||||
}
|
||||
|
||||
pub trait AuthService: Clone + Send + Sync + 'static {
|
||||
@@ -273,4 +308,20 @@ pub trait AuthService: Clone + Send + Sync + 'static {
|
||||
request: AuthRequest<WarrenCpRequest>,
|
||||
warren_service: &WS,
|
||||
) -> impl Future<Output = Result<WarrenCpResponse, AuthError<WarrenCpError>>> + Send;
|
||||
|
||||
fn auth_warren_create_share<WS: WarrenService>(
|
||||
&self,
|
||||
request: AuthRequest<CreateShareBaseRequest>,
|
||||
warren_service: &WS,
|
||||
) -> impl Future<Output = Result<CreateShareResponse, AuthError<CreateShareError>>> + Send;
|
||||
fn auth_warren_list_shares<WS: WarrenService>(
|
||||
&self,
|
||||
request: AuthRequest<ListSharesRequest>,
|
||||
warren_service: &WS,
|
||||
) -> impl Future<Output = Result<ListSharesResponse, AuthError<ListSharesError>>> + Send;
|
||||
fn auth_warren_delete_share<WS: WarrenService>(
|
||||
&self,
|
||||
request: AuthRequest<DeleteShareRequest>,
|
||||
warren_service: &WS,
|
||||
) -> impl Future<Output = Result<DeleteShareResponse, AuthError<DeleteShareError>>> + Send;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,10 @@ use uuid::Uuid;
|
||||
use crate::domain::warren::models::{
|
||||
auth_session::requests::FetchAuthSessionResponse,
|
||||
file::{AbsoluteFilePath, LsResponse},
|
||||
share::{
|
||||
CreateShareResponse, DeleteShareResponse, GetShareResponse, ListSharesResponse,
|
||||
ShareCatResponse, ShareLsResponse,
|
||||
},
|
||||
user::{ListAllUsersAndWarrensResponse, LoginUserOidcResponse, LoginUserResponse, User},
|
||||
user_warren::UserWarren,
|
||||
warren::{
|
||||
@@ -44,6 +48,22 @@ pub trait WarrenNotifier: Clone + Send + Sync + 'static {
|
||||
path: &AbsoluteFilePath,
|
||||
) -> impl Future<Output = ()> + Send;
|
||||
fn warren_cp(&self, response: &WarrenCpResponse) -> impl Future<Output = ()> + Send;
|
||||
|
||||
fn got_warren_share(&self, response: &GetShareResponse) -> impl Future<Output = ()> + Send;
|
||||
fn warren_share_created(
|
||||
&self,
|
||||
response: &CreateShareResponse,
|
||||
) -> impl Future<Output = ()> + Send;
|
||||
fn warren_shares_listed(
|
||||
&self,
|
||||
response: &ListSharesResponse,
|
||||
) -> impl Future<Output = ()> + Send;
|
||||
fn warren_share_deleted(
|
||||
&self,
|
||||
response: &DeleteShareResponse,
|
||||
) -> impl Future<Output = ()> + Send;
|
||||
fn warren_share_ls(&self, response: &ShareLsResponse) -> impl Future<Output = ()> + Send;
|
||||
fn warren_share_cat(&self, response: &ShareCatResponse) -> impl Future<Output = ()> + Send;
|
||||
}
|
||||
|
||||
pub trait FileSystemNotifier: Clone + Send + Sync + 'static {
|
||||
@@ -63,6 +83,7 @@ pub trait FileSystemNotifier: Clone + Send + Sync + 'static {
|
||||
path: &AbsoluteFilePath,
|
||||
target_path: &AbsoluteFilePath,
|
||||
) -> impl Future<Output = ()> + Send;
|
||||
fn stat(&self, path: &AbsoluteFilePath) -> impl Future<Output = ()> + Send;
|
||||
}
|
||||
|
||||
pub trait AuthNotifier: Clone + Send + Sync + 'static {
|
||||
@@ -175,4 +196,22 @@ pub trait AuthNotifier: Clone + Send + Sync + 'static {
|
||||
user: &User,
|
||||
response: &WarrenCpResponse,
|
||||
) -> impl Future<Output = ()> + Send;
|
||||
|
||||
fn auth_warren_share_created(
|
||||
&self,
|
||||
user: &User,
|
||||
response: &CreateShareResponse,
|
||||
) -> impl Future<Output = ()> + Send;
|
||||
|
||||
fn auth_warren_shares_listed(
|
||||
&self,
|
||||
user: &User,
|
||||
response: &ListSharesResponse,
|
||||
) -> impl Future<Output = ()> + Send;
|
||||
|
||||
fn auth_warren_share_deleted(
|
||||
&self,
|
||||
user: &User,
|
||||
response: &DeleteShareResponse,
|
||||
) -> impl Future<Output = ()> + Send;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,13 @@ use crate::domain::warren::models::{
|
||||
file::{
|
||||
CatError, CatRequest, CpError, CpRequest, CpResponse, FileStream, LsError, LsRequest,
|
||||
LsResponse, MkdirError, MkdirRequest, MvError, MvRequest, RmError, RmRequest, SaveError,
|
||||
SaveRequest, SaveResponse, TouchError, TouchRequest,
|
||||
SaveRequest, SaveResponse, StatError, StatRequest, StatResponse, TouchError, TouchRequest,
|
||||
},
|
||||
share::{
|
||||
CreateShareError, CreateShareRequest, CreateShareResponse, DeleteShareError,
|
||||
DeleteShareRequest, DeleteShareResponse, GetShareError, GetShareRequest, ListSharesError,
|
||||
ListSharesRequest, ListSharesResponse, Share, VerifySharePasswordError,
|
||||
VerifySharePasswordRequest, VerifySharePasswordResponse,
|
||||
},
|
||||
user::{
|
||||
CreateOrUpdateUserOidcError, CreateOrUpdateUserOidcRequest, CreateUserError,
|
||||
@@ -63,6 +69,27 @@ pub trait WarrenRepository: Clone + Send + Sync + 'static {
|
||||
&self,
|
||||
request: FetchWarrenRequest,
|
||||
) -> impl Future<Output = Result<Warren, FetchWarrenError>> + Send;
|
||||
|
||||
fn get_warren_share(
|
||||
&self,
|
||||
request: GetShareRequest,
|
||||
) -> impl Future<Output = Result<Share, GetShareError>> + Send;
|
||||
fn create_warren_share(
|
||||
&self,
|
||||
request: CreateShareRequest,
|
||||
) -> impl Future<Output = Result<CreateShareResponse, CreateShareError>> + Send;
|
||||
fn list_warren_shares(
|
||||
&self,
|
||||
request: ListSharesRequest,
|
||||
) -> impl Future<Output = Result<ListSharesResponse, ListSharesError>> + Send;
|
||||
fn delete_warren_share(
|
||||
&self,
|
||||
request: DeleteShareRequest,
|
||||
) -> impl Future<Output = Result<DeleteShareResponse, DeleteShareError>> + Send;
|
||||
fn verify_warren_share_password(
|
||||
&self,
|
||||
request: VerifySharePasswordRequest,
|
||||
) -> impl Future<Output = Result<VerifySharePasswordResponse, VerifySharePasswordError>> + Send;
|
||||
}
|
||||
|
||||
pub trait FileSystemRepository: Clone + Send + Sync + 'static {
|
||||
@@ -78,6 +105,10 @@ pub trait FileSystemRepository: Clone + Send + Sync + 'static {
|
||||
) -> impl Future<Output = Result<SaveResponse, SaveError>> + Send;
|
||||
fn touch(&self, request: TouchRequest) -> impl Future<Output = Result<(), TouchError>> + Send;
|
||||
fn cp(&self, request: CpRequest) -> impl Future<Output = Result<CpResponse, CpError>> + Send;
|
||||
fn stat(
|
||||
&self,
|
||||
request: StatRequest,
|
||||
) -> impl Future<Output = Result<StatResponse, StatError>> + Send;
|
||||
}
|
||||
|
||||
pub trait AuthRepository: Clone + Send + Sync + 'static {
|
||||
|
||||
@@ -12,6 +12,11 @@ use crate::{
|
||||
},
|
||||
},
|
||||
file::FileStream,
|
||||
share::{
|
||||
CreateShareBaseRequest, CreateShareError, CreateShareResponse,
|
||||
DeleteShareError, DeleteShareRequest, DeleteShareResponse, ListSharesError,
|
||||
ListSharesRequest, ListSharesResponse,
|
||||
},
|
||||
user::{
|
||||
CreateUserError, CreateUserRequest, DeleteUserError, DeleteUserRequest,
|
||||
EditUserError, EditUserRequest, GetOidcRedirectError, GetOidcRedirectRequest,
|
||||
@@ -32,12 +37,13 @@ use crate::{
|
||||
warren::{
|
||||
CreateWarrenError, CreateWarrenRequest, DeleteWarrenError, DeleteWarrenRequest,
|
||||
EditWarrenError, EditWarrenRequest, FetchWarrenError, FetchWarrenRequest,
|
||||
FetchWarrensRequest, Warren, WarrenCatError, WarrenCatRequest, WarrenCpError,
|
||||
WarrenCpRequest, WarrenCpResponse, WarrenLsError, WarrenLsRequest,
|
||||
WarrenLsResponse, WarrenMkdirError, WarrenMkdirRequest, WarrenMkdirResponse,
|
||||
WarrenMvError, WarrenMvRequest, WarrenMvResponse, WarrenRmError,
|
||||
WarrenRmRequest, WarrenRmResponse, WarrenSaveError, WarrenSaveRequest,
|
||||
WarrenSaveResponse, WarrenTouchError, WarrenTouchRequest, WarrenTouchResponse,
|
||||
FetchWarrensRequest, HasWarrenId, Warren, WarrenCatError, WarrenCatRequest,
|
||||
WarrenCpError, WarrenCpRequest, WarrenCpResponse, WarrenLsError,
|
||||
WarrenLsRequest, WarrenLsResponse, WarrenMkdirError, WarrenMkdirRequest,
|
||||
WarrenMkdirResponse, WarrenMvError, WarrenMvRequest, WarrenMvResponse,
|
||||
WarrenRmError, WarrenRmRequest, WarrenRmResponse, WarrenSaveError,
|
||||
WarrenSaveRequest, WarrenSaveResponse, WarrenTouchError, WarrenTouchRequest,
|
||||
WarrenTouchResponse,
|
||||
},
|
||||
},
|
||||
ports::{AuthMetrics, AuthNotifier, AuthRepository, AuthService, WarrenService},
|
||||
@@ -644,17 +650,7 @@ where
|
||||
request: AuthRequest<WarrenLsRequest>,
|
||||
warren_service: &WS,
|
||||
) -> Result<WarrenLsResponse, AuthError<WarrenLsError>> {
|
||||
let session_response = self.fetch_auth_session((&request).into()).await?;
|
||||
|
||||
let request = request.into_value();
|
||||
|
||||
let user_warren = self
|
||||
.repository
|
||||
.fetch_user_warren(FetchUserWarrenRequest::new(
|
||||
session_response.user().id().clone(),
|
||||
request.warren_id().clone(),
|
||||
))
|
||||
.await?;
|
||||
let (request, user, user_warren) = self.get_session_data_and_user_warren(request).await?;
|
||||
|
||||
if !user_warren.can_list_files() {
|
||||
return Err(AuthError::InsufficientPermissions);
|
||||
@@ -667,9 +663,7 @@ where
|
||||
|
||||
if let Ok(response) = result.as_ref() {
|
||||
self.metrics.record_auth_warren_ls_success().await;
|
||||
self.notifier
|
||||
.auth_warren_ls(session_response.user(), response)
|
||||
.await;
|
||||
self.notifier.auth_warren_ls(&user, response).await;
|
||||
} else {
|
||||
self.metrics.record_auth_warren_ls_failure().await;
|
||||
}
|
||||
@@ -682,23 +676,14 @@ where
|
||||
request: AuthRequest<WarrenCatRequest>,
|
||||
warren_service: &WS,
|
||||
) -> Result<FileStream, AuthError<WarrenCatError>> {
|
||||
let session_response = self.fetch_auth_session((&request).into()).await?;
|
||||
|
||||
let request = request.into_value();
|
||||
let path = request.base().path().clone();
|
||||
|
||||
let user_warren = self
|
||||
.repository
|
||||
.fetch_user_warren(FetchUserWarrenRequest::new(
|
||||
session_response.user().id().clone(),
|
||||
request.warren_id().clone(),
|
||||
))
|
||||
.await?;
|
||||
let (request, user, user_warren) = self.get_session_data_and_user_warren(request).await?;
|
||||
|
||||
if !user_warren.can_read_files() {
|
||||
return Err(AuthError::InsufficientPermissions);
|
||||
}
|
||||
|
||||
let path = request.base().path().clone();
|
||||
|
||||
let result = warren_service
|
||||
.warren_cat(request)
|
||||
.await
|
||||
@@ -707,7 +692,7 @@ where
|
||||
if let Ok(_stream) = result.as_ref() {
|
||||
self.metrics.record_auth_warren_cat_success().await;
|
||||
self.notifier
|
||||
.auth_warren_cat(session_response.user(), user_warren.warren_id(), &path)
|
||||
.auth_warren_cat(&user, user_warren.warren_id(), &path)
|
||||
.await;
|
||||
} else {
|
||||
self.metrics.record_auth_warren_cat_failure().await;
|
||||
@@ -721,17 +706,7 @@ where
|
||||
request: AuthRequest<WarrenMkdirRequest>,
|
||||
warren_service: &WS,
|
||||
) -> Result<WarrenMkdirResponse, AuthError<WarrenMkdirError>> {
|
||||
let session_response = self.fetch_auth_session((&request).into()).await?;
|
||||
|
||||
let request = request.into_value();
|
||||
|
||||
let user_warren = self
|
||||
.repository
|
||||
.fetch_user_warren(FetchUserWarrenRequest::new(
|
||||
session_response.user().id().clone(),
|
||||
request.warren_id().clone(),
|
||||
))
|
||||
.await?;
|
||||
let (request, user, user_warren) = self.get_session_data_and_user_warren(request).await?;
|
||||
|
||||
// TODO: Maybe create a separate permission for this
|
||||
if !user_warren.can_modify_files() {
|
||||
@@ -745,9 +720,7 @@ where
|
||||
|
||||
if let Ok(response) = result.as_ref() {
|
||||
self.metrics.record_auth_warren_mkdir_success().await;
|
||||
self.notifier
|
||||
.auth_warren_mkdir(session_response.user(), response)
|
||||
.await;
|
||||
self.notifier.auth_warren_mkdir(&user, response).await;
|
||||
} else {
|
||||
self.metrics.record_auth_warren_mkdir_failure().await;
|
||||
}
|
||||
@@ -760,17 +733,7 @@ where
|
||||
request: AuthRequest<WarrenRmRequest>,
|
||||
warren_service: &WS,
|
||||
) -> Result<WarrenRmResponse, AuthError<WarrenRmError>> {
|
||||
let session_response = self.fetch_auth_session((&request).into()).await?;
|
||||
|
||||
let request = request.into_value();
|
||||
|
||||
let user_warren = self
|
||||
.repository
|
||||
.fetch_user_warren(FetchUserWarrenRequest::new(
|
||||
session_response.user().id().clone(),
|
||||
request.warren_id().clone(),
|
||||
))
|
||||
.await?;
|
||||
let (request, user, user_warren) = self.get_session_data_and_user_warren(request).await?;
|
||||
|
||||
if !user_warren.can_delete_files() {
|
||||
return Err(AuthError::InsufficientPermissions);
|
||||
@@ -783,9 +746,7 @@ where
|
||||
|
||||
if let Ok(response) = result.as_ref() {
|
||||
self.metrics.record_auth_warren_rm_success().await;
|
||||
self.notifier
|
||||
.auth_warren_rm(session_response.user(), response)
|
||||
.await;
|
||||
self.notifier.auth_warren_rm(&user, response).await;
|
||||
} else {
|
||||
self.metrics.record_auth_warren_rm_failure().await;
|
||||
}
|
||||
@@ -798,17 +759,7 @@ where
|
||||
request: AuthRequest<WarrenMvRequest>,
|
||||
warren_service: &WS,
|
||||
) -> Result<WarrenMvResponse, AuthError<WarrenMvError>> {
|
||||
let session_response = self.fetch_auth_session((&request).into()).await?;
|
||||
|
||||
let request = request.into_value();
|
||||
|
||||
let user_warren = self
|
||||
.repository
|
||||
.fetch_user_warren(FetchUserWarrenRequest::new(
|
||||
session_response.user().id().clone(),
|
||||
request.warren_id().clone(),
|
||||
))
|
||||
.await?;
|
||||
let (request, user, user_warren) = self.get_session_data_and_user_warren(request).await?;
|
||||
|
||||
if !user_warren.can_modify_files() {
|
||||
return Err(AuthError::InsufficientPermissions);
|
||||
@@ -821,9 +772,7 @@ where
|
||||
|
||||
if let Ok(response) = result.as_ref() {
|
||||
self.metrics.record_auth_warren_mv_success().await;
|
||||
self.notifier
|
||||
.auth_warren_mv(session_response.user(), response)
|
||||
.await;
|
||||
self.notifier.auth_warren_mv(&user, response).await;
|
||||
} else {
|
||||
self.metrics.record_auth_warren_mv_failure().await;
|
||||
}
|
||||
@@ -836,17 +785,7 @@ where
|
||||
request: AuthRequest<WarrenSaveRequest<'_>>,
|
||||
warren_service: &WS,
|
||||
) -> Result<WarrenSaveResponse, AuthError<WarrenSaveError>> {
|
||||
let session_response = self.fetch_auth_session((&request).into()).await?;
|
||||
|
||||
let request = request.into_value();
|
||||
|
||||
let user_warren = self
|
||||
.repository
|
||||
.fetch_user_warren(FetchUserWarrenRequest::new(
|
||||
session_response.user().id().clone(),
|
||||
request.warren_id().clone(),
|
||||
))
|
||||
.await?;
|
||||
let (request, user, user_warren) = self.get_session_data_and_user_warren(request).await?;
|
||||
|
||||
// TODO: Maybe create a separate permission for this
|
||||
if !user_warren.can_modify_files() {
|
||||
@@ -860,9 +799,7 @@ where
|
||||
|
||||
if let Ok(response) = result.as_ref() {
|
||||
self.metrics.record_auth_warren_save_success().await;
|
||||
self.notifier
|
||||
.auth_warren_save(session_response.user(), response)
|
||||
.await;
|
||||
self.notifier.auth_warren_save(&user, response).await;
|
||||
} else {
|
||||
self.metrics.record_auth_warren_save_failure().await;
|
||||
}
|
||||
@@ -875,17 +812,7 @@ where
|
||||
request: AuthRequest<WarrenTouchRequest>,
|
||||
warren_service: &WS,
|
||||
) -> Result<WarrenTouchResponse, AuthError<WarrenTouchError>> {
|
||||
let session_response = self.fetch_auth_session((&request).into()).await?;
|
||||
|
||||
let request = request.into_value();
|
||||
|
||||
let user_warren = self
|
||||
.repository
|
||||
.fetch_user_warren(FetchUserWarrenRequest::new(
|
||||
session_response.user().id().clone(),
|
||||
request.warren_id().clone(),
|
||||
))
|
||||
.await?;
|
||||
let (request, user, user_warren) = self.get_session_data_and_user_warren(request).await?;
|
||||
|
||||
// TODO: Maybe create a separate permission for this
|
||||
if !user_warren.can_modify_files() {
|
||||
@@ -899,9 +826,7 @@ where
|
||||
|
||||
if let Ok(response) = result.as_ref() {
|
||||
self.metrics.record_auth_warren_touch_success().await;
|
||||
self.notifier
|
||||
.auth_warren_touch(session_response.user(), response)
|
||||
.await;
|
||||
self.notifier.auth_warren_touch(&user, response).await;
|
||||
} else {
|
||||
self.metrics.record_auth_warren_touch_failure().await;
|
||||
}
|
||||
@@ -914,17 +839,7 @@ where
|
||||
request: AuthRequest<WarrenCpRequest>,
|
||||
warren_service: &WS,
|
||||
) -> Result<WarrenCpResponse, AuthError<WarrenCpError>> {
|
||||
let session_response = self.fetch_auth_session((&request).into()).await?;
|
||||
|
||||
let request = request.into_value();
|
||||
|
||||
let user_warren = self
|
||||
.repository
|
||||
.fetch_user_warren(FetchUserWarrenRequest::new(
|
||||
session_response.user().id().clone(),
|
||||
request.warren_id().clone(),
|
||||
))
|
||||
.await?;
|
||||
let (request, user, user_warren) = self.get_session_data_and_user_warren(request).await?;
|
||||
|
||||
// TODO: Maybe create a separate permission for this
|
||||
if !user_warren.can_modify_files() {
|
||||
@@ -938,13 +853,128 @@ where
|
||||
|
||||
if let Ok(response) = result.as_ref() {
|
||||
self.metrics.record_auth_warren_cp_success().await;
|
||||
self.notifier
|
||||
.auth_warren_cp(session_response.user(), response)
|
||||
.await;
|
||||
self.notifier.auth_warren_cp(&user, response).await;
|
||||
} else {
|
||||
self.metrics.record_auth_warren_cp_failure().await;
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
async fn auth_warren_create_share<WS: WarrenService>(
|
||||
&self,
|
||||
request: AuthRequest<CreateShareBaseRequest>,
|
||||
warren_service: &WS,
|
||||
) -> Result<CreateShareResponse, AuthError<CreateShareError>> {
|
||||
let (request, user, user_warren) = self.get_session_data_and_user_warren(request).await?;
|
||||
|
||||
if !user_warren.can_create_shares() {
|
||||
return Err(AuthError::InsufficientPermissions);
|
||||
}
|
||||
|
||||
let result = warren_service
|
||||
.warren_create_share(request.build_share_request(user.id().clone()))
|
||||
.await
|
||||
.map_err(AuthError::Custom);
|
||||
|
||||
if let Ok(response) = result.as_ref() {
|
||||
self.metrics.record_auth_share_creation_success().await;
|
||||
self.notifier
|
||||
.auth_warren_share_created(&user, response)
|
||||
.await;
|
||||
} else {
|
||||
self.metrics.record_auth_share_creation_failure().await;
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
async fn auth_warren_list_shares<WS: WarrenService>(
|
||||
&self,
|
||||
request: AuthRequest<ListSharesRequest>,
|
||||
warren_service: &WS,
|
||||
) -> Result<ListSharesResponse, AuthError<ListSharesError>> {
|
||||
let (request, user, user_warren) = self.get_session_data_and_user_warren(request).await?;
|
||||
|
||||
if !user_warren.can_list_shares() {
|
||||
return Err(AuthError::InsufficientPermissions);
|
||||
}
|
||||
|
||||
let result = warren_service
|
||||
.warren_list_shares(request)
|
||||
.await
|
||||
.map_err(AuthError::Custom);
|
||||
|
||||
if let Ok(response) = result.as_ref() {
|
||||
self.metrics.record_auth_share_list_success().await;
|
||||
self.notifier
|
||||
.auth_warren_shares_listed(&user, response)
|
||||
.await;
|
||||
} else {
|
||||
self.metrics.record_auth_share_list_failure().await;
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
async fn auth_warren_delete_share<WS: WarrenService>(
|
||||
&self,
|
||||
request: AuthRequest<DeleteShareRequest>,
|
||||
warren_service: &WS,
|
||||
) -> Result<DeleteShareResponse, AuthError<DeleteShareError>> {
|
||||
let (request, user, user_warren) = self.get_session_data_and_user_warren(request).await?;
|
||||
|
||||
if !user_warren.can_delete_shares() {
|
||||
return Err(AuthError::InsufficientPermissions);
|
||||
}
|
||||
|
||||
let result = warren_service
|
||||
.warren_delete_share(request)
|
||||
.await
|
||||
.map_err(AuthError::Custom);
|
||||
|
||||
if let Ok(response) = result.as_ref() {
|
||||
self.metrics.record_auth_share_deletion_success().await;
|
||||
self.notifier
|
||||
.auth_warren_share_deleted(&user, response)
|
||||
.await;
|
||||
} else {
|
||||
self.metrics.record_auth_share_deletion_failure().await;
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl<R, M, N, OIDC> Service<R, M, N, OIDC>
|
||||
where
|
||||
R: AuthRepository,
|
||||
M: AuthMetrics,
|
||||
N: AuthNotifier,
|
||||
OIDC: OidcService,
|
||||
{
|
||||
/// A helper to get a [UserWarren], [User] and the underlying request from an [AuthRequest]
|
||||
async fn get_session_data_and_user_warren<T, E>(
|
||||
&self,
|
||||
request: AuthRequest<T>,
|
||||
) -> Result<(T, User, UserWarren), AuthError<E>>
|
||||
where
|
||||
T: HasWarrenId,
|
||||
E: std::error::Error,
|
||||
{
|
||||
let session_response = self.fetch_auth_session((&request).into()).await?;
|
||||
let user = session_response.unpack().1;
|
||||
|
||||
let request = request.into_value();
|
||||
|
||||
let user_warren = self
|
||||
.repository
|
||||
.fetch_user_warren(FetchUserWarrenRequest::new(
|
||||
user.id().clone(),
|
||||
request.warren_id().clone(),
|
||||
))
|
||||
.await?;
|
||||
|
||||
Ok((request, user, user_warren))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::domain::warren::{
|
||||
models::file::{
|
||||
CatError, CatRequest, CpError, CpRequest, CpResponse, FileStream, LsError, LsRequest,
|
||||
LsResponse, MkdirError, MkdirRequest, MvError, MvRequest, RmError, RmRequest, SaveError,
|
||||
SaveRequest, SaveResponse, TouchError, TouchRequest,
|
||||
SaveRequest, SaveResponse, StatError, StatRequest, StatResponse, TouchError, TouchRequest,
|
||||
},
|
||||
ports::{FileSystemMetrics, FileSystemNotifier, FileSystemRepository, FileSystemService},
|
||||
};
|
||||
@@ -152,4 +152,18 @@ where
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
async fn stat(&self, request: StatRequest) -> Result<StatResponse, StatError> {
|
||||
let path = request.path().clone();
|
||||
let result = self.repository.stat(request).await;
|
||||
|
||||
if result.is_ok() {
|
||||
self.metrics.record_stat_success().await;
|
||||
self.notifier.stat(&path).await;
|
||||
} else {
|
||||
self.metrics.record_stat_failure().await;
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
use crate::domain::warren::{
|
||||
models::{
|
||||
file::FileStream,
|
||||
file::{File, FileStream},
|
||||
share::{
|
||||
CreateShareError, CreateShareRequest, CreateShareResponse, DeleteShareError,
|
||||
DeleteShareRequest, DeleteShareResponse, GetShareError, GetShareRequest,
|
||||
GetShareResponse, ListSharesError, ListSharesRequest, ListSharesResponse, Share,
|
||||
ShareCatError, ShareCatRequest, ShareCatResponse, ShareLsError, ShareLsRequest,
|
||||
ShareLsResponse,
|
||||
},
|
||||
warren::{
|
||||
CreateWarrenError, CreateWarrenRequest, DeleteWarrenError, DeleteWarrenRequest,
|
||||
EditWarrenError, EditWarrenRequest, FetchWarrensError, FetchWarrensRequest,
|
||||
@@ -335,4 +342,199 @@ where
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
async fn warren_get_share(
|
||||
&self,
|
||||
request: GetShareRequest,
|
||||
) -> Result<GetShareResponse, GetShareError> {
|
||||
let share = match self.repository.get_warren_share(request).await {
|
||||
Ok(share) => share,
|
||||
Err(e) => {
|
||||
self.metrics.record_warren_get_share_failure().await;
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
|
||||
let warren = match self
|
||||
.repository
|
||||
.fetch_warren(FetchWarrenRequest::new(*share.warren_id()))
|
||||
.await
|
||||
{
|
||||
Ok(warren) => warren,
|
||||
Err(e) => {
|
||||
self.metrics.record_warren_get_share_failure().await;
|
||||
return Err(e.into());
|
||||
}
|
||||
};
|
||||
|
||||
let file: File = match self
|
||||
.fs_service
|
||||
.stat(share.build_fs_stat_request(&warren))
|
||||
.await
|
||||
{
|
||||
Ok(stat) => stat.into(),
|
||||
Err(e) => {
|
||||
self.metrics.record_warren_get_share_failure().await;
|
||||
return Err(e.into());
|
||||
}
|
||||
};
|
||||
|
||||
let response = GetShareResponse::new(share, file);
|
||||
|
||||
self.metrics.record_warren_get_share_success().await;
|
||||
self.notifier.got_warren_share(&response).await;
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
async fn warren_create_share(
|
||||
&self,
|
||||
request: CreateShareRequest,
|
||||
) -> Result<CreateShareResponse, CreateShareError> {
|
||||
let warren = self.repository.fetch_warren((&request).into()).await?;
|
||||
let stat_request = request.build_fs_stat_request(&warren);
|
||||
|
||||
self.fs_service.stat(stat_request).await?;
|
||||
|
||||
let result = self.repository.create_warren_share(request).await;
|
||||
|
||||
if let Ok(response) = result.as_ref() {
|
||||
self.metrics.record_warren_share_creation_success().await;
|
||||
self.notifier.warren_share_created(response).await;
|
||||
} else {
|
||||
self.metrics.record_warren_share_creation_failure().await;
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
async fn warren_list_shares(
|
||||
&self,
|
||||
request: ListSharesRequest,
|
||||
) -> Result<ListSharesResponse, ListSharesError> {
|
||||
let result = self.repository.list_warren_shares(request).await;
|
||||
|
||||
if let Ok(response) = result.as_ref() {
|
||||
self.metrics.record_warren_share_list_success().await;
|
||||
self.notifier.warren_shares_listed(response).await;
|
||||
} else {
|
||||
self.metrics.record_warren_share_list_failure().await;
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
async fn warren_delete_share(
|
||||
&self,
|
||||
request: DeleteShareRequest,
|
||||
) -> Result<DeleteShareResponse, DeleteShareError> {
|
||||
let result = self.repository.delete_warren_share(request).await;
|
||||
|
||||
if let Ok(response) = result.as_ref() {
|
||||
self.metrics.record_warren_share_deletion_success().await;
|
||||
self.notifier.warren_share_deleted(response).await;
|
||||
} else {
|
||||
self.metrics.record_warren_share_deletion_failure().await;
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
async fn warren_share_ls(
|
||||
&self,
|
||||
request: ShareLsRequest,
|
||||
) -> Result<ShareLsResponse, ShareLsError> {
|
||||
let share: Share = match self
|
||||
.repository
|
||||
.verify_warren_share_password((&request).into())
|
||||
.await
|
||||
{
|
||||
Ok(response) => response.into(),
|
||||
Err(e) => {
|
||||
self.metrics.record_warren_share_ls_failure().await;
|
||||
return Err(e.into());
|
||||
}
|
||||
};
|
||||
|
||||
let warren = match self
|
||||
.repository
|
||||
.fetch_warren(FetchWarrenRequest::new(*share.warren_id()))
|
||||
.await
|
||||
{
|
||||
Ok(warren) => warren,
|
||||
Err(e) => {
|
||||
self.metrics.record_warren_share_ls_failure().await;
|
||||
return Err(e.into());
|
||||
}
|
||||
};
|
||||
|
||||
let path = request.path().clone();
|
||||
|
||||
let ls_response = match self
|
||||
.warren_ls(request.build_warren_ls_request(&share, &warren))
|
||||
.await
|
||||
{
|
||||
Ok(response) => response,
|
||||
Err(e) => {
|
||||
self.metrics.record_warren_share_ls_failure().await;
|
||||
return Err(e.into());
|
||||
}
|
||||
};
|
||||
|
||||
let response = ShareLsResponse::new(share, warren, path, ls_response.base().clone());
|
||||
|
||||
self.metrics.record_warren_share_ls_success().await;
|
||||
self.notifier.warren_share_ls(&response).await;
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
async fn warren_share_cat(
|
||||
&self,
|
||||
request: ShareCatRequest,
|
||||
) -> Result<ShareCatResponse, ShareCatError> {
|
||||
let share: Share = match self
|
||||
.repository
|
||||
.verify_warren_share_password((&request).into())
|
||||
.await
|
||||
{
|
||||
Ok(response) => response.into(),
|
||||
Err(e) => {
|
||||
self.metrics.record_warren_share_cat_failure().await;
|
||||
return Err(e.into());
|
||||
}
|
||||
};
|
||||
|
||||
let warren = match self
|
||||
.repository
|
||||
.fetch_warren(FetchWarrenRequest::new(*share.warren_id()))
|
||||
.await
|
||||
{
|
||||
Ok(warren) => warren,
|
||||
Err(e) => {
|
||||
self.metrics.record_warren_share_cat_failure().await;
|
||||
return Err(e.into());
|
||||
}
|
||||
};
|
||||
|
||||
let path = request.path().clone();
|
||||
|
||||
let stream = match self
|
||||
.warren_cat(request.build_warren_cat_request(&share, &warren))
|
||||
.await
|
||||
{
|
||||
Ok(response) => response,
|
||||
Err(e) => {
|
||||
self.metrics.record_warren_share_cat_failure().await;
|
||||
return Err(e.into());
|
||||
}
|
||||
};
|
||||
|
||||
let response = ShareCatResponse::new(share, warren, path, stream);
|
||||
|
||||
self.metrics.record_warren_share_cat_success().await;
|
||||
self.notifier.warren_share_cat(&response).await;
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
use crate::{
|
||||
domain::warren::models::{
|
||||
auth_session::{AuthError, requests::FetchAuthSessionError},
|
||||
file::{LsError, MkdirError, RmError},
|
||||
file::{CatError, LsError, MkdirError, RmError, StatError},
|
||||
share::{GetShareError, ShareCatError, ShareLsError, VerifySharePasswordError},
|
||||
user::{
|
||||
CreateUserError, GetOidcRedirectError, LoginUserError, LoginUserOidcError,
|
||||
RegisterUserError, VerifyUserPasswordError,
|
||||
},
|
||||
user_warren::requests::FetchUserWarrenError,
|
||||
warren::{
|
||||
FetchWarrenError, FetchWarrensError, WarrenLsError, WarrenMkdirError, WarrenMvError,
|
||||
WarrenRmError, WarrenSaveError,
|
||||
FetchWarrenError, FetchWarrensError, WarrenCatError, WarrenLsError, WarrenMkdirError,
|
||||
WarrenMvError, WarrenRmError, WarrenSaveError,
|
||||
},
|
||||
},
|
||||
inbound::http::responses::ApiError,
|
||||
@@ -191,7 +192,7 @@ impl<T: std::error::Error> From<AuthError<T>> for ApiError {
|
||||
impl From<CreateUserError> for ApiError {
|
||||
fn from(value: CreateUserError) -> Self {
|
||||
match value {
|
||||
CreateUserError::Unknown(err) => Self::InternalServerError(err.to_string()),
|
||||
CreateUserError::Unknown(err) => err.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -205,7 +206,63 @@ impl From<GetOidcRedirectError> for ApiError {
|
||||
}
|
||||
},
|
||||
GetOidcRedirectError::Disabled => Self::BadRequest("OIDC is disabled".to_string()),
|
||||
GetOidcRedirectError::Unknown(e) => Self::InternalServerError(e.to_string()),
|
||||
GetOidcRedirectError::Unknown(e) => e.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<GetShareError> for ApiError {
|
||||
fn from(value: GetShareError) -> Self {
|
||||
match value {
|
||||
GetShareError::FetchWarren(err) => err.into(),
|
||||
GetShareError::FsStat(err) => match err {
|
||||
StatError::NotFound => Self::NotFound("The share no longer exists".to_string()),
|
||||
StatError::Unknown(err) => err.into(),
|
||||
},
|
||||
GetShareError::Unknown(err) => err.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<VerifySharePasswordError> for ApiError {
|
||||
fn from(value: VerifySharePasswordError) -> Self {
|
||||
match value {
|
||||
VerifySharePasswordError::NotFound => {
|
||||
Self::NotFound("Could not find the specified share".to_string())
|
||||
}
|
||||
VerifySharePasswordError::IncorrectPassword => {
|
||||
Self::BadRequest("Incorrect password".to_string())
|
||||
}
|
||||
VerifySharePasswordError::Unknown(error) => error.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ShareLsError> for ApiError {
|
||||
fn from(value: ShareLsError) -> Self {
|
||||
match value {
|
||||
ShareLsError::VerifySharePassword(err) => err.into(),
|
||||
ShareLsError::WarrenLs(err) => err.into(),
|
||||
ShareLsError::FetchWarren(err) => err.into(),
|
||||
ShareLsError::Unknown(err) => err.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ShareCatError> for ApiError {
|
||||
fn from(value: ShareCatError) -> Self {
|
||||
match value {
|
||||
ShareCatError::VerifySharePassword(err) => err.into(),
|
||||
ShareCatError::FetchWarren(err) => err.into(),
|
||||
ShareCatError::WarrenCat(err) => match err {
|
||||
WarrenCatError::FetchWarren(err) => err.into(),
|
||||
WarrenCatError::FileSystem(err) => match err {
|
||||
CatError::NotFound => Self::NotFound("This file does not exist".to_string()),
|
||||
CatError::Unknown(err) => err.into(),
|
||||
},
|
||||
WarrenCatError::Unknown(err) => err.into(),
|
||||
},
|
||||
ShareCatError::Unknown(err) => err.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,10 @@ pub(super) struct CreateUserWarrenHttpRequestBody {
|
||||
can_read_files: bool,
|
||||
can_modify_files: bool,
|
||||
can_delete_files: bool,
|
||||
can_list_shares: bool,
|
||||
can_create_shares: bool,
|
||||
can_modify_shares: bool,
|
||||
can_delete_shares: bool,
|
||||
}
|
||||
|
||||
impl CreateUserWarrenHttpRequestBody {
|
||||
@@ -37,6 +41,10 @@ impl CreateUserWarrenHttpRequestBody {
|
||||
self.can_read_files,
|
||||
self.can_modify_files,
|
||||
self.can_delete_files,
|
||||
self.can_list_shares,
|
||||
self.can_create_shares,
|
||||
self.can_modify_shares,
|
||||
self.can_delete_shares,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,10 @@ pub(super) struct EditUserWarrenHttpRequestBody {
|
||||
can_read_files: bool,
|
||||
can_modify_files: bool,
|
||||
can_delete_files: bool,
|
||||
can_list_shares: bool,
|
||||
can_create_shares: bool,
|
||||
can_modify_shares: bool,
|
||||
can_delete_shares: bool,
|
||||
}
|
||||
|
||||
impl EditUserWarrenHttpRequestBody {
|
||||
@@ -37,6 +41,10 @@ impl EditUserWarrenHttpRequestBody {
|
||||
self.can_read_files,
|
||||
self.can_modify_files,
|
||||
self.can_delete_files,
|
||||
self.can_list_shares,
|
||||
self.can_create_shares,
|
||||
self.can_modify_shares,
|
||||
self.can_delete_shares,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,8 @@ use axum::{
|
||||
use axum_extra::extract::CookieJar;
|
||||
|
||||
use crate::{
|
||||
domain::warren::models::auth_session::AuthSessionIdWithType, inbound::http::responses::ApiError,
|
||||
domain::warren::models::{auth_session::AuthSessionIdWithType, share::SharePassword},
|
||||
inbound::http::responses::ApiError,
|
||||
};
|
||||
|
||||
pub struct SessionIdHeader(pub AuthSessionIdWithType);
|
||||
@@ -51,3 +52,48 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SharePasswordHeader(pub Option<SharePassword>);
|
||||
|
||||
impl SharePasswordHeader {
|
||||
const NAME: &str = "X-Share-Password";
|
||||
}
|
||||
|
||||
impl<S> FromRequestParts<S> for SharePasswordHeader
|
||||
where
|
||||
S: Send + Sync,
|
||||
{
|
||||
type Rejection = ApiError;
|
||||
|
||||
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
|
||||
// Release build
|
||||
if !cfg!(debug_assertions) {
|
||||
let jar = CookieJar::from_request_parts(parts, state).await.unwrap();
|
||||
|
||||
let Some(cookie) = jar.get(Self::NAME) else {
|
||||
return Ok(Self(None));
|
||||
};
|
||||
|
||||
SharePassword::new(cookie.value())
|
||||
.map(|v| Self(Some(v)))
|
||||
.map_err(|_| ApiError::BadRequest("Invalid password".to_string()))
|
||||
}
|
||||
// Debug build
|
||||
else {
|
||||
let Some(header) = parts.headers.get(Self::NAME) else {
|
||||
return Ok(Self(None));
|
||||
};
|
||||
|
||||
let header_str = header.to_str().map_err(|_| {
|
||||
ApiError::InternalServerError(format!(
|
||||
"Failed to get {} header as string",
|
||||
Self::NAME
|
||||
))
|
||||
})?;
|
||||
|
||||
SharePassword::new(header_str)
|
||||
.map(|v| Self(Some(v)))
|
||||
.map_err(|_| ApiError::BadRequest("Invalid share password".to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use serde::Serialize;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::domain::warren::models::{user::User, user_warren::UserWarren, warren::Warren};
|
||||
use crate::domain::warren::models::{
|
||||
share::Share, user::User, user_warren::UserWarren, warren::Warren,
|
||||
};
|
||||
|
||||
pub mod admin;
|
||||
pub mod auth;
|
||||
@@ -39,6 +41,10 @@ pub(super) struct UserWarrenData {
|
||||
can_read_files: bool,
|
||||
can_modify_files: bool,
|
||||
can_delete_files: bool,
|
||||
can_list_shares: bool,
|
||||
can_create_shares: bool,
|
||||
can_modify_shares: bool,
|
||||
can_delete_shares: bool,
|
||||
}
|
||||
|
||||
impl From<UserWarren> for UserWarrenData {
|
||||
@@ -50,23 +56,10 @@ impl From<UserWarren> for UserWarrenData {
|
||||
can_read_files: value.can_read_files(),
|
||||
can_modify_files: value.can_modify_files(),
|
||||
can_delete_files: value.can_delete_files(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
/// A warren that can be safely sent to the client
|
||||
pub(super) struct WarrenData {
|
||||
id: Uuid,
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl From<Warren> for WarrenData {
|
||||
fn from(value: Warren) -> Self {
|
||||
Self {
|
||||
id: *value.id(),
|
||||
name: value.name().to_string(),
|
||||
can_list_shares: value.can_list_shares(),
|
||||
can_create_shares: value.can_create_shares(),
|
||||
can_modify_shares: value.can_modify_shares(),
|
||||
can_delete_shares: value.can_delete_shares(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -89,3 +82,37 @@ impl From<Warren> for AdminWarrenData {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(super) struct ShareData {
|
||||
id: Uuid,
|
||||
creator_id: Uuid,
|
||||
|
||||
warren_id: Uuid,
|
||||
path: String,
|
||||
|
||||
password: bool,
|
||||
|
||||
expires_at: Option<u64>,
|
||||
|
||||
created_at: u64,
|
||||
}
|
||||
|
||||
impl<T> From<T> for ShareData
|
||||
where
|
||||
T: Into<Share>,
|
||||
{
|
||||
fn from(value: T) -> Self {
|
||||
let value: Share = value.into();
|
||||
Self {
|
||||
id: *value.id(),
|
||||
creator_id: *value.creator_id(),
|
||||
warren_id: *value.warren_id(),
|
||||
path: value.path().to_string(),
|
||||
password: value.password_hash().is_some(),
|
||||
expires_at: value.expires_at(),
|
||||
created_at: value.created_at(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
86
backend/src/lib/inbound/http/handlers/warrens/cat_share.rs
Normal file
86
backend/src/lib/inbound/http/handlers/warrens/cat_share.rs
Normal file
@@ -0,0 +1,86 @@
|
||||
use axum::{
|
||||
body::Body,
|
||||
extract::{Query, State},
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use thiserror::Error;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
domain::warren::{
|
||||
models::{
|
||||
file::{AbsoluteFilePathError, FilePath, FilePathError, FileStream},
|
||||
share::{ShareCatRequest, SharePassword, SharePasswordError},
|
||||
},
|
||||
ports::{AuthService, WarrenService},
|
||||
},
|
||||
inbound::http::{AppState, handlers::extractors::SharePasswordHeader, responses::ApiError},
|
||||
};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
enum ParseShareCatHttpRequestError {
|
||||
#[error(transparent)]
|
||||
FilePath(#[from] FilePathError),
|
||||
#[error(transparent)]
|
||||
AbsoluteFilePath(#[from] AbsoluteFilePathError),
|
||||
#[error(transparent)]
|
||||
Password(#[from] SharePasswordError),
|
||||
}
|
||||
|
||||
impl From<ParseShareCatHttpRequestError> for ApiError {
|
||||
fn from(value: ParseShareCatHttpRequestError) -> Self {
|
||||
match value {
|
||||
ParseShareCatHttpRequestError::FilePath(err) => match err {
|
||||
FilePathError::InvalidPath => Self::BadRequest("The path is invalid".to_string()),
|
||||
},
|
||||
ParseShareCatHttpRequestError::AbsoluteFilePath(err) => match err {
|
||||
AbsoluteFilePathError::NotAbsolute => {
|
||||
Self::BadRequest("The path must be absolute".to_string())
|
||||
}
|
||||
},
|
||||
ParseShareCatHttpRequestError::Password(err) => Self::BadRequest(
|
||||
match err {
|
||||
SharePasswordError::Empty => "The provided password is empty",
|
||||
SharePasswordError::LeadingWhitespace
|
||||
| SharePasswordError::TrailingWhitespace
|
||||
| SharePasswordError::TooShort
|
||||
| SharePasswordError::TooLong => "",
|
||||
}
|
||||
.to_string(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(super) struct ShareCatHttpRequestBody {
|
||||
share_id: Uuid,
|
||||
path: String,
|
||||
}
|
||||
|
||||
impl ShareCatHttpRequestBody {
|
||||
fn try_into_domain(
|
||||
self,
|
||||
password: Option<SharePassword>,
|
||||
) -> Result<ShareCatRequest, ParseShareCatHttpRequestError> {
|
||||
let path = FilePath::new(&self.path)?.try_into()?;
|
||||
|
||||
Ok(ShareCatRequest::new(self.share_id, path, password))
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn cat_share<WS: WarrenService, AS: AuthService>(
|
||||
State(state): State<AppState<WS, AS>>,
|
||||
SharePasswordHeader(password): SharePasswordHeader,
|
||||
Query(request): Query<ShareCatHttpRequestBody>,
|
||||
) -> Result<Body, ApiError> {
|
||||
let domain_request = request.try_into_domain(password)?;
|
||||
|
||||
state
|
||||
.warren_service
|
||||
.warren_share_cat(domain_request)
|
||||
.await
|
||||
.map(|response| FileStream::from(response).into())
|
||||
.map_err(ApiError::from)
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
use axum::{Json, extract::State, http::StatusCode};
|
||||
use serde::Deserialize;
|
||||
use thiserror::Error;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
domain::warren::{
|
||||
models::{
|
||||
auth_session::AuthRequest,
|
||||
file::{AbsoluteFilePath, AbsoluteFilePathError, FilePath, FilePathError},
|
||||
share::{CreateShareBaseRequest, SharePassword, SharePasswordError},
|
||||
},
|
||||
ports::{AuthService, WarrenService},
|
||||
},
|
||||
inbound::http::{
|
||||
AppState,
|
||||
handlers::{ShareData, extractors::SessionIdHeader},
|
||||
responses::{ApiError, ApiSuccess},
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(super) struct CreateShareHttpRequestBody {
|
||||
warren_id: Uuid,
|
||||
path: String,
|
||||
lifetime: Option<u64>,
|
||||
password: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
enum ParseCreateShareHttpRequestError {
|
||||
#[error(transparent)]
|
||||
FilePath(#[from] FilePathError),
|
||||
#[error(transparent)]
|
||||
AbsoluteFilePath(#[from] AbsoluteFilePathError),
|
||||
#[error(transparent)]
|
||||
SharePassword(#[from] SharePasswordError),
|
||||
}
|
||||
|
||||
impl From<ParseCreateShareHttpRequestError> for ApiError {
|
||||
fn from(e: ParseCreateShareHttpRequestError) -> Self {
|
||||
ApiError::BadRequest(e.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl CreateShareHttpRequestBody {
|
||||
fn try_into_domain(self) -> Result<CreateShareBaseRequest, ParseCreateShareHttpRequestError> {
|
||||
let path: AbsoluteFilePath = {
|
||||
let file_path = FilePath::new(&self.path)?;
|
||||
file_path.try_into()?
|
||||
};
|
||||
|
||||
let password = if let Some(password) = self.password {
|
||||
Some(SharePassword::new(&password)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(CreateShareBaseRequest::new(
|
||||
self.warren_id,
|
||||
path,
|
||||
password,
|
||||
self.lifetime,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn create_share<WS: WarrenService, AS: AuthService>(
|
||||
State(state): State<AppState<WS, AS>>,
|
||||
SessionIdHeader(session): SessionIdHeader,
|
||||
Json(request): Json<CreateShareHttpRequestBody>,
|
||||
) -> Result<ApiSuccess<ShareData>, ApiError> {
|
||||
let domain_request = AuthRequest::new(session, request.try_into_domain()?);
|
||||
|
||||
state
|
||||
.auth_service
|
||||
.auth_warren_create_share(domain_request, state.warren_service.as_ref())
|
||||
.await
|
||||
.map(|response| {
|
||||
ApiSuccess::new(
|
||||
StatusCode::CREATED,
|
||||
ShareData::from(response.share().clone()),
|
||||
)
|
||||
})
|
||||
.map_err(ApiError::from)
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
use axum::{Json, extract::State, http::StatusCode};
|
||||
use serde::Deserialize;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
domain::warren::{
|
||||
models::{auth_session::AuthRequest, share::DeleteShareRequest},
|
||||
ports::{AuthService, WarrenService},
|
||||
},
|
||||
inbound::http::{
|
||||
AppState,
|
||||
handlers::{ShareData, extractors::SessionIdHeader},
|
||||
responses::{ApiError, ApiSuccess},
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(super) struct DeleteShareHttpRequestBody {
|
||||
warren_id: Uuid,
|
||||
share_id: Uuid,
|
||||
}
|
||||
|
||||
impl DeleteShareHttpRequestBody {
|
||||
fn into_domain(self) -> DeleteShareRequest {
|
||||
DeleteShareRequest::new(self.warren_id, self.share_id)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn delete_share<WS: WarrenService, AS: AuthService>(
|
||||
State(state): State<AppState<WS, AS>>,
|
||||
SessionIdHeader(session): SessionIdHeader,
|
||||
Json(request): Json<DeleteShareHttpRequestBody>,
|
||||
) -> Result<ApiSuccess<ShareData>, ApiError> {
|
||||
let domain_request = AuthRequest::new(session, request.into_domain());
|
||||
|
||||
state
|
||||
.auth_service
|
||||
.auth_warren_delete_share(domain_request, state.warren_service.as_ref())
|
||||
.await
|
||||
.map(|response| ApiSuccess::new(StatusCode::OK, ShareData::from(response)))
|
||||
.map_err(ApiError::from)
|
||||
}
|
||||
59
backend/src/lib/inbound/http/handlers/warrens/get_share.rs
Normal file
59
backend/src/lib/inbound/http/handlers/warrens/get_share.rs
Normal file
@@ -0,0 +1,59 @@
|
||||
use axum::{Json, extract::State, http::StatusCode};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
domain::warren::{
|
||||
models::share::{GetShareRequest, GetShareResponse},
|
||||
ports::{AuthService, WarrenService},
|
||||
},
|
||||
inbound::http::{
|
||||
AppState,
|
||||
handlers::ShareData,
|
||||
responses::{ApiError, ApiSuccess},
|
||||
},
|
||||
};
|
||||
|
||||
use super::WarrenFileElement;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(super) struct GetShareHttpRequestBody {
|
||||
share_id: Uuid,
|
||||
}
|
||||
|
||||
impl GetShareHttpRequestBody {
|
||||
fn into_domain(self) -> GetShareRequest {
|
||||
GetShareRequest::new(self.share_id)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(super) struct GetShareHttpResponseBody {
|
||||
share: ShareData,
|
||||
file: WarrenFileElement,
|
||||
}
|
||||
|
||||
impl From<GetShareResponse> for GetShareHttpResponseBody {
|
||||
fn from(value: GetShareResponse) -> Self {
|
||||
Self {
|
||||
share: value.share().clone().into(),
|
||||
file: value.file().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_share<WS: WarrenService, AS: AuthService>(
|
||||
State(state): State<AppState<WS, AS>>,
|
||||
Json(request): Json<GetShareHttpRequestBody>,
|
||||
) -> Result<ApiSuccess<GetShareHttpResponseBody>, ApiError> {
|
||||
let domain_request = request.into_domain();
|
||||
|
||||
state
|
||||
.warren_service
|
||||
.warren_get_share(domain_request)
|
||||
.await
|
||||
.map(|response| ApiSuccess::new(StatusCode::OK, response.into()))
|
||||
.map_err(ApiError::from)
|
||||
}
|
||||
77
backend/src/lib/inbound/http/handlers/warrens/list_shares.rs
Normal file
77
backend/src/lib/inbound/http/handlers/warrens/list_shares.rs
Normal file
@@ -0,0 +1,77 @@
|
||||
use axum::{Json, extract::State, http::StatusCode};
|
||||
use serde::Deserialize;
|
||||
use thiserror::Error;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
domain::warren::{
|
||||
models::{
|
||||
auth_session::AuthRequest,
|
||||
file::{AbsoluteFilePath, AbsoluteFilePathError, FilePath, FilePathError},
|
||||
share::ListSharesRequest,
|
||||
},
|
||||
ports::{AuthService, WarrenService},
|
||||
},
|
||||
inbound::http::{
|
||||
AppState,
|
||||
handlers::{ShareData, extractors::SessionIdHeader},
|
||||
responses::{ApiError, ApiSuccess},
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(super) struct ListSharesHttpRequestBody {
|
||||
warren_id: Uuid,
|
||||
path: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
enum ParseListSharesHttpRequestError {
|
||||
#[error(transparent)]
|
||||
FilePath(#[from] FilePathError),
|
||||
#[error(transparent)]
|
||||
AbsoluteFilePath(#[from] AbsoluteFilePathError),
|
||||
}
|
||||
|
||||
impl From<ParseListSharesHttpRequestError> for ApiError {
|
||||
fn from(e: ParseListSharesHttpRequestError) -> Self {
|
||||
ApiError::BadRequest(e.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl ListSharesHttpRequestBody {
|
||||
fn try_into_domain(self) -> Result<ListSharesRequest, ParseListSharesHttpRequestError> {
|
||||
let path: AbsoluteFilePath = {
|
||||
let file_path = FilePath::new(&self.path)?;
|
||||
file_path.try_into()?
|
||||
};
|
||||
|
||||
Ok(ListSharesRequest::new(self.warren_id, path))
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn list_shares<WS: WarrenService, AS: AuthService>(
|
||||
State(state): State<AppState<WS, AS>>,
|
||||
SessionIdHeader(session): SessionIdHeader,
|
||||
Json(request): Json<ListSharesHttpRequestBody>,
|
||||
) -> Result<ApiSuccess<Vec<ShareData>>, ApiError> {
|
||||
let domain_request = AuthRequest::new(session, request.try_into_domain()?);
|
||||
|
||||
state
|
||||
.auth_service
|
||||
.auth_warren_list_shares(domain_request, state.warren_service.as_ref())
|
||||
.await
|
||||
.map(|response| {
|
||||
ApiSuccess::new(
|
||||
StatusCode::OK,
|
||||
response
|
||||
.shares()
|
||||
.into_iter()
|
||||
.cloned()
|
||||
.map(ShareData::from)
|
||||
.collect(),
|
||||
)
|
||||
})
|
||||
.map_err(ApiError::from)
|
||||
}
|
||||
111
backend/src/lib/inbound/http/handlers/warrens/ls_share.rs
Normal file
111
backend/src/lib/inbound/http/handlers/warrens/ls_share.rs
Normal file
@@ -0,0 +1,111 @@
|
||||
use axum::{Json, extract::State, http::StatusCode};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
domain::warren::{
|
||||
models::{
|
||||
file::{AbsoluteFilePathError, FilePath, FilePathError},
|
||||
share::{ShareLsRequest, ShareLsResponse, SharePassword, SharePasswordError},
|
||||
},
|
||||
ports::{AuthService, WarrenService},
|
||||
},
|
||||
inbound::http::{
|
||||
AppState,
|
||||
responses::{ApiError, ApiSuccess},
|
||||
},
|
||||
};
|
||||
|
||||
use super::WarrenFileElement;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
enum ParseShareLsHttpRequestError {
|
||||
#[error(transparent)]
|
||||
FilePath(#[from] FilePathError),
|
||||
#[error(transparent)]
|
||||
AbsoluteFilePath(#[from] AbsoluteFilePathError),
|
||||
#[error(transparent)]
|
||||
Password(#[from] SharePasswordError),
|
||||
}
|
||||
|
||||
impl From<ParseShareLsHttpRequestError> for ApiError {
|
||||
fn from(value: ParseShareLsHttpRequestError) -> Self {
|
||||
match value {
|
||||
ParseShareLsHttpRequestError::FilePath(err) => match err {
|
||||
FilePathError::InvalidPath => Self::BadRequest("The path is invalid".to_string()),
|
||||
},
|
||||
ParseShareLsHttpRequestError::AbsoluteFilePath(err) => match err {
|
||||
AbsoluteFilePathError::NotAbsolute => {
|
||||
Self::BadRequest("The path must be absolute".to_string())
|
||||
}
|
||||
},
|
||||
ParseShareLsHttpRequestError::Password(err) => Self::BadRequest(
|
||||
match err {
|
||||
SharePasswordError::Empty => "The provided password is empty",
|
||||
SharePasswordError::LeadingWhitespace
|
||||
| SharePasswordError::TrailingWhitespace
|
||||
| SharePasswordError::TooShort
|
||||
| SharePasswordError::TooLong => "",
|
||||
}
|
||||
.to_string(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(super) struct LsShareHttpRequestBody {
|
||||
share_id: Uuid,
|
||||
path: String,
|
||||
password: Option<String>,
|
||||
}
|
||||
|
||||
impl LsShareHttpRequestBody {
|
||||
fn try_into_domain(self) -> Result<ShareLsRequest, ParseShareLsHttpRequestError> {
|
||||
let path = FilePath::new(&self.path)?.try_into()?;
|
||||
let password = if let Some(password) = self.password.as_ref() {
|
||||
Some(SharePassword::new(password)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(ShareLsRequest::new(self.share_id, path, password))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ShareLsResponseData {
|
||||
files: Vec<WarrenFileElement>,
|
||||
parent: Option<WarrenFileElement>,
|
||||
}
|
||||
|
||||
impl From<ShareLsResponse> for ShareLsResponseData {
|
||||
fn from(value: ShareLsResponse) -> Self {
|
||||
Self {
|
||||
files: value
|
||||
.base()
|
||||
.files()
|
||||
.into_iter()
|
||||
.map(WarrenFileElement::from)
|
||||
.collect(),
|
||||
parent: value.base().parent().map(WarrenFileElement::from),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn ls_share<WS: WarrenService, AS: AuthService>(
|
||||
State(state): State<AppState<WS, AS>>,
|
||||
Json(request): Json<LsShareHttpRequestBody>,
|
||||
) -> Result<ApiSuccess<ShareLsResponseData>, ApiError> {
|
||||
let domain_request = request.try_into_domain()?;
|
||||
|
||||
state
|
||||
.warren_service
|
||||
.warren_share_ls(domain_request)
|
||||
.await
|
||||
.map(|response| ApiSuccess::new(StatusCode::OK, response.into()))
|
||||
.map_err(ApiError::from)
|
||||
}
|
||||
@@ -1,5 +1,11 @@
|
||||
mod cat_share;
|
||||
mod create_share;
|
||||
mod delete_share;
|
||||
mod fetch_warren;
|
||||
mod get_share;
|
||||
mod list_shares;
|
||||
mod list_warrens;
|
||||
mod ls_share;
|
||||
mod upload_warren_files;
|
||||
mod warren_cat;
|
||||
mod warren_cp;
|
||||
@@ -14,23 +20,53 @@ use axum::{
|
||||
routing::{get, post},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
domain::warren::ports::{AuthService, WarrenService},
|
||||
inbound::http::AppState,
|
||||
};
|
||||
|
||||
use fetch_warren::fetch_warren;
|
||||
use list_warrens::list_warrens;
|
||||
use serde::Serialize;
|
||||
use warren_ls::list_warren_files;
|
||||
|
||||
use warren_mkdir::create_warren_directory;
|
||||
use warren_rm::warren_rm;
|
||||
|
||||
use cat_share::cat_share;
|
||||
use create_share::create_share;
|
||||
use delete_share::delete_share;
|
||||
use get_share::get_share;
|
||||
use list_shares::list_shares;
|
||||
use ls_share::ls_share;
|
||||
use upload_warren_files::warren_save;
|
||||
use warren_cat::fetch_file;
|
||||
use warren_cp::warren_cp;
|
||||
use warren_mv::warren_mv;
|
||||
|
||||
use crate::{
|
||||
domain::warren::{
|
||||
models::file::{File, FileMimeType, FileType},
|
||||
ports::{AuthService, WarrenService},
|
||||
},
|
||||
inbound::http::AppState,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct WarrenFileElement {
|
||||
name: String,
|
||||
file_type: FileType,
|
||||
mime_type: Option<String>,
|
||||
created_at: Option<u64>,
|
||||
}
|
||||
|
||||
impl From<&File> for WarrenFileElement {
|
||||
fn from(value: &File) -> Self {
|
||||
Self {
|
||||
name: value.name().to_string(),
|
||||
file_type: value.file_type().to_owned(),
|
||||
mime_type: value.mime_type().map(FileMimeType::to_string),
|
||||
created_at: value.created_at(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn routes<WS: WarrenService, AS: AuthService>() -> Router<AppState<WS, AS>> {
|
||||
Router::new()
|
||||
.route("/", get(list_warrens))
|
||||
@@ -46,4 +82,10 @@ pub fn routes<WS: WarrenService, AS: AuthService>() -> Router<AppState<WS, AS>>
|
||||
)
|
||||
.route("/files/mv", post(warren_mv))
|
||||
.route("/files/cp", post(warren_cp))
|
||||
.route("/files/create_share", post(create_share))
|
||||
.route("/files/list_shares", post(list_shares))
|
||||
.route("/files/delete_share", post(delete_share))
|
||||
.route("/files/get_share", post(get_share))
|
||||
.route("/files/ls_share", post(ls_share))
|
||||
.route("/files/cat_share", get(cat_share))
|
||||
}
|
||||
|
||||
@@ -2,8 +2,7 @@ use axum::{
|
||||
body::Body,
|
||||
extract::{Query, State},
|
||||
};
|
||||
use base64::Engine as _;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::Deserialize;
|
||||
use thiserror::Error;
|
||||
use tokio_util::io::ReaderStream;
|
||||
use uuid::Uuid;
|
||||
@@ -20,21 +19,6 @@ use crate::{
|
||||
inbound::http::{AppState, handlers::extractors::SessionIdHeader, responses::ApiError},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, PartialEq, Hash)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(super) struct FetchWarrenFileHttpResponseBody {
|
||||
// base64 encoded file content bytes
|
||||
contents: String,
|
||||
}
|
||||
|
||||
impl From<Vec<u8>> for FetchWarrenFileHttpResponseBody {
|
||||
fn from(value: Vec<u8>) -> Self {
|
||||
Self {
|
||||
contents: base64::prelude::BASE64_STANDARD.encode(&value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(super) struct WarrenCatHttpRequestBody {
|
||||
|
||||
@@ -7,10 +7,7 @@ use crate::{
|
||||
domain::warren::{
|
||||
models::{
|
||||
auth_session::AuthRequest,
|
||||
file::{
|
||||
AbsoluteFilePathError, File, FileMimeType, FilePath, FilePathError, FileType,
|
||||
LsRequest,
|
||||
},
|
||||
file::{AbsoluteFilePathError, FilePath, FilePathError, LsRequest},
|
||||
warren::{WarrenLsRequest, WarrenLsResponse},
|
||||
},
|
||||
ports::{AuthService, WarrenService},
|
||||
@@ -22,6 +19,8 @@ use crate::{
|
||||
},
|
||||
};
|
||||
|
||||
use super::WarrenFileElement;
|
||||
|
||||
#[derive(Debug, Clone, Error)]
|
||||
pub enum ParseWarrenLsHttpRequestError {
|
||||
#[error(transparent)]
|
||||
@@ -65,15 +64,6 @@ impl From<ParseWarrenLsHttpRequestError> for ApiError {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct WarrenFileElement {
|
||||
name: String,
|
||||
file_type: FileType,
|
||||
mime_type: Option<String>,
|
||||
created_at: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ListWarrenFilesResponseData {
|
||||
@@ -81,17 +71,6 @@ pub struct ListWarrenFilesResponseData {
|
||||
parent: Option<WarrenFileElement>,
|
||||
}
|
||||
|
||||
impl From<&File> for WarrenFileElement {
|
||||
fn from(value: &File) -> Self {
|
||||
Self {
|
||||
name: value.name().to_string(),
|
||||
file_type: value.file_type().to_owned(),
|
||||
mime_type: value.mime_type().map(FileMimeType::to_string),
|
||||
created_at: value.created_at(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WarrenLsResponse> for ListWarrenFilesResponseData {
|
||||
fn from(value: WarrenLsResponse) -> Self {
|
||||
Self {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
use std::{os::unix::fs::MetadataExt, path::PathBuf};
|
||||
|
||||
use anyhow::{Context, anyhow, bail};
|
||||
use futures_util::TryStreamExt;
|
||||
use rustix::fs::statx;
|
||||
use rustix::fs::{Statx, statx};
|
||||
use tokio::{
|
||||
fs,
|
||||
io::{self, AsyncWriteExt as _},
|
||||
@@ -17,7 +17,8 @@ use crate::{
|
||||
AbsoluteFilePath, CatError, CatRequest, CpError, CpRequest, CpResponse, File,
|
||||
FileMimeType, FileName, FilePath, FileStream, FileType, LsError, LsRequest,
|
||||
LsResponse, MkdirError, MkdirRequest, MvError, MvRequest, RelativeFilePath,
|
||||
RmError, RmRequest, SaveError, SaveRequest, SaveResponse, TouchError, TouchRequest,
|
||||
RmError, RmRequest, SaveError, SaveRequest, SaveResponse, StatError, StatRequest,
|
||||
StatResponse, TouchError, TouchRequest,
|
||||
},
|
||||
warren::UploadFileStream,
|
||||
},
|
||||
@@ -242,7 +243,12 @@ impl FileSystem {
|
||||
async fn touch(&self, path: &AbsoluteFilePath) -> io::Result<()> {
|
||||
let path = self.get_target_path(path);
|
||||
|
||||
fs::File::create(&path).await.map(|_| ())
|
||||
fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.open(&path)
|
||||
.await
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
async fn cp(
|
||||
@@ -257,6 +263,46 @@ impl FileSystem {
|
||||
|
||||
Ok(CpResponse::new(path, target_path))
|
||||
}
|
||||
|
||||
async fn stat(&self, path: AbsoluteFilePath) -> anyhow::Result<File> {
|
||||
let target_path = self.get_target_path(&path);
|
||||
let fs_path = PathBuf::from(target_path.to_string());
|
||||
|
||||
let metadata = fs::metadata(&fs_path).await?;
|
||||
|
||||
let name = {
|
||||
let file_name = fs_path
|
||||
.clone()
|
||||
.file_name()
|
||||
.context("Failed to get file name")?
|
||||
.to_owned()
|
||||
.into_string()
|
||||
.ok()
|
||||
.context("Failed to get file name")?;
|
||||
FileName::new(&file_name)?
|
||||
};
|
||||
|
||||
let file_type = {
|
||||
let file_type = metadata.file_type();
|
||||
|
||||
if file_type.is_dir() {
|
||||
FileType::Directory
|
||||
} else if file_type.is_file() {
|
||||
FileType::File
|
||||
} else {
|
||||
bail!("Invalid file type");
|
||||
}
|
||||
};
|
||||
|
||||
let mime_type = match file_type {
|
||||
FileType::File => FileMimeType::from_name(name.as_str()),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let created_at = get_btime(&fs_path);
|
||||
|
||||
Ok(File::new(name, file_type, mime_type, created_at))
|
||||
}
|
||||
}
|
||||
|
||||
impl FileSystemRepository for FileSystem {
|
||||
@@ -333,11 +379,25 @@ impl FileSystemRepository for FileSystem {
|
||||
.await
|
||||
.map_err(|e| CpError::Unknown(e.into()))
|
||||
}
|
||||
|
||||
async fn stat(&self, request: StatRequest) -> Result<StatResponse, StatError> {
|
||||
let path = request.into_path();
|
||||
Ok(self.stat(path).await.map(StatResponse::new)?)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Use `DirEntry::metadata` once `target=x86_64-unknown-linux-musl` updates from musl 1.2.3 to 1.2.5
|
||||
// https://github.com/rust-lang/rust/pull/142682
|
||||
fn get_btime<P>(path: P) -> Option<u64>
|
||||
where
|
||||
P: rustix::path::Arg,
|
||||
{
|
||||
get_statx(path)
|
||||
.ok()
|
||||
.map(|statx| statx.stx_btime.tv_sec as u64)
|
||||
}
|
||||
|
||||
fn get_statx<P>(path: P) -> rustix::io::Result<Statx>
|
||||
where
|
||||
P: rustix::path::Arg,
|
||||
{
|
||||
@@ -349,6 +409,4 @@ where
|
||||
rustix::fs::StatxFlags::BTIME,
|
||||
)
|
||||
}
|
||||
.ok()
|
||||
.map(|statx| statx.stx_btime.tv_sec as u64)
|
||||
}
|
||||
|
||||
@@ -110,6 +110,47 @@ impl WarrenMetrics for MetricsDebugLogger {
|
||||
async fn record_warren_cp_failure(&self) {
|
||||
tracing::debug!("[Metrics] Warren entry cp failed");
|
||||
}
|
||||
|
||||
async fn record_warren_get_share_success(&self) {
|
||||
tracing::debug!("[Metrics] Warren get share succeeded");
|
||||
}
|
||||
async fn record_warren_get_share_failure(&self) {
|
||||
tracing::debug!("[Metrics] Warren get share failed");
|
||||
}
|
||||
|
||||
async fn record_warren_share_creation_success(&self) {
|
||||
tracing::debug!("[Metrics] Warren share creation succeeded");
|
||||
}
|
||||
async fn record_warren_share_creation_failure(&self) {
|
||||
tracing::debug!("[Metrics] Warren share creation failed");
|
||||
}
|
||||
|
||||
async fn record_warren_share_list_success(&self) {
|
||||
tracing::debug!("[Metrics] Warren share list succeeded");
|
||||
}
|
||||
async fn record_warren_share_list_failure(&self) {
|
||||
tracing::debug!("[Metrics] Warren share list failed");
|
||||
}
|
||||
async fn record_warren_share_deletion_success(&self) {
|
||||
tracing::debug!("[Metrics] Warren share deletion succeeded");
|
||||
}
|
||||
async fn record_warren_share_deletion_failure(&self) {
|
||||
tracing::debug!("[Metrics] Warren share deletion failed");
|
||||
}
|
||||
|
||||
async fn record_warren_share_ls_success(&self) {
|
||||
tracing::debug!("[Metrics] Warren share ls succeeded");
|
||||
}
|
||||
async fn record_warren_share_ls_failure(&self) {
|
||||
tracing::debug!("[Metrics] Warren share ls failed");
|
||||
}
|
||||
|
||||
async fn record_warren_share_cat_success(&self) {
|
||||
tracing::debug!("[Metrics] Warren share cat succeeded");
|
||||
}
|
||||
async fn record_warren_share_cat_failure(&self) {
|
||||
tracing::debug!("[Metrics] Warren share cat failed");
|
||||
}
|
||||
}
|
||||
|
||||
impl FileSystemMetrics for MetricsDebugLogger {
|
||||
@@ -168,6 +209,13 @@ impl FileSystemMetrics for MetricsDebugLogger {
|
||||
async fn record_cp_failure(&self) {
|
||||
tracing::debug!("[Metrics] Cp failed");
|
||||
}
|
||||
|
||||
async fn record_stat_success(&self) {
|
||||
tracing::debug!("[Metrics] Stat succeeded");
|
||||
}
|
||||
async fn record_stat_failure(&self) {
|
||||
tracing::debug!("[Metrics] Stat failed");
|
||||
}
|
||||
}
|
||||
|
||||
impl AuthMetrics for MetricsDebugLogger {
|
||||
@@ -359,6 +407,27 @@ impl AuthMetrics for MetricsDebugLogger {
|
||||
async fn record_auth_warren_cp_failure(&self) {
|
||||
tracing::debug!("[Metrics] Auth warren cp failed");
|
||||
}
|
||||
|
||||
async fn record_auth_share_creation_success(&self) {
|
||||
tracing::debug!("[Metrics] Auth warren share creation succeeded");
|
||||
}
|
||||
async fn record_auth_share_creation_failure(&self) {
|
||||
tracing::debug!("[Metrics] Auth warren share creation failed");
|
||||
}
|
||||
|
||||
async fn record_auth_share_list_success(&self) {
|
||||
tracing::debug!("[Metrics] Auth warren share list succeeded");
|
||||
}
|
||||
async fn record_auth_share_list_failure(&self) {
|
||||
tracing::debug!("[Metrics] Auth warren share list failed");
|
||||
}
|
||||
|
||||
async fn record_auth_share_deletion_success(&self) {
|
||||
tracing::debug!("[Metrics] Auth warren share deletion succeeded");
|
||||
}
|
||||
async fn record_auth_share_deletion_failure(&self) {
|
||||
tracing::debug!("[Metrics] Auth warren share deletion failed");
|
||||
}
|
||||
}
|
||||
|
||||
impl OidcMetrics for MetricsDebugLogger {
|
||||
|
||||
@@ -9,6 +9,10 @@ use crate::domain::{
|
||||
models::{
|
||||
auth_session::requests::FetchAuthSessionResponse,
|
||||
file::{AbsoluteFilePath, LsResponse},
|
||||
share::{
|
||||
CreateShareResponse, DeleteShareResponse, GetShareResponse, ListSharesResponse,
|
||||
ShareCatResponse, ShareLsResponse,
|
||||
},
|
||||
user::{
|
||||
ListAllUsersAndWarrensResponse, LoginUserOidcResponse, LoginUserResponse, User,
|
||||
},
|
||||
@@ -115,6 +119,57 @@ impl WarrenNotifier for NotifierDebugLogger {
|
||||
response.warren().name()
|
||||
);
|
||||
}
|
||||
|
||||
async fn warren_share_created(&self, response: &CreateShareResponse) {
|
||||
tracing::debug!(
|
||||
"[Notifier] Created share for file {} in warren {}",
|
||||
response.share().path(),
|
||||
response.share().warren_id(),
|
||||
);
|
||||
}
|
||||
|
||||
async fn warren_shares_listed(&self, response: &ListSharesResponse) {
|
||||
tracing::debug!(
|
||||
"[Notifier] Listed {} share(s) for file {} in warren {}",
|
||||
response.shares().len(),
|
||||
response.path(),
|
||||
response.warren().id(),
|
||||
);
|
||||
}
|
||||
|
||||
async fn warren_share_deleted(&self, response: &DeleteShareResponse) {
|
||||
tracing::debug!(
|
||||
"[Notifier] Deleted share {} for file {} in warren {}",
|
||||
response.share().id(),
|
||||
response.share().path(),
|
||||
response.share().warren_id(),
|
||||
);
|
||||
}
|
||||
|
||||
async fn got_warren_share(&self, response: &GetShareResponse) {
|
||||
tracing::debug!(
|
||||
"[Notifier] Got share {} from warren {}",
|
||||
response.share().id(),
|
||||
response.share().warren_id()
|
||||
);
|
||||
}
|
||||
|
||||
async fn warren_share_ls(&self, response: &ShareLsResponse) {
|
||||
tracing::debug!(
|
||||
"[Notifier] Listed {} file(s) in share {} at path {}",
|
||||
response.base().files().len(),
|
||||
response.share().id(),
|
||||
response.path()
|
||||
);
|
||||
}
|
||||
|
||||
async fn warren_share_cat(&self, response: &ShareCatResponse) {
|
||||
tracing::debug!(
|
||||
"[Notifier] Fetched file {} from share {}",
|
||||
response.path(),
|
||||
response.share().id(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl FileSystemNotifier for NotifierDebugLogger {
|
||||
@@ -149,6 +204,10 @@ impl FileSystemNotifier for NotifierDebugLogger {
|
||||
async fn cp(&self, path: &AbsoluteFilePath, target_path: &AbsoluteFilePath) {
|
||||
tracing::debug!("[Notifier] Copied file {} to {}", path, target_path);
|
||||
}
|
||||
|
||||
async fn stat(&self, path: &AbsoluteFilePath) {
|
||||
tracing::debug!("[Notifier] Got stats for file {}", path);
|
||||
}
|
||||
}
|
||||
|
||||
impl AuthNotifier for NotifierDebugLogger {
|
||||
@@ -368,6 +427,35 @@ impl AuthNotifier for NotifierDebugLogger {
|
||||
user.id()
|
||||
)
|
||||
}
|
||||
|
||||
async fn auth_warren_share_created(&self, user: &User, response: &CreateShareResponse) {
|
||||
tracing::debug!(
|
||||
"[Notifier] Created share for file {} in warren {} for authenticated user {}",
|
||||
response.share().path(),
|
||||
response.share().warren_id(),
|
||||
user.id(),
|
||||
);
|
||||
}
|
||||
|
||||
async fn auth_warren_shares_listed(&self, user: &User, response: &ListSharesResponse) {
|
||||
tracing::debug!(
|
||||
"[Notifier] Listed {} share(s) for file {} in warren {} for authenticated user {}",
|
||||
response.shares().len(),
|
||||
response.path(),
|
||||
response.warren().id(),
|
||||
user.id(),
|
||||
);
|
||||
}
|
||||
|
||||
async fn auth_warren_share_deleted(&self, user: &User, response: &DeleteShareResponse) {
|
||||
tracing::debug!(
|
||||
"[Notifier] Deleted share {} for file {} in warren {} for authenticated user {}",
|
||||
response.share().id(),
|
||||
response.share().path(),
|
||||
response.share().warren_id(),
|
||||
user.id(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl OidcNotifier for NotifierDebugLogger {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::{Context as _, anyhow, bail};
|
||||
use argon2::{
|
||||
Argon2, PasswordHash, PasswordVerifier as _,
|
||||
@@ -9,8 +7,7 @@ use argon2::{
|
||||
},
|
||||
};
|
||||
use chrono::Utc;
|
||||
use sqlx::{Acquire as _, PgConnection, PgPool};
|
||||
use tokio::task::JoinHandle;
|
||||
use sqlx::{Acquire as _, PgConnection};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::domain::warren::{
|
||||
@@ -372,27 +369,7 @@ impl AuthRepository for Postgres {
|
||||
}
|
||||
|
||||
impl Postgres {
|
||||
pub(super) fn start_session_cleanup_task(pool: PgPool, interval: Duration) -> JoinHandle<()> {
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
{
|
||||
let Ok(mut connection) = pool.acquire().await else {
|
||||
break;
|
||||
};
|
||||
|
||||
if let Ok(count) = Self::delete_expired_auth_sessions(&mut connection).await {
|
||||
tracing::debug!("Removed {count} expired auth session(s)");
|
||||
}
|
||||
}
|
||||
|
||||
tokio::time::sleep(interval).await;
|
||||
}
|
||||
|
||||
tracing::debug!("Session cleanup task stopped");
|
||||
})
|
||||
}
|
||||
|
||||
async fn delete_expired_auth_sessions(
|
||||
pub(super) async fn delete_expired_auth_sessions(
|
||||
connection: &mut PgConnection,
|
||||
) -> Result<u64, sqlx::Error> {
|
||||
let delete_count = sqlx::query(
|
||||
@@ -916,7 +893,11 @@ impl Postgres {
|
||||
can_list_files = $3,
|
||||
can_read_files = $4,
|
||||
can_modify_files = $5,
|
||||
can_delete_files = $6
|
||||
can_delete_files = $6,
|
||||
can_list_shares = $7,
|
||||
can_create_shares = $8,
|
||||
can_modify_shares = $9,
|
||||
can_delete_shares = $10
|
||||
WHERE
|
||||
user_id = $1 AND
|
||||
warren_id = $2
|
||||
@@ -930,6 +911,10 @@ impl Postgres {
|
||||
.bind(user_warren.can_read_files())
|
||||
.bind(user_warren.can_modify_files())
|
||||
.bind(user_warren.can_delete_files())
|
||||
.bind(user_warren.can_list_shares())
|
||||
.bind(user_warren.can_create_shares())
|
||||
.bind(user_warren.can_modify_shares())
|
||||
.bind(user_warren.can_delete_shares())
|
||||
.fetch_one(connection)
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -5,7 +5,9 @@ use sqlx::{
|
||||
ConnectOptions as _, Connection as _, PgConnection, PgPool,
|
||||
postgres::{PgConnectOptions, PgPoolOptions},
|
||||
};
|
||||
use tokio::task::JoinHandle;
|
||||
pub mod auth;
|
||||
pub mod share;
|
||||
pub mod warrens;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -58,10 +60,34 @@ impl Postgres {
|
||||
sqlx::migrate!("./migrations").run(&pool).await?;
|
||||
|
||||
// 3600 seconds = 1 hour
|
||||
Self::start_session_cleanup_task(pool.clone(), Duration::from_secs(3600));
|
||||
Self::start_cleanup_tasks(pool.clone(), Duration::from_secs(3600));
|
||||
|
||||
Ok(Self { pool })
|
||||
}
|
||||
|
||||
pub(super) fn start_cleanup_tasks(pool: PgPool, interval: Duration) -> JoinHandle<()> {
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
{
|
||||
let Ok(mut connection) = pool.acquire().await else {
|
||||
break;
|
||||
};
|
||||
|
||||
if let Ok(count) = Self::delete_expired_auth_sessions(&mut connection).await {
|
||||
tracing::debug!("Removed {count} expired auth session(s)");
|
||||
}
|
||||
|
||||
if let Ok(count) = Self::delete_expired_shares(&mut connection).await {
|
||||
tracing::debug!("Deleted {count} expired share(s)");
|
||||
}
|
||||
}
|
||||
|
||||
tokio::time::sleep(interval).await;
|
||||
}
|
||||
|
||||
tracing::debug!("Session cleanup task stopped");
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn is_not_found_error(err: &sqlx::Error) -> bool {
|
||||
|
||||
285
backend/src/lib/outbound/postgres/share.rs
Normal file
285
backend/src/lib/outbound/postgres/share.rs
Normal file
@@ -0,0 +1,285 @@
|
||||
use anyhow::anyhow;
|
||||
use argon2::{
|
||||
Argon2, PasswordHash, PasswordVerifier as _,
|
||||
password_hash::{PasswordHasher as _, SaltString, rand_core::OsRng},
|
||||
};
|
||||
use chrono::{NaiveDateTime, Utc};
|
||||
use sqlx::{Acquire as _, PgConnection};
|
||||
use thiserror::Error;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::domain::warren::models::{
|
||||
file::{AbsoluteFilePathError, FilePath, FilePathError},
|
||||
share::{
|
||||
CreateShareRequest, DeleteShareRequest, GetShareRequest, ListSharesRequest, Share,
|
||||
VerifySharePasswordError, VerifySharePasswordRequest,
|
||||
},
|
||||
warren::HasWarrenId as _,
|
||||
};
|
||||
|
||||
use super::{Postgres, is_not_found_error};
|
||||
|
||||
#[derive(sqlx::FromRow)]
|
||||
struct ShareRow {
|
||||
id: Uuid,
|
||||
|
||||
creator_id: Uuid,
|
||||
warren_id: Uuid,
|
||||
|
||||
path: String,
|
||||
|
||||
password_hash: Option<String>,
|
||||
|
||||
expires_at: Option<NaiveDateTime>,
|
||||
|
||||
created_at: NaiveDateTime,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
enum TryFromShareRowError {
|
||||
#[error(transparent)]
|
||||
FilePath(#[from] FilePathError),
|
||||
#[error(transparent)]
|
||||
AbsoluteFilePath(#[from] AbsoluteFilePathError),
|
||||
}
|
||||
|
||||
impl TryFrom<ShareRow> for Share {
|
||||
type Error = TryFromShareRowError;
|
||||
|
||||
fn try_from(value: ShareRow) -> Result<Self, Self::Error> {
|
||||
Ok(Share::new(
|
||||
value.id,
|
||||
value.creator_id,
|
||||
value.warren_id,
|
||||
FilePath::new(&value.path)?.try_into()?,
|
||||
value.password_hash,
|
||||
value
|
||||
.expires_at
|
||||
.map(|nt| nt.and_utc().timestamp_millis() as u64),
|
||||
value.created_at.and_utc().timestamp_millis() as u64,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn get_share(
|
||||
connection: &mut PgConnection,
|
||||
request: GetShareRequest,
|
||||
) -> anyhow::Result<Share> {
|
||||
let share_row: ShareRow = sqlx::query_as(
|
||||
"
|
||||
SELECT
|
||||
id,
|
||||
creator_id,
|
||||
warren_id,
|
||||
path,
|
||||
password_hash,
|
||||
expires_at,
|
||||
created_at
|
||||
FROM
|
||||
shares
|
||||
WHERE
|
||||
id = $1 AND
|
||||
(expires_at IS NULL OR expires_at > CURRENT_TIMESTAMP)
|
||||
",
|
||||
)
|
||||
.bind(request.share_id())
|
||||
.fetch_one(connection)
|
||||
.await?;
|
||||
|
||||
Ok(Share::try_from(share_row)?)
|
||||
}
|
||||
|
||||
pub(super) async fn list_shares(
|
||||
connection: &mut PgConnection,
|
||||
request: ListSharesRequest,
|
||||
) -> anyhow::Result<Vec<Share>> {
|
||||
let share_rows: Vec<ShareRow> = sqlx::query_as(
|
||||
"
|
||||
SELECT
|
||||
id,
|
||||
creator_id,
|
||||
warren_id,
|
||||
path,
|
||||
password_hash,
|
||||
expires_at,
|
||||
created_at
|
||||
FROM
|
||||
shares
|
||||
WHERE
|
||||
warren_id = $1 AND
|
||||
path = $2
|
||||
ORDER BY
|
||||
created_at DESC
|
||||
",
|
||||
)
|
||||
.bind(request.warren_id())
|
||||
.bind(request.path())
|
||||
.fetch_all(connection)
|
||||
.await?;
|
||||
|
||||
let shares = share_rows
|
||||
.into_iter()
|
||||
.map(Share::try_from)
|
||||
.collect::<Result<Vec<Share>, TryFromShareRowError>>()?;
|
||||
|
||||
Ok(shares)
|
||||
}
|
||||
|
||||
pub(super) async fn create_share(
|
||||
connection: &mut PgConnection,
|
||||
request: CreateShareRequest,
|
||||
) -> anyhow::Result<Share> {
|
||||
let mut tx = connection.begin().await?;
|
||||
|
||||
let password_hash = if let Some(password) = request.base().password() {
|
||||
let salt = SaltString::generate(&mut OsRng);
|
||||
let argon2 = Argon2::default();
|
||||
|
||||
Some(
|
||||
argon2
|
||||
.hash_password(password.as_str().as_bytes(), &salt)
|
||||
.map(|h| h.to_string())
|
||||
.map_err(|e| anyhow!("Failed to hash file password: {e:?}"))?,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let expires_at = if let Some(lifetime) = request.base().lifetime() {
|
||||
Some(Utc::now().timestamp_millis() + i64::try_from(lifetime)?.saturating_mul(1000))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let share: ShareRow = sqlx::query_as(
|
||||
"
|
||||
INSERT INTO shares (
|
||||
creator_id,
|
||||
warren_id,
|
||||
path,
|
||||
password_hash,
|
||||
expires_at
|
||||
) VALUES (
|
||||
$1,
|
||||
$2,
|
||||
$3,
|
||||
$4,
|
||||
TO_TIMESTAMP($5::double precision / 1000)
|
||||
)
|
||||
RETURNING
|
||||
*
|
||||
",
|
||||
)
|
||||
.bind(request.creator_id())
|
||||
.bind(request.warren_id())
|
||||
.bind(request.base().path())
|
||||
.bind(password_hash)
|
||||
.bind(expires_at)
|
||||
.fetch_one(&mut *tx)
|
||||
.await?;
|
||||
|
||||
tx.commit().await?;
|
||||
|
||||
Ok(Share::try_from(share)?)
|
||||
}
|
||||
|
||||
pub(super) async fn delete_share(
|
||||
connection: &mut PgConnection,
|
||||
request: DeleteShareRequest,
|
||||
) -> anyhow::Result<Share> {
|
||||
let mut tx = connection.begin().await?;
|
||||
|
||||
let share_row: ShareRow = sqlx::query_as(
|
||||
"
|
||||
DELETE FROM
|
||||
shares
|
||||
WHERE
|
||||
id = $1
|
||||
RETURNING
|
||||
*
|
||||
",
|
||||
)
|
||||
.bind(request.share_id())
|
||||
.fetch_one(&mut *tx)
|
||||
.await?;
|
||||
|
||||
tx.commit().await?;
|
||||
|
||||
Ok(Share::try_from(share_row)?)
|
||||
}
|
||||
|
||||
pub(super) async fn verify_password(
|
||||
connection: &mut PgConnection,
|
||||
request: VerifySharePasswordRequest,
|
||||
) -> Result<Share, VerifySharePasswordError> {
|
||||
let share_row: ShareRow = sqlx::query_as(
|
||||
"
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
shares
|
||||
WHERE
|
||||
id = $1 AND
|
||||
(expires_at IS NULL OR expires_at > CURRENT_TIMESTAMP)
|
||||
",
|
||||
)
|
||||
.bind(request.share_id())
|
||||
.fetch_one(connection)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
if is_not_found_error(&e) {
|
||||
VerifySharePasswordError::NotFound
|
||||
} else {
|
||||
anyhow!(e).into()
|
||||
}
|
||||
})?;
|
||||
|
||||
let share = Share::try_from(share_row).map_err(|e| anyhow!(e))?;
|
||||
|
||||
match (request.password(), share.password_hash()) {
|
||||
// If the share doesn't have a password hash or the request provided a password but the share doesn't have one we do
|
||||
// not care
|
||||
(None, None) | (Some(_), None) => Ok(share),
|
||||
// If there is a password hash and the request provided a password we have to check if they
|
||||
// match
|
||||
(Some(password), Some(hash)) => {
|
||||
let argon = Argon2::default();
|
||||
let hash = PasswordHash::new(hash)
|
||||
.map_err(|e| VerifySharePasswordError::Unknown(anyhow!(e)))?;
|
||||
|
||||
argon
|
||||
.verify_password(password.as_str().as_bytes(), &hash)
|
||||
.map_err(|e| match e {
|
||||
argon2::password_hash::Error::Password => {
|
||||
VerifySharePasswordError::IncorrectPassword
|
||||
}
|
||||
_ => VerifySharePasswordError::Unknown(anyhow!(e)),
|
||||
})?;
|
||||
|
||||
Ok(share)
|
||||
}
|
||||
// If the request didn't provide a password but the share has a password hash the access
|
||||
// should be denied
|
||||
(None, Some(_hash)) => Err(VerifySharePasswordError::IncorrectPassword),
|
||||
}
|
||||
}
|
||||
|
||||
impl Postgres {
|
||||
pub(super) async fn delete_expired_shares(
|
||||
connection: &mut PgConnection,
|
||||
) -> Result<u64, sqlx::Error> {
|
||||
let delete_count = sqlx::query(
|
||||
"
|
||||
DELETE FROM
|
||||
shares
|
||||
WHERE
|
||||
expires_at <= CURRENT_TIMESTAMP
|
||||
",
|
||||
)
|
||||
.execute(connection)
|
||||
.await?
|
||||
.rows_affected();
|
||||
|
||||
Ok(delete_count)
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,12 @@ use uuid::Uuid;
|
||||
use crate::domain::warren::{
|
||||
models::{
|
||||
file::AbsoluteFilePath,
|
||||
share::{
|
||||
CreateShareError, CreateShareRequest, CreateShareResponse, DeleteShareError,
|
||||
DeleteShareRequest, DeleteShareResponse, GetShareError, GetShareRequest,
|
||||
ListSharesError, ListSharesRequest, ListSharesResponse, Share,
|
||||
VerifySharePasswordError, VerifySharePasswordRequest, VerifySharePasswordResponse,
|
||||
},
|
||||
warren::{
|
||||
CreateWarrenError, CreateWarrenRequest, DeleteWarrenError, DeleteWarrenRequest,
|
||||
EditWarrenError, EditWarrenRequest, FetchWarrenError, FetchWarrenRequest,
|
||||
@@ -132,6 +138,86 @@ impl WarrenRepository for Postgres {
|
||||
|
||||
Ok(warren)
|
||||
}
|
||||
|
||||
async fn get_warren_share(&self, request: GetShareRequest) -> Result<Share, GetShareError> {
|
||||
let mut connection = self
|
||||
.pool
|
||||
.acquire()
|
||||
.await
|
||||
.context("Failed to get a PostgreSQL connection")?;
|
||||
|
||||
super::share::get_share(&mut connection, request)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn create_warren_share(
|
||||
&self,
|
||||
request: CreateShareRequest,
|
||||
) -> Result<CreateShareResponse, CreateShareError> {
|
||||
let mut connection = self
|
||||
.pool
|
||||
.acquire()
|
||||
.await
|
||||
.context("Failed to get a PostgreSQL connection")?;
|
||||
|
||||
super::share::create_share(&mut connection, request)
|
||||
.await
|
||||
.map(CreateShareResponse::new)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn list_warren_shares(
|
||||
&self,
|
||||
request: ListSharesRequest,
|
||||
) -> Result<ListSharesResponse, ListSharesError> {
|
||||
let warren = self.fetch_warren((&request).into()).await?;
|
||||
|
||||
let mut connection = self
|
||||
.pool
|
||||
.acquire()
|
||||
.await
|
||||
.context("Failed to get a PostgreSQL connection")?;
|
||||
|
||||
let path = request.path().clone();
|
||||
|
||||
super::share::list_shares(&mut connection, request)
|
||||
.await
|
||||
.map(|shares| ListSharesResponse::new(warren, path, shares))
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn delete_warren_share(
|
||||
&self,
|
||||
request: DeleteShareRequest,
|
||||
) -> Result<DeleteShareResponse, DeleteShareError> {
|
||||
let mut connection = self
|
||||
.pool
|
||||
.acquire()
|
||||
.await
|
||||
.context("Failed to get a PostgreSQL connection")?;
|
||||
|
||||
super::share::delete_share(&mut connection, request)
|
||||
.await
|
||||
.map(DeleteShareResponse::new)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn verify_warren_share_password(
|
||||
&self,
|
||||
request: VerifySharePasswordRequest,
|
||||
) -> Result<VerifySharePasswordResponse, VerifySharePasswordError> {
|
||||
let mut connection = self
|
||||
.pool
|
||||
.acquire()
|
||||
.await
|
||||
.context("Failed to get a PostgreSQL connection")?;
|
||||
|
||||
super::share::verify_password(&mut connection, request)
|
||||
.await
|
||||
.map(VerifySharePasswordResponse::new)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl Postgres {
|
||||
|
||||
Reference in New Issue
Block a user