s3 support commit1

This commit is contained in:
Mateusz Gruszczyński
2026-07-23 19:40:30 +02:00
parent 8c3842157d
commit 950142a9c0
10 changed files with 282 additions and 33 deletions
+18 -26
View File
@@ -616,20 +616,19 @@ pub async fn upload_pad_file(
let (original, bytes) = file.ok_or_else(|| ApiError::bad_request("No file provided"))?;
let safe = sanitize_filename(&original);
let file_token = db::pad_file_token(&state.db, pad.id).await?;
let directory = format!("{}_{}", pad.id, file_token);
let dir = std::path::Path::new(&state.files_dir).join("pads").join(&directory);
tokio::fs::create_dir_all(&dir).await.map_err(|_| ApiError::internal("Failed to create the files directory"))?;
let mut stored = safe.clone();
let mut path = dir.join(&stored);
if path.exists() {
let mut key = crate::storage::object_key("pads", pad.id, &file_token, &stored);
if state.storage.exists(&key).await.map_err(|_| ApiError::internal("Failed to check file storage"))? {
let stem = std::path::Path::new(&safe).file_stem().and_then(|v| v.to_str()).unwrap_or("plik");
let ext = std::path::Path::new(&safe).extension().and_then(|v| v.to_str()).map(|v| format!(".{v}")).unwrap_or_default();
stored = format!("{stem}-{}{}", db::random_suffix(6), ext);
path = dir.join(&stored);
key = crate::storage::object_key("pads", pad.id, &file_token, &stored);
}
tokio::fs::write(&path, &bytes).await.map_err(|_| ApiError::internal("Failed to save the file"))?;
let url = format!("/f/{}/{}", file_token, stored);
let mime = mime_guess::from_path(&stored).first_or_octet_stream().to_string();
let cache_control = format!("public, max-age={}", state.file_cache_max_age_seconds);
state.storage.put(&key, bytes.clone().into(), &mime, &cache_control).await
.map_err(|_| ApiError::internal("Failed to save the file"))?;
db::register_pad_file(&state.db, pad.id, &stored, &url, &mime, bytes.len() as i64).await?;
Ok(Json(serde_json::json!({"name": stored, "url": url})))
}
@@ -680,20 +679,19 @@ pub async fn upload_note_file(
let (original, bytes) = file.ok_or_else(|| ApiError::bad_request("No file provided"))?;
let safe = sanitize_filename(&original);
let file_token = db::note_file_token(&state.db, note.id).await?;
let directory = format!("{}_{}", note.id, file_token);
let dir = std::path::Path::new(&state.files_dir).join("notes").join(&directory);
tokio::fs::create_dir_all(&dir).await.map_err(|_| ApiError::internal("Failed to create the files directory"))?;
let mut stored = safe.clone();
let mut path = dir.join(&stored);
if path.exists() {
let mut key = crate::storage::object_key("notes", note.id, &file_token, &stored);
if state.storage.exists(&key).await.map_err(|_| ApiError::internal("Failed to check file storage"))? {
let stem = std::path::Path::new(&safe).file_stem().and_then(|v| v.to_str()).unwrap_or("plik");
let ext = std::path::Path::new(&safe).extension().and_then(|v| v.to_str()).map(|v| format!(".{v}")).unwrap_or_default();
stored = format!("{stem}-{}{}", db::random_suffix(6), ext);
path = dir.join(&stored);
key = crate::storage::object_key("notes", note.id, &file_token, &stored);
}
tokio::fs::write(&path, &bytes).await.map_err(|_| ApiError::internal("Failed to save the file"))?;
let url = format!("/f/{}/{}", file_token, stored);
let mime = mime_guess::from_path(&stored).first_or_octet_stream().to_string();
let cache_control = format!("public, max-age={}", state.file_cache_max_age_seconds);
state.storage.put(&key, bytes.clone().into(), &mime, &cache_control).await
.map_err(|_| ApiError::internal("Failed to save the file"))?;
db::register_note_file(&state.db, note.id, &stored, &url, &mime, bytes.len() as i64).await?;
Ok(Json(serde_json::json!({"name": stored, "url": url})))
}
@@ -742,13 +740,8 @@ pub async fn delete_note_file(
.ok_or_else(ApiError::not_found_file)?;
let relative = file.url.trim_start_matches('/').split('/').collect::<Vec<_>>();
if relative.len() == 3 && relative[0] == "f" {
let directory = format!("{}_{}", note.id, relative[1]);
let path = std::path::Path::new(&state.files_dir).join("notes").join(directory).join(sanitize_filename(relative[2]));
match tokio::fs::remove_file(&path).await {
Ok(()) => {},
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {},
Err(_) => return Err(ApiError::internal("Failed to delete the file")),
}
let key = crate::storage::object_key("notes", note.id, relative[1], &sanitize_filename(relative[2]));
state.storage.delete(&key).await.map_err(|_| ApiError::internal("Failed to delete the file"))?;
}
db::delete_note_file(&state.db, note.id, file_id).await?;
Ok(Json(serde_json::json!({"ok": true})))
@@ -788,11 +781,10 @@ async fn serve_token_file(state: &SharedState, token: &str, filename: &str) -> R
db::FileOwnerKind::Pad => "pads",
db::FileOwnerKind::Note => "notes",
};
let directory = format!("{}_{}", owner.id, token);
let canonical = std::path::Path::new(&state.files_dir).join(kind).join(&directory).join(&safe);
let legacy = std::path::Path::new(&state.files_dir).join(&directory).join(&safe);
let path = if canonical.is_file() { canonical } else { legacy };
let bytes = tokio::fs::read(&path).await.map_err(|_| ApiError::not_found_file())?;
let key = crate::storage::object_key(kind, owner.id, token, &safe);
let legacy_key = crate::storage::legacy_key(owner.id, token, &safe);
let bytes = state.storage.get_local_with_legacy(&key, &legacy_key).await
.map_err(|_| ApiError::not_found_file())?;
let mime = mime_guess::from_path(&safe).first_or_octet_stream();
let mut response = bytes.into_response();
response.headers_mut().insert(