dogy_backend_api/service/users/
handlers.rs

1use axum::extract::State;
2use axum::{extract::Extension, Json};
3use serde_json::{json, Value};
4use sqlx::QueryBuilder;
5use uuid::Uuid;
6
7use crate::middleware::auth::layer::CurrentUser;
8use crate::service::users::models::JoinedFullUser;
9use crate::Result;
10use crate::{AppState, PayloadJson};
11
12use super::models::{
13    FullUser, User, UserNotification, UserNotificationUpdate, UserSubscription,
14    UserSubscriptionUpdate, UserUpdate,
15};
16
17pub async fn create_user(
18    Extension(current_user): Extension<CurrentUser>,
19    State(state): State<AppState>,
20    Json(mut user): Json<FullUser>,
21) -> Result<Json<FullUser>> {
22    let conn = &*state.db;
23    let mut txn = conn.begin().await.map_err(super::Error::from)?;
24
25    // Inserting Base User
26    let user_id: (Uuid,) = sqlx::query_as(
27        r#"INSERT INTO users (name, external_id, timezone, gender, has_onboarded)
28        VALUES ($1, $2, $3, $4, $5) RETURNING id;
29        "#,
30    )
31    .bind(&user.base.name)
32    .bind(&current_user.user_id)
33    .bind(&user.base.timezone)
34    .bind(&user.base.gender)
35    .bind(user.base.has_onboarded)
36    .fetch_one(&mut *txn)
37    .await
38    .map_err(super::Error::from)?;
39
40    sqlx::query(
41        r#"INSERT
42        INTO user_subscriptions (user_id, trial_start_date, subscription_type, is_trial_mode)
43        VALUES ($1, $2, $3, $4);
44        "#,
45    )
46    .bind(user_id.0)
47    .bind(user.subscription.trial_start_date)
48    .bind(&user.subscription.subscription_type)
49    .bind(user.subscription.is_trial_mode)
50    .execute(&mut *txn)
51    .await
52    .map_err(super::Error::from)?;
53
54    sqlx::query(
55        r#"INSERT
56        INTO user_notifications (user_id, enabled, is_registered, daily_enabled, playtime_enabled)
57        VALUES ($1, $2, $3, $4, $5);
58        "#,
59    )
60    .bind(user_id.0)
61    .bind(user.notifications.enabled)
62    .bind(user.notifications.is_registered)
63    .bind(user.notifications.daily_enabled)
64    .bind(user.notifications.playtime_enabled)
65    .execute(&mut *txn)
66    .await
67    .map_err(super::Error::from)?;
68
69    txn.commit().await.map_err(super::Error::from)?;
70
71    user.base.external_id = current_user.user_id;
72
73    Ok(Json(user))
74}
75
76pub async fn get_user(
77    State(state): State<AppState>,
78    Extension(current_user): Extension<CurrentUser>,
79) -> Result<Json<FullUser>> {
80    let conn = &*state.db;
81    let query = r#"
82    SELECT u.*, us.trial_start_date, us.subscription_type, us.is_trial_mode,
83        un.enabled, un.is_registered, un.daily_enabled, un.playtime_enabled
84    FROM users u
85    LEFT JOIN user_subscriptions us ON u.id = us.user_id
86    LEFT JOIN user_notifications un ON u.id = un.user_id
87    WHERE u.id = $1;
88    "#;
89
90    let user_info = sqlx::query_as::<_, JoinedFullUser>(query)
91        .bind(current_user.internal_id.unwrap())
92        .fetch_one(conn)
93        .await
94        .map_err(super::Error::from)?;
95
96    let full_user = FullUser {
97        base: User {
98            external_id: user_info.external_id,
99            name: user_info.name,
100            timezone: user_info.timezone,
101            gender: user_info.gender,
102            has_onboarded: user_info.has_onboarded,
103        },
104        notifications: UserNotification {
105            enabled: user_info.enabled,
106            is_registered: user_info.is_registered,
107            daily_enabled: user_info.daily_enabled,
108            playtime_enabled: user_info.playtime_enabled,
109        },
110        subscription: UserSubscription {
111            trial_start_date: user_info.trial_start_date,
112            subscription_type: user_info.subscription_type,
113            is_trial_mode: user_info.is_trial_mode,
114        },
115    };
116
117    Ok(Json(full_user))
118}
119
120pub async fn update_user_base(
121    State(state): State<AppState>,
122    Extension(current_user): Extension<CurrentUser>,
123    payload: PayloadJson<UserUpdate>,
124) -> Result<Json<UserUpdate>> {
125    let Json(user) = payload?;
126    let conn = &*state.db;
127    let query = r#"
128    UPDATE users
129    SET
130        name = COALESCE($2, name),
131        timezone = COALESCE($3, timezone),
132        gender = COALESCE($4, gender),
133        has_onboarded = COALESCE($5, has_onboarded)
134    WHERE id = $1;
135        "#;
136
137    sqlx::query(query)
138        .bind(current_user.internal_id.unwrap())
139        .bind(&user.name)
140        .bind(&user.timezone)
141        .bind(&user.gender)
142        .bind(user.has_onboarded)
143        .execute(conn)
144        .await
145        .map_err(super::Error::from)?;
146
147    Ok(Json(user))
148}
149
150pub async fn update_user_subscription(
151    State(state): State<AppState>,
152    Extension(current_user): Extension<CurrentUser>,
153    payload: PayloadJson<UserSubscriptionUpdate>,
154) -> Result<Json<UserSubscriptionUpdate>> {
155    let Json(user_sub) = payload?;
156    let conn = &*state.db;
157    let mut query_builder = QueryBuilder::new(
158        r#"
159        UPDATE user_subscriptions SET
160        subscription_type = COALESCE(
161        "#,
162    );
163
164    query_builder
165        .push_bind(&user_sub.subscription_type)
166        .push(r#", subscription_type), is_trial_mode = COALESCE( "#)
167        .push_bind(user_sub.is_trial_mode)
168        .push(", is_trial_mode) ");
169
170    if let Some(trial_date) = user_sub.trial_start_date {
171        match trial_date {
172            Some(date) => {
173                query_builder.push(",trial_start_date = ").push_bind(date);
174            }
175            None => {
176                query_builder.push(",trial_start_date = NULL");
177            }
178        }
179    }
180
181    query_builder
182        .push("\nWHERE user_id = ")
183        .push_bind(current_user.internal_id.unwrap())
184        .push(";");
185
186    let query = query_builder.build();
187    query.execute(conn).await.map_err(super::Error::from)?;
188
189    Ok(Json(user_sub))
190}
191
192pub async fn update_user_notification(
193    State(state): State<AppState>,
194    Extension(current_user): Extension<CurrentUser>,
195    payload: PayloadJson<UserNotificationUpdate>,
196) -> Result<Json<UserNotificationUpdate>> {
197    let Json(user_notif) = payload?;
198    let conn = &*state.db;
199    let query = r#"
200    UPDATE user_notifications
201    SET
202        enabled = COALESCE($2, enabled),
203        is_registered = COALESCE($3, is_registered),
204        daily_enabled = COALESCE($4, daily_enabled),
205        playtime_enabled = COALESCE($5, playtime_enabled)
206    WHERE user_id = $1;
207    "#;
208
209    sqlx::query(query)
210        .bind(current_user.internal_id.unwrap())
211        .bind(user_notif.enabled)
212        .bind(user_notif.is_registered)
213        .bind(user_notif.daily_enabled)
214        .bind(user_notif.playtime_enabled)
215        .execute(conn)
216        .await
217        .map_err(super::Error::from)?;
218
219    Ok(Json(user_notif))
220}
221
222pub async fn delete_user(
223    State(state): State<AppState>,
224    Extension(current_user): Extension<CurrentUser>,
225) -> Result<Json<Value>> {
226    let conn = &*state.db;
227
228    sqlx::query("DELETE FROM users WHERE id = $1;")
229        .bind(current_user.internal_id.unwrap())
230        .execute(conn)
231        .await
232        .map_err(super::Error::from)?;
233
234    Ok(Json(json!({ "message": "User deleted successfully" })))
235}