use super::{write_file, Scaffold, ScaffoldArgs, ScaffoldResult};
use anyhow::Result;
pub struct UploadScaffold;
impl Scaffold for UploadScaffold {
fn name(&self) -> &'static str {
"upload"
}
fn description(&self) -> &'static str {
"File upload/download: multipart handling, rok-storage, type validation, thumbnails"
}
fn generate(&self, args: &ScaffoldArgs) -> Result<ScaffoldResult> {
let mut r = ScaffoldResult::default();
let d = args.dry_run;
write_file(
&mut r,
"src/app/controllers/upload_controller.rs",
UPLOAD_CONTROLLER,
d,
)?;
write_file(&mut r, "src/app/config/upload.rs", UPLOAD_CONFIG, d)?;
write_file(&mut r, "migrations/create_uploads_table.sql", MIGRATION, d)?;
r.warnings
.push("Add STORAGE_DRIVER to .env (local|s3|r2|gcs)".into());
r.warnings
.push("Register POST /uploads and GET /uploads/:id routes".into());
Ok(r)
}
}
const UPLOAD_CONTROLLER: &str = r#"use axum::{extract::{Multipart, Path, State}, response::IntoResponse};
use rok_auth::axum::{Ctx, Response};
use rok_storage::Storage;
pub async fn upload(ctx: Ctx, mut multipart: Multipart) -> impl IntoResponse {
while let Ok(Some(field)) = multipart.next_field().await {
let filename = field.file_name().unwrap_or("file").to_string();
let data = field.bytes().await.unwrap_or_default();
// TODO: validate file type + size, store via rok-storage, save record
return Response::created(serde_json::json!({ "filename": filename, "size": data.len() }));
}
Response::bad_request("No file uploaded")
}
pub async fn download(Path(id): Path<i64>) -> impl IntoResponse {
// TODO: fetch upload record, stream file from storage with Content-Disposition
Response::not_found("Upload not found")
}
pub async fn delete(ctx: Ctx, Path(id): Path<i64>) -> impl IntoResponse {
// TODO: delete from storage + DB
Response::no_content()
}
"#;
const UPLOAD_CONFIG: &str = r#"pub struct UploadConfig {
pub max_size_bytes: u64,
pub allowed_types: Vec<&'static str>,
pub generate_thumbnail: bool,
}
impl Default for UploadConfig {
fn default() -> Self {
Self {
max_size_bytes: 10 * 1024 * 1024, // 10 MB
allowed_types: vec!["image/jpeg", "image/png", "image/webp", "application/pdf"],
generate_thumbnail: true,
}
}
}
"#;
const MIGRATION: &str = r#"CREATE TABLE uploads (
id BIGSERIAL PRIMARY KEY,
filename TEXT NOT NULL,
content_type TEXT NOT NULL,
size_bytes BIGINT NOT NULL,
storage_key TEXT NOT NULL,
uploader_id BIGINT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
"#;