1use anyhow::Result;
6use chrono::Utc;
7use rocket::http::Status;
8use rocket::{State, get, post};
9use serde::{Deserialize, Serialize};
10use sqlx::PgPool;
11use uuid::Uuid;
12
13use super::Token;
14
15#[derive(sqlx::FromRow, Serialize)]
17pub struct SentInviteInfo {
18 id: i32,
20 device_id: uuid::Uuid,
22 sender_id: String,
24 receiver_id: String,
26 receiver_name: String,
28 receiver_email: String,
30 status: i32,
32 expiry_timestamp: i64,
34 created_at: chrono::DateTime<chrono::Utc>,
36}
37
38#[derive(sqlx::FromRow, Serialize)]
40pub struct ReceivedInviteInfo {
41 id: i32,
43 device_id: uuid::Uuid,
45 sender_id: String,
47 receiver_id: String,
49 sender_name: String,
51 sender_email: String,
53 status: i32,
55 expiry_timestamp: i64,
57 created_at: chrono::DateTime<chrono::Utc>,
59}
60
61#[derive(Deserialize)]
63pub struct CreateInviteRequest {
64 receiver_email: String,
66 device_id: String,
68 expiry_duration: String,
70}
71
72#[derive(Deserialize)]
74pub struct UpdateInviteRequest {
75 expiry_duration: String,
77}
78
79#[post("/create_invite", data = "<request>")]
81pub async fn create_invite(
82 token: Token,
83 request: rocket::serde::json::Json<CreateInviteRequest>,
84 db_pool: &State<PgPool>,
85) -> Result<String, Status> {
86 println!(
87 "DEBUG: create_invite called with receiver_email: {}, device_id: {}",
88 request.receiver_email, request.device_id
89 );
90
91 let device_uuid = match Uuid::parse_str(&request.device_id) {
93 Ok(uuid) => uuid,
94 Err(_) => {
95 return Err(Status::BadRequest);
96 }
97 };
98
99 let user_row: Option<(String,)> =
101 sqlx::query_as("SELECT firebase_uid FROM users WHERE current_token = $1")
102 .bind(&token.0)
103 .fetch_optional(&**db_pool)
104 .await
105 .map_err(|_| Status::InternalServerError)?;
106 let sender_id = match user_row {
107 Some((uid,)) => uid,
108 None => {
109 return Err(Status::Unauthorized);
110 }
111 };
112
113 let device_row: Option<(Option<String>,)> =
115 sqlx::query_as("SELECT user_id FROM devices WHERE uuid = $1")
116 .bind(device_uuid)
117 .fetch_optional(&**db_pool)
118 .await
119 .map_err(|_| Status::InternalServerError)?;
120 let owner_id = if let Some((Some(owner),)) = device_row {
121 owner
122 } else {
123 return Err(Status::NotFound);
124 };
125 if owner_id != sender_id {
126 println!(
127 "DEBUG: User {} does not own device {} (owned by {})",
128 sender_id, request.device_id, owner_id
129 );
130 return Err(Status::Forbidden);
131 }
132
133 let receiver_row: Option<(String,)> =
135 sqlx::query_as("SELECT firebase_uid FROM users WHERE email = $1")
136 .bind(&request.receiver_email)
137 .fetch_optional(&**db_pool)
138 .await
139 .map_err(|_| Status::InternalServerError)?;
140 let receiver_id = if let Some((uid,)) = receiver_row {
141 uid
142 } else {
143 println!(
144 "DEBUG: Receiver not found for email: {}",
145 request.receiver_email
146 );
147 return Err(Status::NotFound);
148 };
149
150 let existing_invite: Option<(i32,)> = sqlx::query_as(
152 "SELECT id FROM invites WHERE device_id = $1 AND receiver_id = $2 AND status = 0",
153 )
154 .bind(device_uuid) .bind(&receiver_id)
156 .fetch_optional(&**db_pool)
157 .await
158 .map_err(|_| Status::InternalServerError)?;
159 if existing_invite.is_some() {
160 return Err(Status::Conflict);
161 }
162
163 let now = Utc::now();
165 let expiry_timestamp = calculate_expiry_timestamp(now, &request.expiry_duration);
166
167 let invite_id: i32 = sqlx::query_scalar(
169 "INSERT INTO invites (device_id, sender_id, receiver_id, expiry_timestamp) VALUES ($1, $2, $3, $4) RETURNING id"
170 )
171 .bind(device_uuid) .bind(&sender_id)
173 .bind(&receiver_id)
174 .bind(expiry_timestamp)
175 .fetch_one(&**db_pool)
176 .await
177 .map_err(|_| {
178 Status::InternalServerError
179 })?;
180
181 Ok(serde_json::json!({
182 "invite_id": invite_id,
183 "message": "Invite created successfully"
184 })
185 .to_string())
186}
187
188#[get("/invites")]
190pub async fn get_invites(token: Token, db_pool: &State<PgPool>) -> Result<String, Status> {
191 let user_row: Option<(String,)> =
193 sqlx::query_as("SELECT firebase_uid FROM users WHERE current_token = $1")
194 .bind(&token.0)
195 .fetch_optional(&**db_pool)
196 .await
197 .map_err(|_| Status::InternalServerError)?;
198 let user_id = match user_row {
199 Some((uid,)) => uid,
200 None => {
201 return Err(Status::Unauthorized);
202 }
203 };
204
205 let sent_invites: Vec<SentInviteInfo> =
207 sqlx::query_as("SELECT i.id, i.device_id, i.sender_id, i.receiver_id, ru.name as receiver_name, ru.email as receiver_email, i.status, i.expiry_timestamp, i.created_at FROM invites i LEFT JOIN users ru ON i.receiver_id = ru.firebase_uid WHERE i.sender_id = $1")
208 .bind(&user_id)
209 .fetch_all(&**db_pool)
210 .await
211 .map_err(|_| {
212 Status::InternalServerError
213 })?;
214
215 let received_invites: Vec<ReceivedInviteInfo> =
217 sqlx::query_as("SELECT i.id, i.device_id, i.sender_id, i.receiver_id, su.name as sender_name, su.email as sender_email, i.status, i.expiry_timestamp, i.created_at FROM invites i LEFT JOIN users su ON i.sender_id = su.firebase_uid WHERE i.receiver_id = $1")
218 .bind(&user_id)
219 .fetch_all(&**db_pool)
220 .await
221 .map_err(|_| {
222 Status::InternalServerError
223 })?;
224
225 let sent: Vec<serde_json::Value> = sent_invites
226 .into_iter()
227 .map(|invite| serde_json::to_value(invite).unwrap())
228 .collect();
229
230 let received: Vec<serde_json::Value> = received_invites
231 .into_iter()
232 .map(|invite| serde_json::to_value(invite).unwrap())
233 .collect();
234
235 Ok(serde_json::json!({
236 "sent": sent,
237 "received": received
238 })
239 .to_string())
240}
241
242#[post("/accept_invite/<invite_id>")]
244pub async fn accept_invite(
245 token: Token,
246 invite_id: i32,
247 db_pool: &State<PgPool>,
248) -> Result<(), Status> {
249 let user_row: Option<(String,)> =
251 sqlx::query_as("SELECT firebase_uid FROM users WHERE current_token = $1")
252 .bind(&token.0)
253 .fetch_optional(&**db_pool)
254 .await
255 .map_err(|_| Status::InternalServerError)?;
256 let user_id = match user_row {
257 Some((uid,)) => uid,
258 None => return Err(Status::Unauthorized),
259 };
260
261 let rows_affected = sqlx::query(
263 "UPDATE invites SET status = 1 WHERE id = $1 AND receiver_id = $2 AND status = 0",
264 )
265 .bind(invite_id)
266 .bind(&user_id)
267 .execute(&**db_pool)
268 .await
269 .map_err(|_| Status::InternalServerError)?
270 .rows_affected();
271
272 if rows_affected == 0 {
273 Err(Status::NotFound)
274 } else {
275 Ok(())
276 }
277}
278
279#[post("/reject_invite/<invite_id>")]
281pub async fn reject_invite(
282 token: Token,
283 invite_id: i32,
284 db_pool: &State<PgPool>,
285) -> Result<(), Status> {
286 let user_row: Option<(String,)> =
288 sqlx::query_as("SELECT firebase_uid FROM users WHERE current_token = $1")
289 .bind(&token.0)
290 .fetch_optional(&**db_pool)
291 .await
292 .map_err(|_| Status::InternalServerError)?;
293 let user_id = match user_row {
294 Some((uid,)) => uid,
295 None => {
296 return Err(Status::Unauthorized);
297 }
298 };
299
300 println!(
302 "DEBUG: Deleting invite with id: {}, receiver_id: {}",
303 invite_id, user_id
304 );
305 let rows_affected = sqlx::query("DELETE FROM invites WHERE id = $1 AND receiver_id = $2")
306 .bind(invite_id)
307 .bind(&user_id)
308 .execute(&**db_pool)
309 .await
310 .map_err(|_| Status::InternalServerError)?
311 .rows_affected();
312
313 if rows_affected == 0 {
314 return Err(Status::NotFound);
315 }
316
317 println!(
318 "DEBUG: Invite deleted successfully, rows affected: {}",
319 rows_affected
320 );
321 Ok(())
322}
323
324#[post("/cancel_invite/<invite_id>")]
326pub async fn cancel_invite(
327 token: Token,
328 invite_id: i32,
329 db_pool: &State<PgPool>,
330) -> Result<(), Status> {
331 let user_row: Option<(String,)> =
333 sqlx::query_as("SELECT firebase_uid FROM users WHERE current_token = $1")
334 .bind(&token.0)
335 .fetch_optional(&**db_pool)
336 .await
337 .map_err(|_| Status::InternalServerError)?;
338 let user_id = match user_row {
339 Some((uid,)) => uid,
340 None => return Err(Status::Unauthorized),
341 };
342
343 let rows_affected = sqlx::query("DELETE FROM invites WHERE id = $1 AND sender_id = $2")
345 .bind(invite_id)
346 .bind(&user_id)
347 .execute(&**db_pool)
348 .await
349 .map_err(|_| Status::InternalServerError)?;
350
351 if rows_affected.rows_affected() == 0 {
352 return Err(Status::NotFound);
353 }
354
355 Ok(())
356}
357
358#[post("/update_invite/<invite_id>", data = "<request>")]
360pub async fn update_invite(
361 token: Token,
362 invite_id: i32,
363 request: rocket::serde::json::Json<UpdateInviteRequest>,
364 db_pool: &State<PgPool>,
365) -> Result<(), Status> {
366 let user_row: Option<(String,)> =
368 sqlx::query_as("SELECT firebase_uid FROM users WHERE current_token = $1")
369 .bind(&token.0)
370 .fetch_optional(&**db_pool)
371 .await
372 .map_err(|_| Status::InternalServerError)?;
373 let user_id = match user_row {
374 Some((uid,)) => uid,
375 None => return Err(Status::Unauthorized),
376 };
377
378 let now = Utc::now();
380 let new_expiry_timestamp = calculate_expiry_timestamp(now, &request.expiry_duration);
381
382 let rows_affected =
384 sqlx::query("UPDATE invites SET expiry_timestamp = $1 WHERE id = $2 AND sender_id = $3")
385 .bind(new_expiry_timestamp)
386 .bind(invite_id)
387 .bind(&user_id)
388 .execute(&**db_pool)
389 .await
390 .map_err(|_| Status::InternalServerError)?;
391
392 if rows_affected.rows_affected() == 0 {
393 return Err(Status::NotFound);
394 }
395
396 Ok(())
397}
398
399fn calculate_expiry_timestamp(base_time: chrono::DateTime<chrono::Utc>, duration: &str) -> i64 {
401 let duration = match duration {
402 "2_dias" => chrono::Duration::days(2),
403 "1_semana" => chrono::Duration::days(7),
404 "2_semanas" => chrono::Duration::days(14),
405 "1_mes" => chrono::Duration::days(30),
406 "permanente" => chrono::Duration::days(36500), _ => chrono::Duration::days(7),
408 };
409 (base_time + duration).timestamp_millis()
410}