api/routes/modules/assignments/files/
get.rs1use std::{env, path::PathBuf};
2use axum::{
3 extract::{State, Path},
4 http::{header, HeaderMap, HeaderValue, StatusCode},
5 response::{IntoResponse, Json, Response},
6};
7use tokio::{fs::File as FsFile, io::AsyncReadExt};
8use sea_orm::{
9 ColumnTrait,
10 EntityTrait,
11 QueryFilter,
12};
13use util::state::AppState;
14use crate::response::ApiResponse;
15use db::models::assignment_file::{
16 Column as FileColumn,
17 Entity as FileEntity,
18};
19use crate::routes::modules::assignments::common::File;
20
21pub async fn download_file(
50 State(app_state): State<AppState>,
51 Path((_module_id, assignment_id, file_id)): Path<(i64, i64, i64)>,
52) -> Response {
53 let db = app_state.db();
54
55 let file = FileEntity::find()
56 .filter(FileColumn::Id.eq(file_id as i32))
57 .filter(FileColumn::AssignmentId.eq(assignment_id as i32))
58 .one(db)
59 .await.unwrap().unwrap();
60
61 let storage_root = env::var("ASSIGNMENT_STORAGE_ROOT").unwrap_or_else(|_| "data/assignment_files".to_string());
62 let fs_path = PathBuf::from(storage_root).join(&file.path);
63
64 if tokio::fs::metadata(&fs_path).await.is_err() {
65 return (
66 StatusCode::NOT_FOUND,
67 Json(ApiResponse::<()>::error("File missing on disk")),
68 )
69 .into_response();
70 }
71
72 let mut file_handle = match FsFile::open(&fs_path).await {
73 Ok(f) => f,
74 Err(err) => {
75 eprintln!("File open error: {:?}", err);
76 return (
77 StatusCode::INTERNAL_SERVER_ERROR,
78 Json(ApiResponse::<()>::error("Could not open file")),
79 )
80 .into_response();
81 }
82 };
83
84 let mut buffer = Vec::new();
85 if let Err(err) = file_handle.read_to_end(&mut buffer).await {
86 eprintln!("File read error: {:?}", err);
87 return (
88 StatusCode::INTERNAL_SERVER_ERROR,
89 Json(ApiResponse::<()>::error("Failed to read file")),
90 )
91 .into_response();
92 }
93
94 let mut headers = HeaderMap::new();
95 headers.insert(
96 header::CONTENT_DISPOSITION,
97 HeaderValue::from_str(&format!("attachment; filename=\"{}\"", file.filename))
98 .unwrap_or_else(|_| HeaderValue::from_static("attachment")),
99 );
100 headers.insert(
101 header::CONTENT_TYPE,
102 HeaderValue::from_static("application/octet-stream"),
103 );
104
105 (StatusCode::OK, headers, buffer).into_response()
106}
107
108pub async fn list_files(
152 State(app_state): State<AppState>,
153 Path((_, assignment_id)): Path<(i64, i64)>
154) -> Response {
155 let db = app_state.db();
156
157 match FileEntity::find()
158 .filter(FileColumn::AssignmentId.eq(assignment_id as i32))
159 .all(db)
160 .await
161 {
162 Ok(files) => {
163 let file_list: Vec<File> = files
164 .into_iter()
165 .map(|f| File {
166 id: f.id.to_string(),
167 filename: f.filename,
168 path: f.path,
169 file_type: f.file_type.to_string(),
170 created_at: f.created_at.to_rfc3339(),
171 updated_at: f.updated_at.to_rfc3339(),
172 })
173 .collect();
174
175 (
176 StatusCode::OK,
177 Json(ApiResponse::success(
178 file_list,
179 "Assignment files retrieved successfully",
180 )),
181 )
182 .into_response()
183 }
184 Err(err) => {
185 eprintln!("DB error fetching files: {:?}", err);
186 (
187 StatusCode::INTERNAL_SERVER_ERROR,
188 Json(ApiResponse::<Vec<File>>::error("Failed to retrieve files")),
189 )
190 .into_response()
191 }
192 }
193}