db/models/
assignment_memo_output.rs1use chrono::{DateTime, Utc};
2use sea_orm::entity::prelude::*;
3use sea_orm::{ActiveValue::Set, DatabaseConnection, EntityTrait};
4use std::env;
5use std::fs;
6use std::path::PathBuf;
7
8#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
13#[sea_orm(table_name = "assignment_memo_outputs")]
14pub struct Model {
15 #[sea_orm(primary_key)]
17 pub id: i64,
18 pub assignment_id: i64,
20 pub task_id: i64,
22 pub path: String,
24 pub created_at: DateTime<Utc>,
26 pub updated_at: DateTime<Utc>,
28}
29
30#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
31pub enum Relation {
32 #[sea_orm(
34 belongs_to = "super::assignment::Entity",
35 from = "Column::AssignmentId",
36 to = "super::assignment::Column::Id"
37 )]
38 Assignment,
39
40 #[sea_orm(
42 belongs_to = "super::assignment_task::Entity",
43 from = "Column::TaskId",
44 to = "super::assignment_task::Column::Id"
45 )]
46 AssignmentTask,
47}
48
49impl ActiveModelBehavior for ActiveModel {}
50
51impl Model {
52 pub fn storage_root() -> PathBuf {
60 let relative_root = env::var("ASSIGNMENT_STORAGE_ROOT")
61 .unwrap_or_else(|_| "data/assignment_files".to_string());
62
63 let project_root = env::current_dir().expect("Failed to get current dir");
64
65 project_root.join(relative_root)
66 }
67
68 pub fn full_directory_path(module_id: i64, assignment_id: i64) -> PathBuf {
71 Self::storage_root()
72 .join(format!("module_{module_id}"))
73 .join(format!("assignment_{assignment_id}"))
74 .join(format!("memo_output"))
75 }
76
77 pub fn full_path(&self) -> PathBuf {
79 Self::storage_root().join(&self.path)
80 }
81
82 pub async fn save_file(
84 db: &DatabaseConnection,
85 assignment_id: i64,
86 task_id: i64,
87 filename: &str,
88 bytes: &[u8],
89 ) -> Result<Self, DbErr> {
90 let now = Utc::now();
91
92 let partial = ActiveModel {
93 assignment_id: Set(assignment_id),
94 task_id: Set(task_id),
95 path: Set("".to_string()),
96 created_at: Set(now),
97 updated_at: Set(now),
98 ..Default::default()
99 };
100
101 let inserted: Model = partial.insert(db).await?;
102
103 let ext = PathBuf::from(filename)
104 .extension()
105 .map(|e| e.to_string_lossy().to_string());
106
107 let stored_filename = match ext {
108 Some(ext) => format!("{}.{}", inserted.id, ext),
109 None => inserted.id.to_string(),
110 };
111
112 let assignment = super::assignment::Entity::find_by_id(assignment_id)
114 .one(db)
115 .await
116 .map_err(|e| DbErr::Custom(format!("DB error finding assignment: {}", e)))?
117 .ok_or_else(|| DbErr::Custom("Assignment not found".to_string()))?;
118
119 let module_id = assignment.module_id;
120
121 let dir_path = Self::full_directory_path(module_id, assignment_id);
122 fs::create_dir_all(&dir_path)
123 .map_err(|e| DbErr::Custom(format!("Failed to create directory: {e}")))?;
124
125 let file_path = dir_path.join(&stored_filename);
126 let relative_path = file_path
127 .strip_prefix(Self::storage_root())
128 .unwrap()
129 .to_string_lossy()
130 .to_string();
131
132 fs::write(&file_path, bytes)
133 .map_err(|e| DbErr::Custom(format!("Failed to write file: {e}")))?;
134
135 let mut model: ActiveModel = inserted.into();
136 model.path = Set(relative_path);
137 model.updated_at = Set(Utc::now());
138
139 model.update(db).await
140 }
141
142 pub fn read_memo_output_file(
145 module_id: i64,
146 assignment_id: i64,
147 file_id: i64,
148 ) -> Result<Vec<u8>, std::io::Error> {
149 let storage_root = Self::storage_root();
150
151 let dir_path = storage_root
152 .join(format!("module_{module_id}"))
153 .join(format!("assignment_{assignment_id}"))
154 .join("memo_output");
155
156 let file_path = dir_path.join(file_id.to_string());
157
158 std::fs::read(file_path)
159 }
160}