headless_lms_server/controllers/main_frontend/
chapters.rs1use std::{path::PathBuf, str::FromStr, sync::Arc};
4
5use headless_lms_models::chapters::DatabaseChapter;
6use models::chapters::{Chapter, ChapterUpdate, NewChapter};
7
8use crate::{
9 domain::{
10 models_requests::{self, JwtKey},
11 request_id::RequestId,
12 },
13 prelude::*,
14};
15
16#[instrument(skip(pool, file_store, app_conf))]
35async fn post_new_chapter(
36 request_id: RequestId,
37 pool: web::Data<PgPool>,
38 payload: web::Json<NewChapter>,
39 user: AuthUser,
40 file_store: web::Data<dyn FileStore>,
41 app_conf: web::Data<ApplicationConfiguration>,
42 jwt_key: web::Data<JwtKey>,
43) -> ControllerResult<web::Json<Chapter>> {
44 let mut conn = pool.acquire().await?;
45 let token = authorize(
46 &mut conn,
47 Act::Edit,
48 Some(user.id),
49 Res::Course(payload.course_id),
50 )
51 .await?;
52 let new_chapter = payload.0;
53 let (database_chapter, ..) = models::library::content_management::create_new_chapter(
54 &mut conn,
55 PKeyPolicy::Generate,
56 &new_chapter,
57 user.id,
58 models_requests::make_spec_fetcher(
59 app_conf.base_url.clone(),
60 request_id.0,
61 Arc::clone(&jwt_key),
62 ),
63 models_requests::fetch_service_info,
64 )
65 .await?;
66 return token.authorized_ok(web::Json(Chapter::from_database_chapter(
67 &database_chapter,
68 file_store.as_ref(),
69 app_conf.as_ref(),
70 )));
71}
72
73#[instrument(skip(pool, file_store, app_conf))]
78async fn delete_chapter(
79 chapter_id: web::Path<String>,
80 pool: web::Data<PgPool>,
81 user: AuthUser,
82 file_store: web::Data<dyn FileStore>,
83 app_conf: web::Data<ApplicationConfiguration>,
84) -> ControllerResult<web::Json<Chapter>> {
85 let mut conn = pool.acquire().await?;
86 let chapter_id = Uuid::from_str(&chapter_id)?;
87 let token = authorize(
88 &mut conn,
89 Act::Edit,
90 Some(user.id),
91 Res::Chapter(chapter_id),
92 )
93 .await?;
94 let deleted_chapter = models::chapters::delete_chapter(&mut conn, chapter_id).await?;
95 return token.authorized_ok(web::Json(Chapter::from_database_chapter(
96 &deleted_chapter,
97 file_store.as_ref(),
98 app_conf.as_ref(),
99 )));
100}
101
102#[instrument(skip(payload, pool, file_store, app_conf))]
122async fn update_chapter(
123 payload: web::Json<ChapterUpdate>,
124 chapter_id: web::Path<String>,
125 pool: web::Data<PgPool>,
126 user: AuthUser,
127 file_store: web::Data<dyn FileStore>,
128 app_conf: web::Data<ApplicationConfiguration>,
129) -> ControllerResult<web::Json<Chapter>> {
130 let mut conn = pool.acquire().await?;
131 let chapter_id = Uuid::from_str(&chapter_id)?;
132 let course_id = models::chapters::get_course_id(&mut conn, chapter_id).await?;
133 let token = authorize(&mut conn, Act::Edit, Some(user.id), Res::Course(course_id)).await?;
134 let course_update = payload.0;
135 let chapter = models::chapters::update_chapter(&mut conn, chapter_id, course_update).await?;
136
137 let response = Chapter::from_database_chapter(&chapter, file_store.as_ref(), app_conf.as_ref());
138
139 token.authorized_ok(web::Json(response))
140}
141
142#[instrument(skip(request, payload, pool, file_store, app_conf))]
157async fn set_chapter_image(
158 request: HttpRequest,
159 payload: Multipart,
160 chapter_id: web::Path<Uuid>,
161 pool: web::Data<PgPool>,
162 user: AuthUser,
163 file_store: web::Data<dyn FileStore>,
164 app_conf: web::Data<ApplicationConfiguration>,
165) -> ControllerResult<web::Json<Chapter>> {
166 let mut conn = pool.acquire().await?;
167 let chapter = models::chapters::get_chapter(&mut conn, *chapter_id).await?;
168 let token = authorize(
169 &mut conn,
170 Act::Edit,
171 Some(user.id),
172 Res::Course(chapter.course_id),
173 )
174 .await?;
175
176 let course = models::courses::get_course(&mut conn, chapter.course_id).await?;
177 let chapter_image = upload_file_from_cms(
178 request.headers(),
179 payload,
180 StoreKind::Course(course.id),
181 file_store.as_ref(),
182 &mut conn,
183 user,
184 )
185 .await?
186 .to_string_lossy()
187 .to_string();
188 let updated_chapter =
189 models::chapters::update_chapter_image_path(&mut conn, chapter.id, Some(chapter_image))
190 .await?;
191
192 if let Some(old_image_path) = chapter.chapter_image_path {
194 let file = PathBuf::from_str(&old_image_path).map_err(|original_error| {
195 ControllerError::new(
196 ControllerErrorType::InternalServerError,
197 original_error.to_string(),
198 Some(original_error.into()),
199 )
200 })?;
201 file_store.delete(&file).await.map_err(|original_error| {
202 ControllerError::new(
203 ControllerErrorType::InternalServerError,
204 original_error.to_string(),
205 Some(original_error.into()),
206 )
207 })?;
208 }
209
210 let response =
211 Chapter::from_database_chapter(&updated_chapter, file_store.as_ref(), app_conf.as_ref());
212
213 token.authorized_ok(web::Json(response))
214}
215
216#[instrument(skip(pool, file_store))]
228async fn remove_chapter_image(
229 chapter_id: web::Path<Uuid>,
230 pool: web::Data<PgPool>,
231 user: AuthUser,
232 file_store: web::Data<dyn FileStore>,
233) -> ControllerResult<web::Json<()>> {
234 let mut conn = pool.acquire().await?;
235 let chapter = models::chapters::get_chapter(&mut conn, *chapter_id).await?;
236 let token = authorize(
237 &mut conn,
238 Act::Edit,
239 Some(user.id),
240 Res::Course(chapter.course_id),
241 )
242 .await?;
243 if let Some(chapter_image_path) = chapter.chapter_image_path {
244 let file = PathBuf::from_str(&chapter_image_path).map_err(|original_error| {
245 ControllerError::new(
246 ControllerErrorType::InternalServerError,
247 original_error.to_string(),
248 Some(original_error.into()),
249 )
250 })?;
251 let _res = models::chapters::update_chapter_image_path(&mut conn, chapter.id, None).await?;
252 file_store.delete(&file).await.map_err(|original_error| {
253 ControllerError::new(
254 ControllerErrorType::InternalServerError,
255 original_error.to_string(),
256 Some(original_error.into()),
257 )
258 })?;
259 }
260 token.authorized_ok(web::Json(()))
261}
262
263async fn get_all_chapters_by_course_id(
267 course_id: web::Path<Uuid>,
268 pool: web::Data<PgPool>,
269 user: AuthUser,
270) -> ControllerResult<web::Json<Vec<DatabaseChapter>>> {
271 let mut conn = pool.acquire().await?;
272 let token = authorize(&mut conn, Act::View, Some(user.id), Res::Course(*course_id)).await?;
273
274 let mut chapters = models::chapters::course_chapters(&mut conn, *course_id).await?;
275
276 chapters.sort_by(|a, b| a.chapter_number.cmp(&b.chapter_number));
277
278 token.authorized_ok(web::Json(chapters))
279}
280
281pub fn _add_routes(cfg: &mut ServiceConfig) {
289 cfg.route("", web::post().to(post_new_chapter))
290 .route("/{chapter_id}", web::delete().to(delete_chapter))
291 .route("/{chapter_id}", web::put().to(update_chapter))
292 .route("/{chapter_id}/image", web::put().to(set_chapter_image))
293 .route(
294 "/{chapter_id}/image",
295 web::delete().to(remove_chapter_image),
296 )
297 .route(
298 "/{course_id}/all-chapters-for-course",
299 web::get().to(get_all_chapters_by_course_id),
300 );
301}