Add (untested) get_url_for_private_file

This commit is contained in:
Josiah Glosson 2025-06-11 12:41:57 -05:00
parent 3e6a05cd04
commit 9e1f613aae
5 changed files with 50 additions and 25 deletions

View File

@ -9,7 +9,7 @@ use ariadne::ids::DecodingError;
#[error("{}", .error_type)] #[error("{}", .error_type)]
pub struct OAuthError { pub struct OAuthError {
#[source] #[source]
pub error_type: OAuthErrorType, pub error_type: Box<OAuthErrorType>,
pub state: Option<String>, pub state: Option<String>,
pub valid_redirect_uri: Option<ValidatedRedirectUri>, pub valid_redirect_uri: Option<ValidatedRedirectUri>,
@ -32,7 +32,7 @@ impl OAuthError {
/// See: IETF RFC 6749 4.1.2.1 (https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1) /// See: IETF RFC 6749 4.1.2.1 (https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1)
pub fn error(error_type: impl Into<OAuthErrorType>) -> Self { pub fn error(error_type: impl Into<OAuthErrorType>) -> Self {
Self { Self {
error_type: error_type.into(), error_type: Box::new(error_type.into()),
valid_redirect_uri: None, valid_redirect_uri: None,
state: None, state: None,
} }
@ -48,7 +48,7 @@ impl OAuthError {
valid_redirect_uri: &ValidatedRedirectUri, valid_redirect_uri: &ValidatedRedirectUri,
) -> Self { ) -> Self {
Self { Self {
error_type: err.into(), error_type: Box::new(err.into()),
state: state.clone(), state: state.clone(),
valid_redirect_uri: Some(valid_redirect_uri.clone()), valid_redirect_uri: Some(valid_redirect_uri.clone()),
} }
@ -57,7 +57,7 @@ impl OAuthError {
impl actix_web::ResponseError for OAuthError { impl actix_web::ResponseError for OAuthError {
fn status_code(&self) -> StatusCode { fn status_code(&self) -> StatusCode {
match self.error_type { match *self.error_type {
OAuthErrorType::AuthenticationError(_) OAuthErrorType::AuthenticationError(_)
| OAuthErrorType::FailedScopeParse(_) | OAuthErrorType::FailedScopeParse(_)
| OAuthErrorType::ScopesTooBroad | OAuthErrorType::ScopesTooBroad

View File

@ -101,7 +101,7 @@ mod tests {
); );
assert!(validated.is_err_and(|e| matches!( assert!(validated.is_err_and(|e| matches!(
e.error_type, *e.error_type,
OAuthErrorType::RedirectUriNotConfigured(_) OAuthErrorType::RedirectUriNotConfigured(_)
))); )));
} }

View File

@ -8,6 +8,7 @@ use chrono::Utc;
use hex::ToHex; use hex::ToHex;
use sha2::Digest; use sha2::Digest;
use std::path::PathBuf; use std::path::PathBuf;
use std::time::Duration;
#[derive(Default)] #[derive(Default)]
pub struct MockHost(()); pub struct MockHost(());
@ -47,6 +48,15 @@ impl FileHost for MockHost {
}) })
} }
async fn get_url_for_private_file(
&self,
file_name: &str,
_expiry: Duration,
) -> Result<String, FileHostingError> {
let cdn_url = dotenvy::var("CDN_URL").unwrap();
Ok(format!("{cdn_url}/private/{file_name}"))
}
async fn delete_file( async fn delete_file(
&self, &self,
file_name: &str, file_name: &str,

View File

@ -1,4 +1,5 @@
use async_trait::async_trait; use async_trait::async_trait;
use std::time::Duration;
use thiserror::Error; use thiserror::Error;
mod mock; mod mock;
@ -10,8 +11,8 @@ pub use s3_host::S3Host;
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum FileHostingError { pub enum FileHostingError {
#[error("S3 error: {0}")] #[error("S3 error when {0}: {1}")]
S3Error(String), S3Error(&'static str, s3::error::S3Error),
#[error("File system error in file hosting: {0}")] #[error("File system error in file hosting: {0}")]
FileSystemError(#[from] std::io::Error), FileSystemError(#[from] std::io::Error),
#[error("Invalid Filename")] #[error("Invalid Filename")]
@ -51,6 +52,12 @@ pub trait FileHost {
file_bytes: Bytes, file_bytes: Bytes,
) -> Result<UploadFileData, FileHostingError>; ) -> Result<UploadFileData, FileHostingError>;
async fn get_url_for_private_file(
&self,
file_name: &str,
expiry: Duration,
) -> Result<String, FileHostingError>;
async fn delete_file( async fn delete_file(
&self, &self,
file_name: &str, file_name: &str,

View File

@ -10,6 +10,7 @@ use s3::bucket::Bucket;
use s3::creds::Credentials; use s3::creds::Credentials;
use s3::region::Region; use s3::region::Region;
use sha2::Digest; use sha2::Digest;
use std::time::Duration;
pub struct S3Host { pub struct S3Host {
public_bucket: Bucket, public_bucket: Bucket,
@ -46,16 +47,12 @@ impl S3Host {
None, None,
None, None,
) )
.map_err(|_| { .map_err(|e| {
FileHostingError::S3Error( FileHostingError::S3Error("creating credentials", e.into())
"Error while creating credentials".to_string(),
)
})?, })?,
) )
.map_err(|_| { .map_err(|e| {
FileHostingError::S3Error( FileHostingError::S3Error("creating Bucket instance", e)
"Error while creating Bucket instance".to_string(),
)
})?; })?;
if bucket_uses_path_style { if bucket_uses_path_style {
@ -100,11 +97,7 @@ impl FileHost for S3Host {
content_type, content_type,
) )
.await .await
.map_err(|err| { .map_err(|e| FileHostingError::S3Error("uploading file", e))?;
FileHostingError::S3Error(format!(
"Error while uploading file {file_name} to S3: {err}"
))
})?;
Ok(UploadFileData { Ok(UploadFileData {
file_name: file_name.to_string(), file_name: file_name.to_string(),
@ -118,6 +111,25 @@ impl FileHost for S3Host {
}) })
} }
async fn get_url_for_private_file(
&self,
file_name: &str,
expiry: Duration,
) -> Result<String, FileHostingError> {
let url = self
.private_bucket
.presign_get(
format!("/{file_name}"),
expiry.as_secs().try_into().unwrap(),
None,
)
.await
.map_err(|e| {
FileHostingError::S3Error("generating presigned URL", e)
})?;
Ok(url)
}
async fn delete_file( async fn delete_file(
&self, &self,
file_name: &str, file_name: &str,
@ -126,11 +138,7 @@ impl FileHost for S3Host {
self.get_bucket(file_publicity) self.get_bucket(file_publicity)
.delete_object(format!("/{file_name}")) .delete_object(format!("/{file_name}"))
.await .await
.map_err(|err| { .map_err(|e| FileHostingError::S3Error("deleting file", e))?;
FileHostingError::S3Error(format!(
"Error while deleting file {file_name} to S3: {err}"
))
})?;
Ok(DeleteFileData { Ok(DeleteFileData {
file_name: file_name.to_string(), file_name: file_name.to_string(),