Skip to main content

headless_lms_models/
exercise_repositories.rs

1use crate::prelude::*;
2use secrecy::{ExposeSecret, SecretString};
3use utoipa::ToSchema;
4
5#[derive(Debug, Serialize, sqlx::Type, ToSchema)]
6#[sqlx(type_name = "exercise_repository_status", rename_all = "kebab-case")]
7pub enum ExerciseRepositoryStatus {
8    Pending,
9    Success,
10    Failure,
11}
12
13#[derive(Debug, Serialize, ToSchema)]
14
15pub struct ExerciseRepository {
16    pub id: Uuid,
17    pub url: String,
18    pub course_id: Option<Uuid>,
19    pub exam_id: Option<Uuid>,
20    pub status: ExerciseRepositoryStatus,
21    pub error_message: Option<String>,
22}
23
24pub async fn get(conn: &mut PgConnection, id: Uuid) -> ModelResult<ExerciseRepository> {
25    let res = sqlx::query_as!(
26        ExerciseRepository,
27        r#"
28SELECT id,
29  url,
30  course_id,
31  exam_id,
32  status,
33  error_message
34FROM exercise_repositories
35WHERE id = $1
36  AND deleted_at IS NULL
37"#,
38        id
39    )
40    .fetch_one(conn)
41    .await?;
42    Ok(res)
43}
44
45pub async fn new(
46    conn: &mut PgConnection,
47    id: Uuid,
48    course_or_exam_id: CourseOrExamId,
49    url: &str,
50    public_key: Option<&str>,
51    deploy_key: Option<&SecretString>,
52) -> ModelResult<()> {
53    let (course_id, exam_id) = course_or_exam_id.to_course_and_exam_ids();
54    sqlx::query!(
55        "
56INSERT INTO exercise_repositories (id, course_id, exam_id, url, public_key, deploy_key)
57VALUES ($1, $2, $3, $4, $5, $6)
58",
59        id,
60        course_id,
61        exam_id,
62        url,
63        public_key,
64        // Exposed only here, at the DB-write boundary.
65        deploy_key.map(|k| k.expose_secret())
66    )
67    .execute(conn)
68    .await?;
69    Ok(())
70}
71
72pub async fn mark_success(conn: &mut PgConnection, id: Uuid) -> ModelResult<()> {
73    sqlx::query!(
74        "
75UPDATE exercise_repositories
76SET status = 'success'
77WHERE id = $1
78",
79        id
80    )
81    .execute(conn)
82    .await?;
83    Ok(())
84}
85
86pub async fn mark_failure(
87    conn: &mut PgConnection,
88    id: Uuid,
89    error_message: &str,
90) -> ModelResult<()> {
91    sqlx::query!(
92        "
93UPDATE exercise_repositories
94SET status = 'failure',
95  error_message = $2
96WHERE id = $1
97",
98        id,
99        error_message
100    )
101    .execute(conn)
102    .await?;
103    Ok(())
104}
105
106pub async fn delete(conn: &mut PgConnection, id: Uuid) -> ModelResult<()> {
107    sqlx::query!(
108        "
109UPDATE exercise_repositories
110SET deleted_at = now()
111WHERE id = $1
112AND deleted_at IS NULL
113",
114        id
115    )
116    .execute(conn)
117    .await?;
118    Ok(())
119}
120
121pub async fn get_for_course_or_exam(
122    conn: &mut PgConnection,
123    id: CourseOrExamId,
124) -> ModelResult<Vec<ExerciseRepository>> {
125    let (course_id, exam_id) = id.to_course_and_exam_ids();
126    let res = sqlx::query_as!(
127        ExerciseRepository,
128        r#"
129SELECT id,
130  url,
131  course_id,
132  exam_id,
133  status,
134  error_message
135FROM exercise_repositories
136WHERE (
137    course_id = $1
138    OR exam_id = $2
139  )
140  AND deleted_at IS NULL
141"#,
142        course_id,
143        exam_id
144    )
145    .fetch_all(conn)
146    .await?;
147    Ok(res)
148}
149
150#[derive(Debug, Deserialize, ToSchema)]
151pub struct ExerciseRepositoryUpdate {
152    pub url: String,
153}
154
155pub async fn update(
156    conn: &mut PgConnection,
157    id: Uuid,
158    update: &ExerciseRepositoryUpdate,
159) -> ModelResult<()> {
160    sqlx::query!(
161        "
162UPDATE exercise_repositories SET url = $1 WHERE id = $2
163",
164        update.url,
165        id
166    )
167    .execute(conn)
168    .await?;
169    Ok(())
170}