Skip to content

Commit 068ed93

Browse files
committed
feat: update to latest changes from docbox
1 parent bc97041 commit 068ed93

25 files changed

Lines changed: 900 additions & 621 deletions

Cargo.lock

Lines changed: 268 additions & 212 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,23 @@ members = [
77
]
88

99
[workspace.dependencies]
10-
docbox-core = "0.7.0"
11-
docbox-database = "0.6.0"
12-
docbox-processing = "0.2.0"
13-
docbox-search = "0.6.0"
14-
docbox-secrets = "0.3.0"
15-
docbox-storage = "0.4.0"
16-
docbox-web-scraper = "0.4.0"
10+
docbox-core = "0.8.0"
11+
docbox-database = "0.7.0"
12+
docbox-processing = "0.3.0"
13+
docbox-search = "0.7.0"
14+
docbox-secrets = "0.4.0"
15+
docbox-storage = "0.5.0"
16+
docbox-web-scraper = "0.5.0"
1717

1818
# Futures utilities
1919
futures = "=0.3.31"
2020

21-
2221
# Bytes type used for cheap shared bytes (and utilities)
23-
bytes = "=1.10.1"
22+
bytes = "=1.11.0"
2423
bytes-utils = "=0.1.4"
2524

2625
# Logging
27-
tracing = "=0.1.41"
26+
tracing = "=0.1.44"
2827

2928
# Error handling
3029
thiserror = "=2.0.17"
@@ -33,19 +32,18 @@ thiserror = "=2.0.17"
3332
chrono = { version = "=0.4.42", features = ["serde"] }
3433

3534
# AWS configuration
36-
aws-config = { version = "=1.8.10", features = ["behavior-version-latest"] }
35+
aws-config = { version = "=1.8.12", features = ["behavior-version-latest"] }
3736

3837
# URL parsing
3938
url = "=2.5.7"
4039

4140
# UUID v4 support
42-
uuid = { version = "=1.18.1", features = ["v4", "serde"] }
43-
41+
uuid = { version = "=1.19.0", features = ["v4", "serde"] }
4442

4543
# Serialization and JSON
4644
serde = { version = "=1.0.228", features = ["derive"] }
47-
serde_json = "=1.0.145"
48-
serde_with = "=3.15.0"
45+
serde_json = "=1.0.148"
46+
serde_with = "=3.16.1"
4947

5048
# Mime types, parsing, extension guessing, reverse mime lookup
5149
mime = "=0.3.17"
@@ -56,7 +54,7 @@ mime2ext = "=0.1.54"
5654
http = "=1.3.1"
5755

5856
# Validation
59-
garde = { version = "=0.22.0", features = ["derive", "full"] }
57+
garde = { version = "=0.22.1", features = ["derive", "full"] }
6058

