dogy_backend_api/service/users/
handlers.rs1use 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 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(¤t_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}