api/routes/modules/assignments/interpreter/
get.rs1use crate::response::ApiResponse;
2use axum::{
3 extract::{Path, State},
4 http::{HeaderMap, HeaderValue, StatusCode, header},
5 response::{IntoResponse, Json, Response},
6};
7use db::models::assignment_interpreter::{
8 Column as InterpreterColumn, Entity as InterpreterEntity,
9};
10use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
11use serde::Serialize;
12use std::{env, path::PathBuf};
13use tokio::{fs::File as FsFile, io::AsyncReadExt};
14use util::state::AppState;
15
16pub async fn download_interpreter(
30 State(app_state): State<AppState>,
31 Path((_module_id, assignment_id)): Path<(i64, i64)>,
32) -> Response {
33 let db = app_state.db();
34
35 let interpreter = match InterpreterEntity::find()
37 .filter(InterpreterColumn::AssignmentId.eq(assignment_id))
38 .one(db)
39 .await
40 {
41 Ok(Some(interpreter)) => interpreter,
42 Ok(None) => {
43 return (
44 StatusCode::NOT_FOUND,
45 Json(ApiResponse::<()>::error("Interpreter not found")),
46 )
47 .into_response();
48 }
49 Err(err) => {
50 eprintln!("DB error fetching interpreter: {:?}", err);
51 return (
52 StatusCode::INTERNAL_SERVER_ERROR,
53 Json(ApiResponse::<()>::error("Database error")),
54 )
55 .into_response();
56 }
57 };
58
59 let storage_root =
60 env::var("ASSIGNMENT_STORAGE_ROOT").unwrap_or_else(|_| "data/interpreters".to_string());
61 let fs_path = PathBuf::from(storage_root).join(&interpreter.path);
62
63 if tokio::fs::metadata(&fs_path).await.is_err() {
64 return (
65 StatusCode::NOT_FOUND,
66 Json(ApiResponse::<()>::error("Interpreter file missing on disk")),
67 )
68 .into_response();
69 }
70
71 let mut file_handle = match FsFile::open(&fs_path).await {
72 Ok(f) => f,
73 Err(err) => {
74 eprintln!("File open error: {:?}", err);
75 return (
76 StatusCode::INTERNAL_SERVER_ERROR,
77 Json(ApiResponse::<()>::error("Could not open interpreter file")),
78 )
79 .into_response();
80 }
81 };
82
83 let mut buffer = Vec::new();
84 if let Err(err) = file_handle.read_to_end(&mut buffer).await {
85 eprintln!("File read error: {:?}", err);
86 return (
87 StatusCode::INTERNAL_SERVER_ERROR,
88 Json(ApiResponse::<()>::error("Failed to read interpreter file")),
89 )
90 .into_response();
91 }
92
93 let mut headers = HeaderMap::new();
94 headers.insert(
95 header::CONTENT_DISPOSITION,
96 HeaderValue::from_str(&format!(
97 "attachment; filename=\"{}\"",
98 interpreter.filename
99 ))
100 .unwrap_or_else(|_| HeaderValue::from_static("attachment")),
101 );
102 headers.insert(
103 header::CONTENT_TYPE,
104 HeaderValue::from_static("application/octet-stream"),
105 );
106
107 (StatusCode::OK, headers, buffer).into_response()
108}
109
110
111#[derive(Debug, Serialize)]
112pub struct InterpreterInfo {
113 pub id: i64,
114 pub assignment_id: i64,
115 pub filename: String,
116 pub path: String,
117 pub command: String,
118 pub created_at: String,
119 pub updated_at: String,
120}
121
122pub async fn get_interpreter_info(
129 State(app_state): State<AppState>,
130 Path((_module_id, assignment_id)): Path<(i64, i64)>,
131) -> Response {
132 let db = app_state.db();
133
134 let interpreter = match InterpreterEntity::find()
135 .filter(InterpreterColumn::AssignmentId.eq(assignment_id))
136 .one(db)
137 .await
138 {
139 Ok(Some(m)) => m,
140 Ok(None) => {
141 return (
142 StatusCode::NOT_FOUND,
143 Json(ApiResponse::<()>::error("Interpreter not found")),
144 )
145 .into_response();
146 }
147 Err(err) => {
148 eprintln!("DB error fetching interpreter info: {:?}", err);
149 return (
150 StatusCode::INTERNAL_SERVER_ERROR,
151 Json(ApiResponse::<()>::error("Database error")),
152 )
153 .into_response();
154 }
155 };
156
157 let payload = InterpreterInfo {
158 id: interpreter.id,
159 assignment_id: interpreter.assignment_id,
160 filename: interpreter.filename,
161 path: interpreter.path,
162 command: interpreter.command,
163 created_at: interpreter.created_at.to_rfc3339(),
164 updated_at: interpreter.updated_at.to_rfc3339(),
165 };
166
167 (
168 StatusCode::OK,
169 Json(ApiResponse::<InterpreterInfo>::success(payload, "OK")),
170 )
171 .into_response()
172}