6159
# OpenAPI spec generation
6260
utoipa = { version = "=5.4.0", features = [
@@ -66,4 +64,4 @@ utoipa = { version = "=5.4.0", features = [
6664
"axum_extras",
6765
] }
6866

69-
dotenvy = "0.15.7"
67+
dotenvy = "=0.15.7"

lambdas/http/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ lambda_http = "1.0.0"
99
tokio = { version = "1", features = ["macros"] }
1010

1111
# HTTP server framework
12-
axum = { version = "=0.8.6" }
12+
axum = { version = "=0.8.8" }
1313

1414
# HTTP layers for ratelimiting, CORS, and tracing
15-
tower-http = { version = "=0.6.6", features = ["limit", "cors", "trace"] }
15+
tower-http = { version = "=0.6.8", features = ["limit", "cors", "trace"] }
1616
tower = { version = "=0.5.2" }
1717

1818
# Validation & Axum validation integration

lambdas/http/src/background/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub mod purge_expired_presigned_tasks;
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
use chrono::Utc;
2+
use docbox_database::{
3+
DatabasePoolCache, DbPool, DbResult,
4+
models::{
5+
presigned_upload_task::{PresignedTaskStatus, PresignedUploadTask},
6+
tenant::Tenant,
7+
},
8+
};
9+
use docbox_storage::{StorageLayerFactory, TenantStorageLayer};
10+
use std::sync::Arc;
11+
use thiserror::Error;
12+
13+
#[derive(Debug, Error)]
14+
pub enum PurgeExpiredPresignedError {
15+
#[error("failed to connect to database")]
16+
ConnectDatabase,
17+
18+
#[error("failed to query available tenants")]
19+
QueryTenants,
20+
}
21+
22+
#[tracing::instrument(skip_all)]
23+
pub async fn purge_expired_presigned_tasks(
24+
db_cache: Arc<DatabasePoolCache>,
25+
storage: StorageLayerFactory,
26+
) -> Result<(), PurgeExpiredPresignedError> {
27+
let db = db_cache.get_root_pool().await.map_err(|error| {
28+
tracing::error!(?error, "failed to connect to root database");
29+
PurgeExpiredPresignedError::ConnectDatabase
30+
})?;
31+
32+
let tenants = Tenant::all(&db).await.map_err(|error| {
33+
tracing::error!(?error, "failed to query available tenants");
34+
PurgeExpiredPresignedError::QueryTenants
35+
})?;
36+
37+
// Early drop the root database pool access
38+
drop(db);
39+
40+
for tenant in tenants {
41+
// Create the database connection pool
42+
let db = db_cache.get_tenant_pool(&tenant).await.map_err(|error| {
43+
tracing::error!(?error, "failed to connect to tenant database");
44+
PurgeExpiredPresignedError::ConnectDatabase
45+
})?;
46+
47+
let storage = storage.create_storage_layer(&tenant);
48+
49+
if let Err(error) = purge_expired_presigned_tasks_tenant(&db, &storage).await {
50+
tracing::error!(
51+
?error,
52+
?tenant,
53+
"failed to purge presigned tasks for tenant"
54+
);
55+
}
56+
}
57+
58+
Ok(())
59+
}
60+
61+
pub async fn purge_expired_presigned_tasks_tenant(
62+
db: &DbPool,
63+
storage: &TenantStorageLayer,
64+
) -> DbResult<()> {
65+
let current_date = Utc::now();
66+
let tasks = PresignedUploadTask::find_expired(db, current_date).await?;
67+
if tasks.is_empty() {
68+
return Ok(());
69+
}
70+
71+
for task in tasks {
72+
// Delete the task itself
73+
if let Err(error) = PresignedUploadTask::delete(db, task.id).await {
74+
tracing::error!(?error, "failed to delete presigned upload task");
75+
}
76+
77+
// Delete incomplete file uploads
78+
match task.status {
79+
PresignedTaskStatus::Completed { .. } => {
80+
// Upload completed, nothing to revert
81+
}
82+
PresignedTaskStatus::Failed { .. } | PresignedTaskStatus::Pending => {
83+
if let Err(error) = storage.delete_file(&task.file_key).await {
84+
tracing::error!(
85+
?error,
86+
"failed to delete expired presigned task file from tenant"
87+
);
88+
}
89+
}
90+
}
91+
}
92+
93+
Ok(())
94+
}

lambdas/http/src/error.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ use std::{
99
fmt::{Debug, Display},
1010
};
1111
use thiserror::Error;
12-
use tracing::error;
1312
use utoipa::ToSchema;
1413

1514
/// Type alias for dynamic error handling and JSON responses

lambdas/http/src/main.rs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use axum::{Extension, Router};
55
use docbox_core::{
66
aws::{SqsClient, aws_config},
77
events::{EventPublisherFactory, sqs::SqsEventPublisherFactory},
8+
links::resolve_website::{ResolveWebsiteConfig, ResolveWebsiteService},
89
tenant::tenant_cache::TenantCache,
910
};
1011
use docbox_database::{DatabasePoolCache, DatabasePoolCacheConfig};
@@ -16,6 +17,7 @@ use lambda_http::{Error, run_with_streaming_response, tracing};
1617
use std::sync::Arc;
1718
use tower_http::trace::TraceLayer;
1819

20+
mod background;
1921
pub mod docs;
2022
mod error;
2123
mod extensions;
@@ -48,13 +50,18 @@ async fn app() -> Result<Router, Box<dyn std::error::Error + Send + Sync>> {
4850
Err(_) => 100 * 1000 * 1024,
4951
};
5052

53+
// Load AWS configuration
54+
let aws_config = aws_config().await;
55+
5156
// Create website scraping service
5257
let website_meta_service_config = WebsiteMetaServiceConfig::from_env()?;
53-
let website_meta_service = Arc::new(WebsiteMetaService::from_config(
54-
website_meta_service_config,
55-
)?);
58+
let website_meta_service = WebsiteMetaService::from_config(website_meta_service_config)?;
59+
let resolve_website_config = ResolveWebsiteConfig::from_env()?;
5660

57-
let aws_config = aws_config().await;
61+
let caching_website_meta_service = Arc::new(ResolveWebsiteService::from_client_with_config(
62+
website_meta_service,
63+
resolve_website_config,
64+
));
5865

5966
// Create secrets manager
6067
let secrets_config = SecretsManagerConfig::from_env()?;
@@ -96,8 +103,8 @@ async fn app() -> Result<Router, Box<dyn std::error::Error + Send + Sync>> {
96103
let mut app = router()
97104
.layer(Extension(search))
98105
.layer(Extension(storage))
99-
.layer(Extension(db_cache.clone()))
100-
.layer(Extension(website_meta_service))
106+
.layer(Extension(db_cache))
107+
.layer(Extension(caching_website_meta_service))
101108
.layer(Extension(events))
102109
.layer(Extension(tenant_cache))
103110
.layer(Extension(MaxFileSizeBytes(max_file_size_bytes)))

lambdas/http/src/models/admin.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ pub struct TenantDocumentBoxesRequest {
2121

2222
#[derive(Debug, Serialize, ToSchema)]
2323
pub struct TenantDocumentBoxesResponse {
24+
/// The document boxes
2425
pub results: Vec<DocumentBox>,
26+
/// The total number of document boxes available to query
2527
pub total: i64,
2628
}
2729

lambdas/http/src/models/document_box.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use utoipa::ToSchema;
1414
/// Valid document box scope string, must be: A-Z, a-z, 0-9, ':', '-', '_', '.'
1515
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, ToSchema, Serialize)]
1616
#[serde(transparent)]
17-
#[schema(example = "user:1:files", value_type = String)]
17+
#[schema(examples( "user:1:files"), value_type = String)]
1818
pub struct DocumentBoxScope(pub String);
1919

2020
impl Display for DocumentBoxScope {

lambdas/http/src/models/file.rs

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use garde::Validate;
1212
use mime::Mime;
1313
use serde::{Deserialize, Serialize};
1414
use serde_with::serde_as;
15-
use std::collections::HashMap;
15+
use std::{collections::HashMap, marker::PhantomData};
1616
use thiserror::Error;
1717
use utoipa::ToSchema;
1818

@@ -25,23 +25,25 @@ pub struct CreatePresignedRequest {
2525
#[schema(min_length = 1, max_length = 255)]
2626
pub name: String,
2727

28-
/// Folder to store the file in
28+
/// ID of the folder to store the file in
2929
#[garde(skip)]
3030
#[schema(value_type = Uuid)]
3131
pub folder_id: FolderId,
3232

33-
/// Size of the file being uploaded
33+
/// Size of the file being uploaded in bytes. Must match the size of the
34+
/// file being uploaded
3435
#[garde(range(min = 1))]
3536
#[schema(minimum = 1)]
3637
pub size: i32,
3738

3839
/// Mime type of the file
3940
#[garde(skip)]
40-
#[serde_as(as = "serde_with::DisplayFromStr")]
41-
#[schema(value_type = String)]
42-
pub mime: Mime,
41+
#[serde_as(as = "Option<serde_with::DisplayFromStr>")]
42+
#[schema(value_type = Option<String>)]
43+
pub mime: Option<Mime>,
4344

44-
/// Optional parent file ID
45+
/// Optional ID of the parent file if this file is associated as a child
46+
/// of another file. Mainly used to associating attachments to email files
4547
#[garde(skip)]
4648
#[schema(value_type = Option<Uuid>)]
4749
pub parent_id: Option<FileId>,
@@ -57,25 +59,37 @@ pub struct CreatePresignedRequest {
5759
pub disable_mime_sniffing: Option<bool>,
5860
}
5961

62+
/// Response describing how to upload the presigned file and the ID
63+
/// for polling the progress
6064
#[derive(Serialize, ToSchema)]
6165
pub struct PresignedUploadResponse {
66+
/// ID of the file upload task to poll
6267
#[schema(value_type = Uuid)]
6368
pub task_id: PresignedUploadTaskId,
69+
/// HTTP method to use when uploading the file
6470
pub method: String,
71+
/// URL to upload the file to
6572
pub uri: String,
73+
/// Headers to include on the file upload request
6674
pub headers: HashMap<String, String>,
6775
}
6876

6977
#[derive(Serialize, ToSchema)]
7078
#[serde(tag = "status")]
7179
#[allow(clippy::large_enum_variant)]
7280
pub enum PresignedStatusResponse {
81+
/// Presigned upload is currently pending
7382
Pending,
83+
/// Presigned upload is completed
7484
Complete {
85+
/// The uploaded file
7586
file: FileWithExtra,
87+
/// The generated file
7688
generated: Vec<GeneratedFile>,
7789
},
90+
/// Presigned upload failed
7891
Failed {
92+
/// The error that occurred
7993
error: String,
8094
},
8195
}
@@ -131,6 +145,11 @@ pub struct PresignedDownloadResponse {
131145
pub expires_at: DateTime<Utc>,
132146
}
133147

148+
/// Type hint type for Utoipa to indicate a binary response type
149+
#[derive(ToSchema)]
150+
#[schema(value_type = String, format = Binary)]
151+
pub struct BinaryResponse(PhantomData<Vec<u8>>);
152+
134153
#[derive(Debug, Error)]
135154
pub enum HttpFileError {
136155
#[error("unknown file")]
@@ -141,6 +160,7 @@ pub enum HttpFileError {
141160

142161
#[error("file size is larger than the maximum allowed size (requested: {0}, maximum: {1})")]
143162
FileTooLarge(i32, i32),
163+
144164
#[error("no matching generated file")]
145165
NoMatchingGenerated,
146166

0 commit comments

Comments
 (0)