tallyweb_frontend/
api.rs

1use countable::{self, Counter};
2use leptos::*;
3
4use super::*;
5
6#[cfg(feature = "ssr")]
7use actix_web::web::Data;
8#[cfg(feature = "ssr")]
9use leptos_actix::extract;
10
11#[cfg(feature = "ssr")]
12pub async fn extract_pool() -> Result<Data<backend::PgPool>, AppError> {
13    extract::<Data<backend::PgPool>>()
14        .await
15        .map_err(|err| AppError::Extraction(err.to_string()))
16}
17
18#[server(CheckUser, "/api")]
19pub async fn check_user(session: UserSession) -> Result<(), ServerFnError> {
20    use backend::auth::SessionState;
21    let pool = extract_pool().await?;
22    match backend::auth::check_user(&pool, &session.username, session.token).await {
23        Ok(SessionState::Valid) => Ok(()),
24        Ok(SessionState::Expired) => Err(AppError::ExpiredToken)?,
25        Err(err) => {
26            leptos_actix::redirect("/login");
27            Err(err.into())
28        }
29    }
30}
31
32#[server(LoginUser, "/api", "Url", "login_user")]
33pub async fn login_user(
34    username: String,
35    password: String,
36    remember: Option<String>,
37) -> Result<UserSession, ServerFnError> {
38    let pool = extract_pool().await?;
39
40    let dur = if remember.is_some() {
41        chrono::Duration::days(30)
42    } else {
43        chrono::Duration::days(1)
44    };
45
46    let user = backend::auth::login_user(&pool, username, password, dur).await?;
47
48    let session = UserSession {
49        user_uuid: user.uuid,
50        username: user.username,
51        token: user.token.unwrap(),
52    };
53
54    set_session_cookie(session.clone()).await?;
55    leptos_actix::redirect("/");
56
57    Ok(session)
58}
59
60#[server(CreateAccount, "/api", "Url", "create_account")]
61pub async fn create_account(
62    username: String,
63    password: String,
64    password_repeat: String,
65) -> Result<UserSession, ServerFnError> {
66    if password != password_repeat {
67        Err(backend::LoginError::InvalidPassword)?;
68    }
69
70    let pool = extract_pool().await?;
71    let user = backend::auth::insert_user(&pool, &username, &password).await?;
72
73    let session_user = UserSession {
74        user_uuid: user.uuid,
75        username: user.username,
76        token: user.token.unwrap(),
77    };
78
79    login_user(username, password, None).await?;
80
81    Ok(session_user)
82}
83
84#[server(ChangePassword, "/api")]
85pub async fn change_password(
86    username: String,
87    old_pass: String,
88    new_pass: String,
89    new_pass_repeat: String,
90) -> Result<(), ServerFnError> {
91    if new_pass != new_pass_repeat {
92        Err(backend::LoginError::InvalidPassword)?;
93    };
94
95    let pool = extract_pool().await?;
96    let _ = backend::auth::change_password(&pool, username, old_pass, new_pass).await?;
97
98    Ok(())
99}
100
101#[allow(clippy::too_many_arguments)]
102#[server(EditCountableForm)]
103pub async fn edit_countable_form(
104    session_user_uuid: uuid::Uuid,
105    session_username: String,
106    session_token: uuid::Uuid,
107
108    countable_key: uuid::Uuid,
109    countable_kind: CountableKind,
110    countable_name: String,
111    countable_count: i32,
112    countable_hours: i64,
113    countable_mins: i64,
114    countable_secs: i64,
115    countable_millis: i64,
116    countable_hunttype: String,
117    countable_charm: Option<String>,
118) -> Result<(), ServerFnError> {
119    let session = UserSession {
120        user_uuid: session_user_uuid,
121        username: session_username,
122        token: session_token,
123    };
124    check_user(session).await?;
125
126    let countable_time =
127        ((countable_hours * 60 + countable_mins) * 60 + countable_secs) * 1000 + countable_millis;
128
129    let mut conn = extract_pool().await?.begin().await?;
130    match countable_kind {
131        CountableKind::Counter => {
132            backend::counter::set_name(&mut conn, countable_key, &countable_name).await?;
133            backend::counter::set_count(&mut conn, countable_key, countable_count).await?;
134            backend::counter::set_time(&mut conn, countable_key, countable_time).await?;
135            backend::counter::set_hunttype(&mut conn, countable_key, countable_hunttype.into())
136                .await?;
137            backend::counter::set_charm(&mut conn, countable_key, countable_charm.is_some())
138                .await?;
139        }
140        CountableKind::Phase => {
141            backend::phase::set_name(&mut conn, countable_key, &countable_name).await?;
142            backend::phase::set_count(&mut conn, countable_key, countable_count).await?;
143            backend::phase::set_time(&mut conn, countable_key, countable_time).await?;
144            backend::phase::set_hunttype(&mut conn, countable_key, countable_hunttype.into())
145                .await?;
146            backend::phase::set_charm(&mut conn, countable_key, countable_charm.is_some()).await?;
147        }
148        _ => (),
149    }
150
151    conn.commit().await?;
152
153    return Ok(());
154}
155
156#[server(UpdateCountable, "/api/session")]
157pub async fn update_countable_many(list: Vec<countable::Countable>) -> Result<(), ServerFnError> {
158    let pool = extract_pool().await?;
159    let session = session::actix_extract_user().await?;
160
161    let mut tx = pool.begin().await?;
162
163    for countable in list {
164        match countable {
165            countable::Countable::Counter(c) => {
166                if c.lock()?.owner_uuid != session.user_uuid {
167                    Err(AppError::Unauthorized)?
168                }
169                backend::counter::update(&mut tx, c.lock()?.clone().into()).await?
170            }
171            countable::Countable::Phase(p) => {
172                if p.lock()?.owner_uuid != session.user_uuid {
173                    Err(AppError::Unauthorized)?
174                }
175                backend::phase::update(&mut tx, p.lock()?.clone().into()).await?
176            }
177            countable::Countable::Chain(_) => todo!(),
178        }
179    }
180
181    tx.commit().await?;
182
183    return Ok(());
184}
185
186#[server(UpdateCounter, "/api")]
187pub async fn update_counter(session: UserSession, counter: Counter) -> Result<(), ServerFnError> {
188    let pool = extract_pool().await?;
189
190    let _ =
191        backend::update_counter(&pool, &session.username, session.token, counter.into()).await?;
192
193    Ok(())
194}
195
196#[server(ArchiveCountable, "/api/session")]
197pub async fn archive_countable(countable: Countable) -> Result<(), ServerFnError> {
198    let pool = extract_pool().await?;
199    let mut tx = pool.begin().await?;
200
201    let uuid = countable.uuid();
202
203    if let Err(err) = match countable {
204        Countable::Counter(_) => backend::counter::archive(&mut tx, uuid).await,
205        Countable::Phase(_) => backend::phase::archive(&mut tx, uuid).await,
206        Countable::Chain(_) => todo!(),
207    } {
208        tx.rollback().await?;
209        return Err(err.into());
210    }
211
212    tx.commit().await?;
213
214    Ok(())
215}
216
217#[server(RemoveCountable, "/api/session")]
218pub async fn remove_countable(
219    session: UserSession,
220    countable: Countable,
221) -> Result<(), ServerFnError> {
222    match countable {
223        Countable::Counter(_) => remove_counter(session, countable.uuid()).await?,
224        Countable::Phase(_) => remove_phase(session, countable.uuid()).await?,
225        Countable::Chain(_) => todo!(),
226    }
227
228    return Ok(());
229}
230
231#[server(RemoveCounter, "/api")]
232pub async fn remove_counter(
233    session: UserSession,
234    counter_id: uuid::Uuid,
235) -> Result<(), ServerFnError> {
236    let pool = extract_pool().await?;
237
238    backend::remove_counter(&pool, &session.username, session.token, counter_id).await?;
239
240    Ok(())
241}
242
243#[server(RemovePhase, "/api")]
244pub async fn remove_phase(session: UserSession, phase_id: uuid::Uuid) -> Result<(), ServerFnError> {
245    let pool = extract_pool().await?;
246
247    let _ = backend::auth::get_user(&pool, &session.username, session.token).await?;
248    backend::remove_phase(&pool, phase_id).await?;
249
250    Ok(())
251}
252
253#[server(GetUserPreferences, "/api")]
254pub async fn get_user_preferences(session: UserSession) -> Result<Preferences, ServerFnError> {
255    let pool = extract_pool().await?;
256
257    let user = match backend::auth::get_user(&pool, &session.username, session.token).await {
258        Ok(user) => user,
259        Err(_) => {
260            return Ok(Preferences::default());
261        }
262    };
263    let session_user = UserSession {
264        user_uuid: user.uuid,
265        username: user.username,
266        token: user.token.unwrap_or_default(),
267    };
268    let prefs = match backend::DbPreferences::db_get(&pool, user.uuid).await {
269        Ok(data) => Preferences::from_db(&session_user, data),
270        Err(backend::BackendError::DataNotFound(_)) => {
271            let new_prefs = Preferences::new(&session_user);
272            save_preferences(
273                session.user_uuid,
274                session.username,
275                session.token,
276                new_prefs.clone(),
277            )
278            .await?;
279            new_prefs
280        }
281        Err(err) => return Err(err)?,
282    };
283
284    Ok(Preferences::from(prefs))
285}
286
287#[server(SavePreferences, "/api/session")]
288pub async fn save_preferences(
289    session_user_uuid: uuid::Uuid,
290    session_username: String,
291    session_token: uuid::Uuid,
292    preferences: Preferences,
293) -> Result<(), ServerFnError> {
294    let session = UserSession {
295        user_uuid: session_user_uuid,
296        username: session_username,
297        token: session_token,
298    };
299    let pool = extract_pool().await?;
300
301    let user = backend::auth::get_user(&pool, &session.username, session.token).await?;
302
303    let accent_color = if preferences.use_default_accent_color {
304        None
305    } else {
306        Some(preferences.accent_color.0)
307    };
308
309    let db_prefs = backend::DbPreferences {
310        user_uuid: user.uuid,
311        use_default_accent_color: preferences.use_default_accent_color,
312        accent_color,
313        show_separator: preferences.show_separator,
314        multi_select: preferences.multi_select,
315        save_on_pause: preferences.save_on_pause,
316    };
317    db_prefs
318        .db_set(&pool, &session.username, session.token)
319        .await?;
320
321    Ok(())
322}
323
324#[server]
325pub async fn set_session_cookie(session: UserSession) -> Result<(), ServerFnError> {
326    use actix_web::cookie::{self, time::Duration};
327    use actix_web::http::header;
328
329    let resp = expect_context::<leptos_actix::ResponseOptions>();
330
331    let mut cookie = cookie::Cookie::new("session", serde_json::to_string(&session)?);
332    cookie.set_max_age(Duration::days(30));
333    cookie.set_path("/");
334
335    resp.append_header(
336        header::SET_COOKIE,
337        header::HeaderValue::from_str(&cookie.to_string())?,
338    );
339
340    return Ok(());
341}
342
343#[server(ServerChangeAccountInfo, "/api")]
344async fn change_username(
345    old_username: String,
346    password: String,
347    new_username: String,
348) -> Result<UserSession, ServerFnError> {
349    let pool = api::extract_pool().await?;
350    let user =
351        backend::auth::change_username(&pool, &old_username, &new_username, &password).await?;
352
353    let session_user = UserSession {
354        user_uuid: user.uuid,
355        username: user.username.clone(),
356        token: user.token.unwrap(),
357    };
358
359    api::login_user(user.username, password, Some(String::new())).await?;
360    leptos_actix::redirect("/preferences");
361
362    return Ok(session_user);
363}