Files
upl/src/model.rs
T
ddidderr 5ca52b5780 feat: assemble completed uploads
Implement POST /api/uploads/{id}/complete. The storage layer now reloads upload
metadata, verifies that every expected chunk exists with the exact expected
length, concatenates chunks in order into a temporary final file, flushes it,
and renames it into data/complete only after assembly succeeds.

The endpoint preserves staging data after completion, rejects incomplete uploads
with a conflict response, and refuses to overwrite an existing completed file.
This keeps failed or duplicate completion attempts explicit rather than silently
clobbering local files.

Extend the model, router, documentation, and test checklist for completion
responses and add integration coverage for successful assembly, incomplete
uploads, staging preservation, and duplicate completion conflicts.

Test Plan:
- just check

Refs: PLAN.md milestone 8
2026-05-30 17:02:59 +02:00

81 lines
2.0 KiB
Rust

use serde::{Deserialize, Serialize};
pub const CHUNK_SIZE: u64 = 16 * 1024 * 1024;
#[derive(Debug, Deserialize)]
pub struct CreateUploadRequest {
pub name: String,
pub size: u64,
pub last_modified: i64,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct CreateUploadResponse {
pub upload_id: String,
pub chunk_size: u64,
pub total_chunks: u64,
pub completed_chunks: Vec<u64>,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct UploadProgressResponse {
pub upload_id: String,
pub name: String,
pub size: u64,
pub chunk_size: u64,
pub total_chunks: u64,
pub completed_chunks: Vec<u64>,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct CompleteUploadResponse {
pub upload_id: String,
pub name: String,
pub file_path: String,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct UploadMeta {
pub id: String,
pub original_name: String,
pub safe_name: String,
pub size: u64,
pub last_modified: i64,
pub chunk_size: u64,
pub total_chunks: u64,
pub created_at: String,
}
impl UploadMeta {
#[must_use]
pub fn create_response(&self) -> CreateUploadResponse {
CreateUploadResponse {
upload_id: self.id.clone(),
chunk_size: self.chunk_size,
total_chunks: self.total_chunks,
completed_chunks: Vec::new(),
}
}
#[must_use]
pub fn progress_response(&self, completed_chunks: Vec<u64>) -> UploadProgressResponse {
UploadProgressResponse {
upload_id: self.id.clone(),
name: self.original_name.clone(),
size: self.size,
chunk_size: self.chunk_size,
total_chunks: self.total_chunks,
completed_chunks,
}
}
#[must_use]
pub fn complete_response(&self, file_path: String) -> CompleteUploadResponse {
CompleteUploadResponse {
upload_id: self.id.clone(),
name: self.safe_name.clone(),
file_path,
}
}
}