headless_lms_server/domain/csv_export/
submissions.rs1use anyhow::Result;
2use bytes::Bytes;
3
4use futures::TryStreamExt;
5use headless_lms_models::exercise_task_submissions;
6
7use async_trait::async_trait;
8
9use crate::domain::csv_export::CsvWriter;
10
11use sqlx::PgConnection;
12use std::io::Write;
13use tokio::sync::mpsc::UnboundedSender;
14
15use uuid::Uuid;
16
17use crate::prelude::*;
18
19use super::{
20 super::authorization::{AuthorizationToken, AuthorizedResponse},
21 CSVExportAdapter, CsvExportDataLoader,
22};
23
24pub struct ExamSubmissionExportOperation {
25 pub exam_id: Uuid,
26}
27
28#[async_trait]
29impl CsvExportDataLoader for ExamSubmissionExportOperation {
30 async fn load_data(
31 &self,
32 sender: UnboundedSender<Result<AuthorizedResponse<Bytes>, ControllerError>>,
33 conn: &mut PgConnection,
34 token: AuthorizationToken,
35 ) -> anyhow::Result<CSVExportAdapter> {
36 export_exam_submissions(
37 &mut *conn,
38 self.exam_id,
39 CSVExportAdapter {
40 sender,
41 authorization_token: token,
42 },
43 )
44 .await
45 }
46}
47
48pub async fn export_exam_submissions<W>(
50 conn: &mut PgConnection,
51 exam_id: Uuid,
52 writer: W,
53) -> Result<W>
54where
55 W: Write + Send + 'static,
56{
57 let headers = IntoIterator::into_iter([
58 "id".to_string(),
59 "user_id".to_string(),
60 "created_at".to_string(),
61 "exercise_id".to_string(),
62 "exercise_task_id".to_string(),
63 "score_given".to_string(),
64 "data_json".to_string(),
65 ]);
66
67 let mut stream = exercise_task_submissions::stream_exam_submissions(conn, exam_id);
68
69 let writer = CsvWriter::new_with_initialized_headers(writer, headers).await?;
70 while let Some(next) = stream.try_next().await? {
71 let csv_row = vec![
72 next.id.to_string(),
73 next.user_id.to_string(),
74 next.created_at.to_rfc3339(),
75 next.exercise_id.to_string(),
76 next.exercise_task_id.to_string(),
77 next.score_given.unwrap_or(0.0).to_string(),
78 next.data_json
79 .map(|o| o.to_string())
80 .unwrap_or_else(|| "".to_string()),
81 ];
82 writer.write_record(csv_row);
83 }
84 let writer = writer.finish().await?;
85 Ok(writer)
86}
87
88pub struct CourseSubmissionExportOperation {
89 pub course_id: Uuid,
90}
91
92#[async_trait]
93impl CsvExportDataLoader for CourseSubmissionExportOperation {
94 async fn load_data(
95 &self,
96 sender: UnboundedSender<Result<AuthorizedResponse<Bytes>, ControllerError>>,
97 conn: &mut PgConnection,
98 token: AuthorizationToken,
99 ) -> anyhow::Result<CSVExportAdapter> {
100 export_course_exercise_task_submissions(
101 &mut *conn,
102 self.course_id,
103 CSVExportAdapter {
104 sender,
105 authorization_token: token,
106 },
107 )
108 .await
109 }
110}
111
112pub async fn export_course_exercise_task_submissions<W>(
114 conn: &mut PgConnection,
115 course_id: Uuid,
116 writer: W,
117) -> Result<W>
118where
119 W: Write + Send + 'static,
120{
121 let headers = IntoIterator::into_iter([
122 "exercise_slide_submission_id".to_string(),
123 "exercise_task_submission_id".to_string(),
124 "user_id".to_string(),
125 "created_at".to_string(),
126 "course_instance_id".to_string(),
127 "exercise_id".to_string(),
128 "exercise_task_id".to_string(),
129 "score_given".to_string(),
130 "data_json".to_string(),
131 ]);
132
133 let mut stream = exercise_task_submissions::stream_course_submissions(conn, course_id);
134
135 let writer = CsvWriter::new_with_initialized_headers(writer, headers).await?;
136 while let Some(next) = stream.try_next().await? {
137 let csv_row = vec![
138 next.exercise_slide_submission_id.to_string(),
139 next.id.to_string(),
140 next.user_id.to_string(),
141 next.created_at.to_rfc3339(),
142 next.course_instance_id
143 .map(|o| o.to_string())
144 .unwrap_or_else(|| "".to_string()),
145 next.exercise_id.to_string(),
146 next.exercise_task_id.to_string(),
147 next.score_given.unwrap_or(0.0).to_string(),
148 next.data_json
149 .map(|o| o.to_string())
150 .unwrap_or_else(|| "".to_string()),
151 ];
152 writer.write_record(csv_row);
153 }
154 let writer = writer.finish().await?;
155 Ok(writer)
156}