tests
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
pub mod auth;
|
||||
pub mod config;
|
||||
pub mod db;
|
||||
pub mod error;
|
||||
pub mod models;
|
||||
pub mod routes;
|
||||
pub mod s3;
|
||||
pub mod schema;
|
||||
pub mod state;
|
||||
pub mod storage;
|
||||
+10
-15
@@ -1,23 +1,17 @@
|
||||
mod auth;
|
||||
mod config;
|
||||
mod db;
|
||||
mod error;
|
||||
mod models;
|
||||
mod routes;
|
||||
mod s3;
|
||||
mod schema;
|
||||
mod state;
|
||||
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
|
||||
use tokio::net::TcpListener;
|
||||
use tower::make::Shared;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
|
||||
use crate::auth::jwt::JwtService;
|
||||
use crate::config::AppConfig;
|
||||
use crate::s3::build_client;
|
||||
use crate::state::AppState;
|
||||
use paperless_backend::auth::jwt::JwtService;
|
||||
use paperless_backend::config::AppConfig;
|
||||
use paperless_backend::db;
|
||||
use paperless_backend::routes;
|
||||
use paperless_backend::s3::build_client;
|
||||
use paperless_backend::state::AppState;
|
||||
use paperless_backend::storage::S3Storage;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
@@ -27,9 +21,10 @@ async fn main() -> anyhow::Result<()> {
|
||||
let config = AppConfig::from_env()?;
|
||||
let pool = db::init_pool(&config.database_url)?;
|
||||
let s3_client = build_client(&config).await?;
|
||||
let storage = Arc::new(S3Storage::new(s3_client, config.s3_bucket.clone()));
|
||||
let jwt = JwtService::from_config(&config)?;
|
||||
|
||||
let state = AppState::new(pool, config, s3_client, jwt);
|
||||
let state = AppState::new(pool, config, storage, jwt);
|
||||
|
||||
let router = routes::create_router(state.clone());
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
use std::collections::HashMap;
|
||||
use std::time::Duration;
|
||||
|
||||
use aws_sdk_s3::presigning::PresigningConfig;
|
||||
use aws_sdk_s3::primitives::ByteStream;
|
||||
use axum::extract::{Json, Multipart, Path, Query, State};
|
||||
use axum::http::StatusCode;
|
||||
use axum::response::IntoResponse;
|
||||
@@ -267,21 +265,11 @@ pub async fn upload_document(
|
||||
}
|
||||
}
|
||||
|
||||
let mut put_request = state
|
||||
.s3
|
||||
.put_object()
|
||||
.bucket(&state.config.s3_bucket)
|
||||
.key(&s3_key)
|
||||
.body(ByteStream::from(file_bytes.clone()));
|
||||
|
||||
if let Some(ref ct) = content_type {
|
||||
put_request = put_request.content_type(ct.clone());
|
||||
}
|
||||
|
||||
put_request
|
||||
.send()
|
||||
state
|
||||
.storage
|
||||
.put_object(&s3_key, file_bytes.clone(), content_type.clone())
|
||||
.await
|
||||
.map_err(|err| AppError::internal(format!("failed to upload to s3: {err}")))?;
|
||||
.map_err(|err| AppError::internal(format!("failed to store document: {err}")))?;
|
||||
|
||||
let metadata_value = if metadata.is_null() {
|
||||
Value::Object(Default::default())
|
||||
@@ -349,22 +337,17 @@ pub async fn download_document(
|
||||
.filter(document_versions::version_number.eq(doc.current_version))
|
||||
.first(&mut conn)?;
|
||||
|
||||
let presign_config = PresigningConfig::builder()
|
||||
.expires_in(Duration::from_secs(PRESIGNED_URL_EXPIRY_SECONDS))
|
||||
.build()
|
||||
.map_err(|err| AppError::internal(format!("failed to build presigning config: {err}")))?;
|
||||
|
||||
let presigned = state
|
||||
.s3
|
||||
.get_object()
|
||||
.bucket(&state.config.s3_bucket)
|
||||
.key(&version.s3_key)
|
||||
.presigned(presign_config)
|
||||
let presigned_url = state
|
||||
.storage
|
||||
.presign_get_object(
|
||||
&version.s3_key,
|
||||
Duration::from_secs(PRESIGNED_URL_EXPIRY_SECONDS),
|
||||
)
|
||||
.await
|
||||
.map_err(|err| AppError::internal(format!("failed to generate download URL: {err}")))?;
|
||||
|
||||
Ok(Json(DocumentDownloadResponse {
|
||||
url: presigned.uri().to_string(),
|
||||
url: presigned_url,
|
||||
expires_in: PRESIGNED_URL_EXPIRY_SECONDS,
|
||||
filename: doc.original_name.clone(),
|
||||
content_type: doc.content_type.clone(),
|
||||
|
||||
@@ -16,8 +16,6 @@ diesel::table! {
|
||||
version_number -> Int4,
|
||||
#[max_length = 500]
|
||||
s3_key -> Varchar,
|
||||
#[max_length = 100]
|
||||
s3_bucket -> Varchar,
|
||||
size_bytes -> Int8,
|
||||
#[max_length = 64]
|
||||
checksum -> Varchar,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use aws_sdk_s3::Client as S3Client;
|
||||
use diesel::{
|
||||
pg::PgConnection,
|
||||
r2d2::{ConnectionManager, PooledConnection},
|
||||
@@ -11,6 +10,7 @@ use crate::{
|
||||
config::AppConfig,
|
||||
db::PgPool,
|
||||
error::{AppError, AppResult},
|
||||
storage::ObjectStorage,
|
||||
};
|
||||
|
||||
type PgPooledConnection = PooledConnection<ConnectionManager<PgConnection>>;
|
||||
@@ -19,16 +19,21 @@ type PgPooledConnection = PooledConnection<ConnectionManager<PgConnection>>;
|
||||
pub struct AppState {
|
||||
pub pool: PgPool,
|
||||
pub config: Arc<AppConfig>,
|
||||
pub s3: S3Client,
|
||||
pub storage: Arc<dyn ObjectStorage>,
|
||||
pub jwt: JwtService,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
pub fn new(pool: PgPool, config: AppConfig, s3: S3Client, jwt: JwtService) -> Self {
|
||||
pub fn new(
|
||||
pool: PgPool,
|
||||
config: AppConfig,
|
||||
storage: Arc<dyn ObjectStorage>,
|
||||
jwt: JwtService,
|
||||
) -> Self {
|
||||
Self {
|
||||
pool,
|
||||
config: Arc::new(config),
|
||||
s3,
|
||||
storage,
|
||||
jwt,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use async_trait::async_trait;
|
||||
use aws_sdk_s3::presigning::PresigningConfig;
|
||||
use aws_sdk_s3::primitives::ByteStream;
|
||||
use aws_sdk_s3::Client as S3Client;
|
||||
|
||||
#[async_trait]
|
||||
pub trait ObjectStorage: Send + Sync + 'static {
|
||||
async fn put_object(
|
||||
&self,
|
||||
key: &str,
|
||||
bytes: Vec<u8>,
|
||||
content_type: Option<String>,
|
||||
) -> Result<()>;
|
||||
|
||||
async fn presign_get_object(&self, key: &str, expires_in: Duration) -> Result<String>;
|
||||
}
|
||||
|
||||
pub struct S3Storage {
|
||||
client: S3Client,
|
||||
bucket: String,
|
||||
}
|
||||
|
||||
impl S3Storage {
|
||||
pub fn new(client: S3Client, bucket: impl Into<String>) -> Self {
|
||||
Self {
|
||||
client,
|
||||
bucket: bucket.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ObjectStorage for S3Storage {
|
||||
async fn put_object(
|
||||
&self,
|
||||
key: &str,
|
||||
bytes: Vec<u8>,
|
||||
content_type: Option<String>,
|
||||
) -> Result<()> {
|
||||
let mut request = self
|
||||
.client
|
||||
.put_object()
|
||||
.bucket(&self.bucket)
|
||||
.key(key)
|
||||
.body(ByteStream::from(bytes));
|
||||
|
||||
if let Some(content_type) = content_type {
|
||||
request = request.content_type(content_type);
|
||||
}
|
||||
|
||||
request
|
||||
.send()
|
||||
.await
|
||||
.context("failed to upload object to S3")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn presign_get_object(&self, key: &str, expires_in: Duration) -> Result<String> {
|
||||
let presign_config = PresigningConfig::builder()
|
||||
.expires_in(expires_in)
|
||||
.build()
|
||||
.context("failed to build S3 presigning config")?;
|
||||
|
||||
let presigned = self
|
||||
.client
|
||||
.get_object()
|
||||
.bucket(&self.bucket)
|
||||
.key(key)
|
||||
.presigned(presign_config)
|
||||
.await
|
||||
.context("failed to generate presigned download URL")?;
|
||||
|
||||
Ok(presigned.uri().to_string())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user