headless_lms_server/controllers/main_frontend/
page_audio_files.rs1use std::path::Path;
4
5use futures::StreamExt;
6use models::page_audio_files::PageAudioFile;
7use utoipa::{OpenApi, ToSchema};
8
9use crate::prelude::*;
10
11#[derive(OpenApi)]
12#[openapi(paths(set_page_audio, remove_page_audio, get_page_audio))]
13pub(crate) struct MainFrontendPageAudioApiDoc;
14
15#[allow(dead_code)]
16#[derive(Debug, ToSchema)]
17struct PageAudioUploadPayload {
18 #[schema(content_media_type = "application/octet-stream")]
19 file: Vec<u8>,
20}
21
22#[instrument(skip(request, payload, pool, file_store))]
37#[utoipa::path(
38 post,
39 path = "/{page_id}",
40 operation_id = "createPageAudioFile",
41 tag = "page_audio",
42 params(
43 ("page_id" = Uuid, Path, description = "Page id")
44 ),
45 request_body(content = inline(PageAudioUploadPayload), content_type = "multipart/form-data"),
46 responses(
47 (status = 200, description = "Page audio uploaded", body = bool)
48 )
49)]
50async fn set_page_audio(
51 request: HttpRequest,
52 mut payload: Multipart,
53 page_id: web::Path<Uuid>,
54 pool: web::Data<PgPool>,
55 user: AuthUser,
56 file_store: web::Data<dyn FileStore>,
57) -> ControllerResult<web::Json<bool>> {
58 let mut conn = pool.acquire().await?;
59 let page = models::pages::get_page(&mut conn, *page_id).await?;
60 if let Some(course_id) = page.course_id {
61 let token = authorize(&mut conn, Act::Edit, Some(user.id), Res::Course(course_id)).await?;
62
63 let field = match payload.next().await {
64 Some(Ok(field)) => field,
65 Some(Err(error)) => {
66 return Err(ControllerError::new(
67 ControllerErrorType::InternalServerError,
68 error.to_string(),
69 None,
70 ));
71 }
72 None => {
73 return Err(ControllerError::new(
74 ControllerErrorType::BadRequest,
75 "Didn't upload any files",
76 None,
77 ));
78 }
79 };
80
81 let mime_type = field
82 .content_type()
83 .map(|ct| ct.to_string())
84 .unwrap_or_else(|| "".to_string());
85
86 match mime_type.as_str() {
87 "audio/mpeg" | "audio/ogg" => {}
88 unsupported => {
89 return Err(ControllerError::new(
90 ControllerErrorType::BadRequest,
91 format!("Unsupported audio Mime type: {}", unsupported),
92 None,
93 ));
94 }
95 };
96
97 let course = models::courses::get_course(&mut conn, course_id).await?;
98 let media_path = upload_field_from_cms(
99 request.headers(),
100 field,
101 StoreKind::Course(course.id),
102 file_store.as_ref(),
103 &mut conn,
104 user,
105 )
106 .await?;
107
108 models::page_audio_files::insert_page_audio(
109 &mut conn,
110 page.id,
111 &media_path.as_path().to_string_lossy(),
112 &mime_type,
113 )
114 .await?;
115
116 token.authorized_ok(web::Json(true))
117 } else {
118 Err(ControllerError::new(
119 ControllerErrorType::BadRequest,
120 "The page needs to be related to a course.".to_string(),
121 None,
122 ))
123 }
124}
125
126#[instrument(skip(pool, file_store))]
138#[utoipa::path(
139 delete,
140 path = "/{file_id}",
141 operation_id = "deletePageAudioFile",
142 tag = "page_audio",
143 params(
144 ("file_id" = Uuid, Path, description = "Page audio file id")
145 ),
146 responses(
147 (status = 200, description = "Page audio deleted")
148 )
149)]
150async fn remove_page_audio(
151 file_id: web::Path<Uuid>,
152 pool: web::Data<PgPool>,
153 user: AuthUser,
154 file_store: web::Data<dyn FileStore>,
155) -> ControllerResult<web::Json<()>> {
156 let mut conn = pool.acquire().await?;
157 let audio = models::page_audio_files::get_page_audio_files_by_id(&mut conn, *file_id).await?;
158 let page = models::pages::get_page(&mut conn, audio.page_id).await?;
159 if let Some(course_id) = page.course_id {
160 let token = authorize(&mut conn, Act::Edit, Some(user.id), Res::Course(course_id)).await?;
161
162 let path = models::page_audio_files::delete_page_audio(&mut conn, *file_id).await?;
163 file_store.delete(Path::new(&path)).await.map_err(|_| {
164 ControllerError::new(
165 ControllerErrorType::BadRequest,
166 "Could not delete the file from the file store".to_string(),
167 None,
168 )
169 })?;
170 token.authorized_ok(web::Json(()))
171 } else {
172 Err(ControllerError::new(
173 ControllerErrorType::BadRequest,
174 "The page needs to be related to a course.".to_string(),
175 None,
176 ))
177 }
178}
179
180#[instrument(skip(app_conf))]
186#[utoipa::path(
187 get,
188 path = "/{page_id}/files",
189 operation_id = "getPageAudioFiles",
190 tag = "page_audio",
191 params(
192 ("page_id" = Uuid, Path, description = "Page id")
193 ),
194 responses(
195 (status = 200, description = "Page audio files", body = Vec<PageAudioFile>)
196 )
197)]
198async fn get_page_audio(
199 page_id: web::Path<Uuid>,
200 pool: web::Data<PgPool>,
201 user: AuthUser,
202 app_conf: web::Data<ApplicationConfiguration>,
203) -> ControllerResult<web::Json<Vec<PageAudioFile>>> {
204 let mut conn = pool.acquire().await?;
205 let token = authorize(&mut conn, Act::Edit, Some(user.id), Res::Page(*page_id)).await?;
206
207 let mut page_audio_files =
208 models::page_audio_files::get_page_audio_files(&mut conn, *page_id).await?;
209
210 let base_url = &app_conf.base_url;
211 for audio in page_audio_files.iter_mut() {
212 audio.path = format!("{base_url}/api/v0/files/{}", audio.path);
213 }
214
215 token.authorized_ok(web::Json(page_audio_files))
216}
217
218pub fn _add_routes(cfg: &mut ServiceConfig) {
226 cfg.route("/{page_id}", web::post().to(set_page_audio))
227 .route("/{file_id}", web::delete().to(remove_page_audio))
228 .route("/{page_id}/files", web::get().to(get_page_audio));
229}