headless_lms_models/
open_university_product_access_tokens.rs1use secrecy::ExposeSecret;
2
3use crate::prelude::*;
4
5#[derive(Debug, Clone)]
6pub struct OpenUniversityProductAccessToken {
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 open_university_product_id: String,
12 pub access_token: DbSecret,
13 pub state: String,
14 pub document_state: String,
15 pub suotar_token_id: Option<String>,
16 pub last_refreshed_at: DateTime<Utc>,
17 pub last_refresh_failed_at: Option<DateTime<Utc>>,
18 pub last_refresh_error: Option<String>,
19 pub consecutive_failures: i32,
20}
21
22#[derive(Debug, Clone)]
23pub struct NewOpenUniversityProductAccessToken {
24 pub open_university_product_id: String,
25 pub access_token: DbSecret,
26 pub state: String,
27 pub document_state: String,
28 pub suotar_token_id: Option<String>,
29}
30
31pub async fn upsert(
33 conn: &mut PgConnection,
34 new: &NewOpenUniversityProductAccessToken,
35) -> ModelResult<Uuid> {
36 let res = sqlx::query!(
37 r#"
38INSERT INTO open_university_product_access_tokens (
39 open_university_product_id,
40 access_token,
41 state,
42 document_state,
43 suotar_token_id
44 )
45VALUES ($1, $2, $3, $4, $5) ON CONFLICT (open_university_product_id, deleted_at) DO
46UPDATE
47SET access_token = $2,
48 state = $3,
49 document_state = $4,
50 suotar_token_id = $5,
51 last_refreshed_at = now(),
52 last_refresh_failed_at = NULL,
53 last_refresh_error = NULL,
54 consecutive_failures = 0
55RETURNING id
56 "#,
57 new.open_university_product_id,
58 new.access_token.expose_secret(),
59 new.state,
60 new.document_state,
61 new.suotar_token_id,
62 )
63 .fetch_one(conn)
64 .await?;
65 Ok(res.id)
66}
67
68pub async fn get_by_product_id(
69 conn: &mut PgConnection,
70 open_university_product_id: &str,
71) -> ModelResult<Option<OpenUniversityProductAccessToken>> {
72 let res = sqlx::query_as!(
73 OpenUniversityProductAccessToken,
74 r#"
75SELECT *
76FROM open_university_product_access_tokens
77WHERE open_university_product_id = $1
78 AND deleted_at IS NULL
79 "#,
80 open_university_product_id
81 )
82 .fetch_optional(conn)
83 .await?;
84 Ok(res)
85}
86
87pub async fn get_all(
88 conn: &mut PgConnection,
89) -> ModelResult<Vec<OpenUniversityProductAccessToken>> {
90 let res = sqlx::query_as!(
91 OpenUniversityProductAccessToken,
92 r#"
93SELECT *
94FROM open_university_product_access_tokens
95WHERE deleted_at IS NULL
96ORDER BY open_university_product_id
97 "#,
98 )
99 .fetch_all(conn)
100 .await?;
101 Ok(res)
102}
103
104pub async fn record_refresh_failure(
106 conn: &mut PgConnection,
107 open_university_product_id: &str,
108 error: &str,
109) -> ModelResult<()> {
110 sqlx::query!(
111 r#"
112UPDATE open_university_product_access_tokens
113SET last_refresh_failed_at = now(),
114 last_refresh_error = $2,
115 consecutive_failures = consecutive_failures + 1
116WHERE open_university_product_id = $1
117 AND deleted_at IS NULL
118 "#,
119 open_university_product_id,
120 error,
121 )
122 .execute(conn)
123 .await?;
124 Ok(())
125}
126
127pub async fn soft_delete(conn: &mut PgConnection, id: Uuid) -> ModelResult<()> {
128 sqlx::query!(
129 r#"
130UPDATE open_university_product_access_tokens
131SET deleted_at = now()
132WHERE id = $1
133 AND deleted_at IS NULL
134 "#,
135 id
136 )
137 .execute(conn)
138 .await?;
139 Ok(())
140}