Skip to main content

headless_lms_models/
course_page_markdown_content.rs

1use serde_json::Value;
2
3use crate::prelude::*;
4
5#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
6pub struct CoursePageMarkdownContent {
7    pub id: Uuid,
8    pub created_at: DateTime<Utc>,
9    pub updated_at: DateTime<Utc>,
10    pub deleted_at: Option<DateTime<Utc>>,
11    pub markdown_content: String,
12    pub page_history_id: Uuid,
13    pub page_id: Uuid,
14}
15
16// Struct used in document_lookup chatbot tool
17pub struct CoursePageContent {
18    pub page_id: Uuid,
19    pub course_id: Uuid,
20    pub title: String,
21    pub json_content: Value,
22    /// Latest LLM-generated Markdown.
23    pub markdown_content: Option<String>,
24}
25
26pub async fn insert(
27    conn: &mut PgConnection,
28    content: &str,
29    page_history_id: &Uuid,
30    page_id: &Uuid,
31) -> ModelResult<CoursePageMarkdownContent> {
32    let res = sqlx::query_as!(
33        CoursePageMarkdownContent,
34        r#"
35INSERT INTO course_page_markdown_content (markdown_content, page_history_id, page_id)
36VALUES ($1, $2, $3)
37RETURNING *
38    "#,
39        content,
40        page_history_id,
41        page_id
42    )
43    .fetch_one(conn)
44    .await?;
45
46    Ok(res)
47}
48
49pub async fn insert_batch(
50    conn: &mut PgConnection,
51    page_id_history_id_contents: Vec<(Uuid, (Uuid, String))>,
52) -> ModelResult<Vec<CoursePageMarkdownContent>> {
53    let (page_ids, history_ids_contents): (Vec<Uuid>, Vec<(Uuid, String)>) =
54        page_id_history_id_contents.into_iter().unzip();
55    let (history_ids, contents): (Vec<Uuid>, Vec<String>) =
56        history_ids_contents.into_iter().unzip();
57
58    let res = sqlx::query_as!(
59        CoursePageMarkdownContent,
60        r#"
61
62INSERT INTO course_page_markdown_content (markdown_content, page_history_id, page_id)
63SELECT data.content, data.page_history_id, data.page_id
64FROM (
65    SELECT unnest($1::text []) AS content,
66      unnest($2::uuid []) AS page_history_id,
67      unnest($3::uuid []) AS page_id
68  ) AS data
69RETURNING *
70    "#,
71        &contents,
72        &history_ids,
73        &page_ids
74    )
75    .fetch_all(conn)
76    .await?;
77
78    Ok(res)
79}
80
81pub async fn get(conn: &mut PgConnection, id: Uuid) -> ModelResult<CoursePageMarkdownContent> {
82    let res = sqlx::query_as!(
83        CoursePageMarkdownContent,
84        r#"
85SELECT * FROM course_page_markdown_content
86WHERE id = $1
87AND deleted_at IS NULL
88    "#,
89        id
90    )
91    .fetch_one(conn)
92    .await?;
93
94    Ok(res)
95}
96
97pub async fn get_many(
98    conn: &mut PgConnection,
99    ids: &[Uuid],
100) -> ModelResult<Vec<CoursePageMarkdownContent>> {
101    let res = sqlx::query_as!(
102        CoursePageMarkdownContent,
103        r#"
104SELECT * FROM course_page_markdown_content
105WHERE id = ANY($1)
106AND deleted_at IS NULL
107    "#,
108        ids
109    )
110    .fetch_all(conn)
111    .await?;
112
113    Ok(res)
114}
115
116/// Get latest page content, either latest Markdown that has been synced or json format.
117pub async fn get_course_page_content_by_page_id(
118    conn: &mut PgConnection,
119    page_id: Uuid,
120) -> ModelResult<CoursePageContent> {
121    let content = sqlx::query_as!(
122        CoursePageContent,
123        r#"
124SELECT pages.content AS json_content,
125  pages.title,
126  pages.id AS page_id,
127  cpmc.markdown_content,
128  cps.course_id
129FROM pages
130  JOIN page_history AS ph ON pages.id = ph.page_id
131  JOIN chatbot_page_sync_statuses AS cps ON pages.id = cps.page_id
132  JOIN course_page_markdown_content AS cpmc ON cps.converted_markdown_content_id = cpmc.id
133WHERE ph.id IN (
134    SELECT synced_page_revision_id
135    FROM chatbot_page_sync_statuses
136    WHERE page_id = $1
137      AND deleted_at IS NULL
138    ORDER BY updated_at DESC
139    LIMIT 1
140  )
141  AND pages.id = $1
142  AND pages.deleted_at IS NULL
143  AND ph.deleted_at IS NULL
144  AND cps.deleted_at IS NULL
145  AND cpmc.deleted_at IS NULL
146    "#,
147        page_id,
148    )
149    .fetch_optional(&mut *conn)
150    .await?;
151
152    if let Some(s) = content {
153        Ok(s)
154    } else {
155        // Get fall back page content as json. The course_id should not be null
156        // because this result is for course page content look up.
157        let fallback_content = sqlx::query_as!(
158            CoursePageContent,
159            r#"
160SELECT pages.content AS json_content,
161  pages.title,
162  pages.id AS page_id,
163  null as markdown_content,
164  pages.course_id as "course_id!"
165FROM pages
166  WHERE pages.course_id IS NOT NULL
167  AND pages.id = $1
168  AND pages.deleted_at IS NULL
169    "#,
170            page_id,
171        )
172        .fetch_one(&mut *conn)
173        .await?;
174
175        Ok(fallback_content)
176    }
177}