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: *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 != chatbot.course_id {
141        return Err(ControllerError::new(
142            ControllerErrorType::BadRequest,
143            "Chatbot course id doesn't match the course id provided.".to_string(),
144            None,
145        ));
146    }
147
148    let configuration = models::chatbot_configurations::set_default_chatbot_for_course(
149        &mut tx,
150        chatbot_configuration_id,
151    )
152    .await?;
153    tx.commit().await?;
154
155    token.authorized_ok(web::Json(configuration))
156}
157
158/// POST `/api/v0/main-frontend/courses/{course_id}/chatbots/{chatbot_configuration_id}/set-as-non-default`
159#[utoipa::path(
160    post,
161    path = "/{chatbot_configuration_id}/set-as-non-default",
162    operation_id = "setCourseChatbotAsNonDefault",
163    tag = "courses",
164    params(
165        ("course_id" = String, Path, description = "Course id"),
166        ("chatbot_configuration_id" = String, Path, description = "Chatbot configuration id")
167    ),
168    responses(
169        (status = 200, description = "Updated course chatbot", body = ChatbotConfiguration)
170    )
171)]
172#[instrument(skip(pool))]
173async fn set_non_default_chatbot(
174    ids: web::Path<(Uuid, Uuid)>,
175    pool: web::Data<PgPool>,
176    user: AuthUser,
177) -> ControllerResult<web::Json<ChatbotConfiguration>> {
178    let mut conn = pool.acquire().await?;
179    let (course_id, chatbot_configuration_id) = *ids;
180
181    let token = authorize(&mut conn, Act::Edit, Some(user.id), Res::Course(course_id)).await?;
182    let mut tx = conn.begin().await?;
183
184    models::chatbot_configurations::remove_default_chatbot_from_course(&mut tx, course_id).await?;
185
186    let configuration =
187        models::chatbot_configurations::get_by_id(&mut tx, chatbot_configuration_id).await?;
188
189    if course_id != configuration.course_id {
190        return Err(ControllerError::new(
191            ControllerErrorType::BadRequest,
192            "Chatbot course id doesn't match the course id provided.".to_string(),
193            None,
194        ));
195    }
196    tx.commit().await?;
197
198    token.authorized_ok(web::Json(configuration))
199}
200
201pub fn _add_routes(cfg: &mut web::ServiceConfig) {
202    cfg.route("", web::get().to(get_chatbots))
203        .route("", web::post().to(create_chatbot))
204        .route(
205            "/{chatbot_configuration_id}/set-as-default",
206            web::post().to(set_default_chatbot),
207        )
208        .route(
209            "/{chatbot_configuration_id}/set-as-non-default",
210            web::post().to(set_non_default_chatbot),
211        );
212}