api/routes/modules/assignments/interpreter/
post.rs1use crate::response::ApiResponse;
2use axum::{
3 Json,
4 extract::{Multipart, Path, State},
5 http::StatusCode,
6 response::IntoResponse,
7};
8use db::models::assignment_interpreter::Model as InterpreterModel;
9use serde::Serialize;
10use util::state::AppState;
11
12#[derive(Debug, Serialize)]
13pub struct UploadedInterpreterMetadata {
14 pub id: i64,
15 pub assignment_id: i64,
16 pub filename: String,
17 pub path: String,
18 pub command: String,
19 pub created_at: String,
20 pub updated_at: String,
21}
22
23pub async fn upload_interpreter(
42 State(app_state): State<AppState>,
43 Path((module_id, assignment_id)): Path<(i64, i64)>,
44 mut multipart: Multipart,
45) -> impl IntoResponse {
46 let db = app_state.db();
47
48 let mut command: Option<String> = None;
49 let mut file_name: Option<String> = None;
50 let mut file_bytes: Option<Vec<u8>> = None;
51 let mut file_count = 0;
52
53 while let Some(field) = multipart.next_field().await.unwrap_or(None) {
54 let name = field.name().unwrap_or("");
55
56 match name {
57 "command" => {
58 if let Ok(cmd) = field.text().await {
59 command = Some(cmd);
60 }
61 }
62 "file" => {
63 if file_count > 0 {
64 return (
65 StatusCode::BAD_REQUEST,
66 Json(ApiResponse::<UploadedInterpreterMetadata>::error(
67 "Only one file may be uploaded per request",
68 )),
69 )
70 .into_response();
71 }
72 file_name = field.file_name().map(|s| s.to_string());
73 file_bytes = Some(field.bytes().await.unwrap_or_default().to_vec());
74 file_count += 1;
75 }
76 _ => continue,
77 }
78 }
79
80 let command = match command {
81 Some(c) if !c.trim().is_empty() => c,
82 _ => {
83 return (
84 StatusCode::BAD_REQUEST,
85 Json(ApiResponse::<UploadedInterpreterMetadata>::error(
86 "Missing required field: command",
87 )),
88 )
89 .into_response();
90 }
91 };
92
93 let file_name = match file_name {
94 Some(name) => name,
95 None => {
96 return (
97 StatusCode::BAD_REQUEST,
98 Json(ApiResponse::<UploadedInterpreterMetadata>::error(
99 "Missing file upload",
100 )),
101 )
102 .into_response();
103 }
104 };
105
106 let file_bytes = match file_bytes {
107 Some(bytes) if !bytes.is_empty() => bytes,
108 _ => {
109 return (
110 StatusCode::BAD_REQUEST,
111 Json(ApiResponse::<UploadedInterpreterMetadata>::error(
112 "Empty file provided",
113 )),
114 )
115 .into_response();
116 }
117 };
118
119 match InterpreterModel::save_file(
120 db,
121 assignment_id,
122 module_id,
123 &file_name,
124 &command,
125 &file_bytes,
126 )
127 .await
128 {
129 Ok(saved) => {
130 let metadata = UploadedInterpreterMetadata {
131 id: saved.id,
132 assignment_id: saved.assignment_id,
133 filename: saved.filename,
134 path: saved.path,
135 command: saved.command,
136 created_at: saved.created_at.to_rfc3339(),
137 updated_at: saved.updated_at.to_rfc3339(),
138 };
139 (
140 StatusCode::CREATED,
141 Json(ApiResponse::success(
142 metadata,
143 "Interpreter uploaded successfully",
144 )),
145 )
146 .into_response()
147 }
148 Err(e) => {
149 eprintln!("Interpreter save error: {:?}", e);
150 return (
151 StatusCode::INTERNAL_SERVER_ERROR,
152 Json(ApiResponse::<UploadedInterpreterMetadata>::error(
153 "Failed to save interpreter",
154 )),
155 )
156 .into_response();
157 }
158 }
159}