Skip to main content

headless_lms_server/controllers/main_frontend/courses/
chatbots.rs

1//! Controllers for requests starting with `/api/v0/main-frontend/{course_id}/chatbots`.
2use crate::prelude::*;
3
4use headless_lms_models::chatbot_configurations::NewChatbotConf;
5use models::chatbot_configurations::ChatbotConfiguration;
6use utoipa::OpenApi;
7
8#[derive(OpenApi)]
9#[openapi(paths(
10    get_chatbots,
11    create_chatbot,
12    set_default_chatbot,
13    set_non_default_chatbot
14))]
15pub(crate) struct MainFrontendCourseChatbotsApiDoc;
16
17/// GET `/api/v0/main-frontend/courses/{course_id}/chatbots`
18#[utoipa::path(
19    get,
20    path = "",
21    operation_id = "getCourseChatbots",
22    tag = "courses",
23    params(
24        ("course_id" = String, Path, description = "Course id")
25    ),
26    responses(
27        (status = 200, description = "Course chatbots", body = Vec<ChatbotConfiguration>)
28    )
29)]
30#[instrument(skip(pool))]
31async fn get_chatbots(
32    course_id: web::Path<Uuid>,
33    pool: web::Data<PgPool>,
34    user: AuthUser,
35) -> ControllerResult<web::Json<Vec<ChatbotConfiguration>>> {
36    let mut conn = pool.acquire().await?;
37    let token = authorize(&mut conn, Act::Edit, Some(user.id), Res::Course(*course_id)).await?;
38
39    let configurations =
40        models::chatbot_configurations::get_for_course(&mut conn, *course_id).await?;
41    token.authorized_ok(web::Json(configurations))
42}
43
44/// POST `/api/v0/main-frontend/courses/{course_id}/chatbots`
45#[utoipa::path(
46    post,
47    path = "",
48    operation_id = "createCourseChatbot",
49    tag = "courses",
50    params(
51        ("course_id" = String, Path, description = "Course id")
52    ),
53    request_body(
54        content = String,
55        description = "JSON string literal chatbot name, e.g. \"Chatbot 1\".",
56        content_type = "application/json"
57    ),
58    responses(
59        (status = 200, description = "Created course chatbot", body = ChatbotConfiguration)
60    )
61)]
62#[instrument(skip(pool, payload))]
63async fn create_chatbot(
64    course_id: web::Path<Uuid>,
65    payload: web::Json<String>,
66    pool: web::Data<PgPool>,
67    user: AuthUser,
68) -> ControllerResult<web::Json<ChatbotConfiguration>> {
69    let mut conn = pool.acquire().await?;
70    let token = authorize(&mut conn, Act::Edit, Some(user.id), Res::Course(*course_id)).await?;
71    let mut tx = conn.begin().await?;
72
73    let course = models::courses::get_course(&mut tx, *course_id).await?;
74
75    if !course.can_add_chatbot {
76        return Err(ControllerError::new(
77            ControllerErrorType::BadRequest,
78            "Course doesn't allow creating chatbots.".to_string(),
79            None,
80        ));
81    }
82
83    let model = models::chatbot_configurations_models::get_default(&mut tx)
84        .await
85        .map_err(|e| {
86            ControllerError::new(
87                ControllerErrorType::BadRequest,
88                "No default chatbot model configured. Ask an admin to set one.".to_string(),
89                Some(e.into()),
90            )
91        })?;
92
93    let configuration = models::chatbot_configurations::insert(
94        &mut tx,
95        PKeyPolicy::Generate,
96        NewChatbotConf {
97            chatbot_name: payload.into_inner(),
98            course_id: Some(*course_id),
99            model_id: model.id,
100            ..Default::default()
101        },
102    )
103    .await?;
104    tx.commit().await?;
105
106    token.authorized_ok(web::Json(configuration))
107}
108
109/// POST `/api/v0/main-frontend/courses/{course_id}/chatbots/{chatbot_configuration_id}/set-as-default`
110#[utoipa::path(
111    post,
112    path = "/{chatbot_configuration_id}/set-as-default",
113    operation_id = "setCourseChatbotAsDefault",
114    tag = "courses",
115    params(
116        ("course_id" = String, Path, description = "Course id"),
117        ("chatbot_configuration_id" = String, Path, description = "Chatbot configuration id")
118    ),
119    responses(
120        (status = 200, description = "Updated course chatbot", body = ChatbotConfiguration)
121    )
122)]
123#[instrument(skip(pool))]
124async fn set_default_chatbot(
125    ids: web::Path<(Uuid, Uuid)>,
126    pool: web::Data<PgPool>,
127    user: AuthUser,
128) -> ControllerResult<web::Json<ChatbotConfiguration>> {
129    let mut conn = pool.acquire().await?;
130    let (course_id, chatbot_configuration_id) = *ids;
131
132    let token = authorize(&mut conn, Act::Edit, Some(user.id), Res::Course(course_id)).await?;
133    let mut tx = conn.begin().await?;
134
135    models::chatbot_configurations::remove_default_chatbot_from_course(&mut tx, course_id).await?;
136
137    let chatbot =
138        models::chatbot_configurations::get_by_id(&mut tx, chatbot_configuration_id).await?;
139
140    if course_id
141        != chatbot.course_id.ok_or_else(|| {
142            ControllerError::new(
143                ControllerErrorType::BadRequest,
144                "Chatbot course id is missing.".to_string(),
145                None,
146            )
147        })?
148    {
149        return Err(ControllerError::new(
150            ControllerErrorType::BadRequest,
151            "Chatbot course id doesn't match the course id provided.".to_string(),
152            None,
153        ));
154    }
155
156    let configuration = models::chatbot_configurations::set_default_chatbot_for_course(
157        &mut tx,
158        chatbot_configuration_id,
159    )
160    .await?;
161    tx.commit().await?;
162
163    token.authorized_ok(web::Json(configuration))
164}
165
166/// POST `/api/v0/main-frontend/courses/{course_id}/chatbots/{chatbot_configuration_id}/set-as-non-default`
167#[utoipa::path(
168    post,
169    path = "/{chatbot_configuration_id}/set-as-non-default",
170    operation_id = "setCourseChatbotAsNonDefault",
171    tag = "courses",
172    params(
173        ("course_id" = String, Path, description = "Course id"),
174        ("chatbot_configuration_id" = String, Path, description = "Chatbot configuration id")
175    ),
176    responses(
177        (status = 200, description = "Updated course chatbot", body = ChatbotConfiguration)
178    )
179)]
180#[instrument(skip(pool))]
181async fn set_non_default_chatbot(
182    ids: web::Path<(Uuid, Uuid)>,
183    pool: web::Data<PgPool>,
184    user: AuthUser,
185) -> ControllerResult<web::Json<ChatbotConfiguration>> {
186    let mut conn = pool.acquire().await?;
187    let (course_id, chatbot_configuration_id) = *ids;
188
189    let token = authorize(&mut conn, Act::Edit, Some(user.id), Res::Course(course_id)).await?;
190    let mut tx = conn.begin().await?;
191
192    models::chatbot_configurations::remove_default_chatbot_from_course(&mut tx, course_id).await?;
193
194    let configuration =
195        models::chatbot_configurations::get_by_id(&mut tx, chatbot_configuration_id).await?;
196
197    if course_id
198        != configuration.course_id.ok_or_else(|| {
199            ControllerError::new(
200                ControllerErrorType::BadRequest,
201                "Chatbot course id is missing.".to_string(),
202                None,
203            )
204        })?
205    {
206        return Err(ControllerError::new(
207            ControllerErrorType::BadRequest,
208            "Chatbot course id doesn't match the course id provided.".to_string(),
209            None,
210        ));
211    }
212    tx.commit().await?;
213
214    token.authorized_ok(web::Json(configuration))
215}
216
217pub fn _add_routes(cfg: &mut web::ServiceConfig) {
218    cfg.route("", web::get().to(get_chatbots))
219        .route("", web::post().to(create_chatbot))
220        .route(
221            "/{chatbot_configuration_id}/set-as-default",
222            web::post().to(set_default_chatbot),
223        )
224        .route(
225            "/{chatbot_configuration_id}/set-as-non-default",
226            web::post().to(set_non_default_chatbot),
227        );
228}