Skip to content

Commit a788302

Browse files
committed
Refactor DB
1 parent 5a1defb commit a788302

File tree

8 files changed

+514
-317
lines changed

8 files changed

+514
-317
lines changed

src/api/v1/event/car/mod.rs

Lines changed: 57 additions & 200 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
use crate::api::v1::auth::models::UserInfo;
2-
use crate::app::AppState;
2+
use crate::app::{AppState, MultipleRiderChange, RedisJob};
3+
use crate::db::car::{Car, CarData};
34
use crate::{api::v1::auth::models::UserData, auth::SessionAuth};
45
use actix_session::Session;
56
use actix_web::{
67
delete, get, post, put,
78
web::{self},
89
HttpResponse, Responder, Scope,
910
};
10-
use chrono::{DateTime, Utc};
11-
use serde::{Deserialize, Serialize};
11+
use redis_work_queue::{Item, WorkQueue};
1212
use serde_json::json;
13-
use sqlx::{query, query_as};
14-
use utoipa::{OpenApi, ToSchema};
13+
use sqlx::query;
14+
use utoipa::OpenApi;
1515

1616
use log::error;
1717

@@ -29,80 +29,10 @@ mod rider;
2929
update_car,
3030
delete_car
3131
),
32-
components(schemas(CarData, CreateCar, UserData))
32+
components(schemas(Car, CarData, UserData))
3333
)]
3434
pub struct ApiDoc;
3535

36-
#[derive(Serialize, Deserialize, sqlx::FromRow, ToSchema)]
37-
pub struct Car {
38-
pub id: i32,
39-
pub event_id: Option<i32>,
40-
pub driver: String,
41-
pub max_capacity: i32,
42-
pub departure_time: DateTime<Utc>,
43-
pub return_time: DateTime<Utc>,
44-
}
45-
46-
#[derive(Serialize, Deserialize, sqlx::FromRow, ToSchema)]
47-
#[serde(rename_all = "camelCase")]
48-
pub struct CarData {
49-
pub id: i32,
50-
pub event_id: Option<i32>,
51-
pub driver: UserData,
52-
pub riders: Option<Vec<UserData>>,
53-
pub max_capacity: i32,
54-
pub departure_time: DateTime<Utc>,
55-
pub return_time: DateTime<Utc>,
56-
pub comment: String,
57-
}
58-
59-
#[derive(Deserialize, ToSchema)]
60-
#[serde(rename_all = "camelCase")]
61-
pub struct CreateCar {
62-
pub max_capacity: i32,
63-
pub departure_time: DateTime<Utc>,
64-
pub return_time: DateTime<Utc>,
65-
pub comment: String,
66-
pub riders: Vec<String>,
67-
}
68-
69-
fn validate_car(car: &CreateCar, user: &String, other_cars: Vec<CarData>) -> Vec<String> {
70-
let mut out = Vec::new();
71-
if car.return_time < car.departure_time {
72-
out.push("Return time cannot be before departure.".to_string())
73-
}
74-
if car.departure_time < Utc::now() {
75-
out.push("Car cannot leave in the past.".to_string());
76-
}
77-
if car.max_capacity < 0 {
78-
out.push("Capacity must be greater than or equal to 0".to_string());
79-
}
80-
if car.riders.len() > (car.max_capacity as usize) {
81-
out.push("You have too many riders for your capacity.".to_string());
82-
}
83-
if car.riders.contains(user) {
84-
out.push("You cannot be a rider in your own car.".to_string());
85-
}
86-
let other_car_members: Vec<String> = other_cars
87-
.iter()
88-
.flat_map(|car| {
89-
let mut out = car.riders.as_ref().unwrap().clone();
90-
out.push(car.driver.clone());
91-
out
92-
})
93-
.map(|user| user.id)
94-
.collect();
95-
for rider in car.riders.iter() {
96-
if other_car_members.contains(&rider) {
97-
out.push(format!(
98-
"{} is already in another car or is a driver.",
99-
rider
100-
))
101-
}
102-
}
103-
out
104-
}
105-
10636
#[utoipa::path(
10737
params(
10838
("event_id" = i32, Path, description = "ID of the Event this Car Applies To")
@@ -115,50 +45,21 @@ fn validate_car(car: &CreateCar, user: &String, other_cars: Vec<CarData>) -> Vec
11545
async fn create_car(
11646
data: web::Data<AppState>,
11747
session: Session,
118-
car: web::Json<CreateCar>,
48+
car: web::Json<CarData>,
11949
path: web::Path<i32>,
12050
) -> impl Responder {
12151
let event_id: i32 = path.into_inner();
12252
let user_id = session.get::<UserInfo>("userinfo").unwrap().unwrap().id;
12353
let mut tx = data.db.begin().await.unwrap();
124-
let other_cars = query_as!(
125-
CarData,
126-
r#"SELECT car.id, car.event_id, car.max_capacity, car.departure_time, car.return_time, car.comment,
127-
(driverUser.id, driverUser.realm::text, driverUser.name, driverUser.email) AS "driver!: UserData",
128-
ARRAY_REMOVE(ARRAY_AGG(
129-
CASE WHEN riderUser.id IS NOT NULL
130-
THEN (riderUser.id, riderUser.realm::text, riderUser.name, riderUser.email)
131-
END
132-
), NULL) as "riders!: Vec<UserData>"
133-
FROM car
134-
JOIN users driverUser ON car.driver = driverUser.id
135-
LEFT JOIN rider on car.id = rider.car_id
136-
LEFT JOIN users riderUser ON rider.rider = riderUser.id
137-
WHERE event_id = $1 GROUP BY car.id, driverUser.id"#,
138-
event_id)
139-
.fetch_all(&mut *tx)
140-
.await.unwrap();
141-
let validate = validate_car(&car, &user_id, other_cars);
142-
if !validate.is_empty() {
54+
let other_cars = Car::select_all(event_id, &mut *tx).await.unwrap();
55+
if let Err(errs) = car.validate(&user_id, other_cars) {
56+
tx.rollback().await.unwrap();
14357
return HttpResponse::BadRequest().json(json!({
144-
"errors": validate
58+
"errors": errs
14559
}));
14660
}
14761

148-
let record = query!(
149-
r#"
150-
INSERT INTO car (event_id, driver, max_capacity, departure_time, return_time, comment)
151-
VALUES ($1, $2, $3, $4, $5, $6) RETURNING id
152-
"#,
153-
event_id,
154-
user_id,
155-
car.max_capacity,
156-
car.departure_time,
157-
car.return_time,
158-
car.comment
159-
)
160-
.fetch_one(&mut *tx)
161-
.await
62+
let record = Car::insert_new(event_id, user_id, &car, &mut *tx).await
16263
.unwrap();
16364

16465
let _ = query!(
@@ -172,6 +73,19 @@ async fn create_car(
17273
.await
17374
.unwrap();
17475
tx.commit().await.unwrap();
76+
let work_queue = WorkQueue::new(data.work_queue_key.clone());
77+
let item = Item::from_json_data(&RedisJob::RiderUpdate(MultipleRiderChange {
78+
event_id,
79+
car_id: record.id,
80+
old_riders: Vec::new(),
81+
new_riders: car.riders.clone()
82+
}))
83+
.unwrap();
84+
let mut redis = data.redis.lock().unwrap().clone();
85+
work_queue
86+
.add_item(&mut redis, &item)
87+
.await
88+
.expect("failed to add item to work queue");
17589
HttpResponse::Ok().json(record.id)
17690
}
17791

@@ -186,30 +100,12 @@ async fn create_car(
186100
#[get("/{car_id}", wrap = "SessionAuth")]
187101
async fn get_car(data: web::Data<AppState>, path: web::Path<(i32, i32)>) -> impl Responder {
188102
let (event_id, car_id) = path.into_inner();
189-
let result: Option<CarData> = query_as!(
190-
CarData,
191-
r#"SELECT car.id, car.event_id, car.max_capacity, car.departure_time, car.return_time, car.comment,
192-
(driverUser.id, driverUser.realm::text, driverUser.name, driverUser.email) AS "driver!: UserData",
193-
ARRAY_REMOVE(ARRAY_AGG(
194-
CASE WHEN riderUser.id IS NOT NULL
195-
THEN (riderUser.id, riderUser.realm::text, riderUser.name, riderUser.email)
196-
END
197-
), NULL) as "riders!: Vec<UserData>"
198-
FROM car
199-
JOIN users driverUser ON car.driver = driverUser.id
200-
LEFT JOIN rider on car.id = rider.car_id
201-
LEFT JOIN users riderUser ON rider.rider = riderUser.id
202-
WHERE event_id = $1 AND car.id = $2 GROUP BY car.id, driverUser.id"#,
203-
event_id,
204-
car_id
205-
)
206-
.fetch_optional(&data.db)
207-
.await
208-
.unwrap_or(None);
103+
let result = Car::select_one(event_id, car_id, &data.db).await;
209104

210105
match result {
211-
Some(car) => HttpResponse::Ok().json(car),
212-
None => HttpResponse::NotFound().body("Car not found"),
106+
Ok(Some(car)) => HttpResponse::Ok().json(car),
107+
Ok(None) => HttpResponse::NotFound().body("Car not found"),
108+
Err(_) => HttpResponse::InternalServerError().body("Failed to get Car")
213109
}
214110
}
215111

@@ -224,23 +120,7 @@ async fn get_car(data: web::Data<AppState>, path: web::Path<(i32, i32)>) -> impl
224120
#[get("/", wrap = "SessionAuth")]
225121
async fn get_all_cars(data: web::Data<AppState>, path: web::Path<i32>) -> impl Responder {
226122
let event_id: i32 = path.into_inner();
227-
let result = query_as!(
228-
CarData,
229-
r#"SELECT car.id, car.event_id, car.max_capacity, car.departure_time, car.return_time, car.comment,
230-
(driverUser.id, driverUser.realm::text, driverUser.name, driverUser.email) AS "driver!: UserData",
231-
ARRAY_REMOVE(ARRAY_AGG(
232-
CASE WHEN riderUser.id IS NOT NULL
233-
THEN (riderUser.id, riderUser.realm::text, riderUser.name, riderUser.email)
234-
END
235-
), NULL) as "riders!: Vec<UserData>"
236-
FROM car
237-
JOIN users driverUser ON car.driver = driverUser.id
238-
LEFT JOIN rider on car.id = rider.car_id
239-
LEFT JOIN users riderUser ON rider.rider = riderUser.id
240-
WHERE event_id = $1 GROUP BY car.id, driverUser.id"#,
241-
event_id)
242-
.fetch_all(&data.db)
243-
.await;
123+
let result = Car::select_all(event_id, &data.db).await;
244124

245125
match result {
246126
Ok(cars) => HttpResponse::Ok().json(cars),
@@ -264,62 +144,29 @@ async fn update_car(
264144
data: web::Data<AppState>,
265145
session: Session,
266146
path: web::Path<(i32, i32)>,
267-
car: web::Json<CreateCar>,
147+
car: web::Json<CarData>,
268148
) -> impl Responder {
269149
let (event_id, car_id) = path.into_inner();
270150
let user_id = session.get::<UserInfo>("userinfo").unwrap().unwrap().id;
271151
let mut tx = data.db.begin().await.unwrap();
272-
let other_cars = query_as!(
273-
CarData,
274-
r#"SELECT car.id, car.event_id, car.max_capacity, car.departure_time, car.return_time, car.comment,
275-
(driverUser.id, driverUser.realm::text, driverUser.name, driverUser.email) AS "driver!: UserData",
276-
ARRAY_REMOVE(ARRAY_AGG(
277-
CASE WHEN riderUser.id IS NOT NULL
278-
THEN (riderUser.id, riderUser.realm::text, riderUser.name, riderUser.email)
279-
END
280-
), NULL) as "riders!: Vec<UserData>"
281-
FROM car
282-
JOIN users driverUser ON car.driver = driverUser.id
283-
LEFT JOIN rider on car.id = rider.car_id
284-
LEFT JOIN users riderUser ON rider.rider = riderUser.id
285-
WHERE event_id = $1 AND car_id != $2 GROUP BY car.id, driverUser.id"#,
286-
event_id, car_id)
287-
.fetch_all(&mut *tx)
288-
.await.unwrap();
289-
let validate = validate_car(&car, &user_id, other_cars);
290-
if !validate.is_empty() {
152+
let other_cars = Car::select_all(event_id, &mut *tx).await.unwrap().into_iter().filter(|car| car.id != car_id).collect();
153+
if let Err(errs) = car.validate(&user_id, other_cars) {
154+
tx.rollback().await.unwrap();
291155
return HttpResponse::BadRequest().json(json!({
292-
"errors": validate
156+
"errors": errs
293157
}));
294158
}
295-
let updated = query!(
296-
r#"
297-
UPDATE car SET
298-
max_capacity = COALESCE($1, max_capacity),
299-
departure_time = COALESCE($2, departure_time),
300-
return_time = COALESCE($3, return_time),
301-
comment = COALESCE($4, comment)
302-
WHERE event_id = $5 AND id = $6 AND driver = $7 RETURNING id
303-
"#,
304-
car.max_capacity,
305-
car.departure_time,
306-
car.return_time,
307-
car.comment,
308-
event_id,
309-
car_id,
310-
user_id
311-
)
312-
.fetch_optional(&mut *tx)
313-
.await;
159+
let updated = Car::update(car_id, event_id, user_id, &car, &mut *tx).await;
314160

315161
match updated {
316162
Ok(Some(_)) => {}
317163
Ok(None) => {
318164
tx.rollback().await.unwrap();
319165
return HttpResponse::NotFound().body("Car not found or you are not the driver.");
320166
}
321-
Err(_) => {
167+
Err(err) => {
322168
tx.rollback().await.unwrap();
169+
error!("{}", err);
323170
return HttpResponse::InternalServerError().body("Failed to update car");
324171
}
325172
}
@@ -348,6 +195,19 @@ async fn update_car(
348195
.unwrap();
349196
tx.commit().await.unwrap();
350197

198+
let work_queue = WorkQueue::new(data.work_queue_key.clone());
199+
let item = Item::from_json_data(&RedisJob::RiderUpdate(MultipleRiderChange {
200+
event_id,
201+
car_id,
202+
old_riders: current_riders,
203+
new_riders: car.riders.clone()
204+
}))
205+
.unwrap();
206+
let mut redis = data.redis.lock().unwrap().clone();
207+
work_queue
208+
.add_item(&mut redis, &item)
209+
.await
210+
.expect("failed to add item to work queue");
351211
HttpResponse::Ok().body("Car updated successfully")
352212
}
353213

@@ -366,20 +226,17 @@ async fn delete_car(
366226
path: web::Path<(i32, i32)>,
367227
) -> impl Responder {
368228
let (event_id, car_id) = path.into_inner();
229+
let user_id = session.get::<UserInfo>("userinfo").unwrap().unwrap().id;
369230

370-
let deleted = query!(
371-
"DELETE FROM car WHERE event_id = $1 AND id = $2 AND driver = $3 RETURNING id",
372-
event_id,
373-
car_id,
374-
session.get::<UserInfo>("userinfo").unwrap().unwrap().id
375-
)
376-
.fetch_optional(&data.db)
377-
.await;
231+
let deleted = Car::delete(car_id, event_id, user_id, &data.db).await;
378232

379233
match deleted {
380234
Ok(Some(_)) => HttpResponse::Ok().body("Car deleted"),
381235
Ok(None) => HttpResponse::NotFound().body("Car not found or you are not the driver."),
382-
Err(_) => HttpResponse::InternalServerError().body("Failed to delete car"),
236+
Err(err) => {
237+
error!("{}", err);
238+
HttpResponse::InternalServerError().body("Failed to delete car")
239+
},
383240
}
384241
}
385242

0 commit comments

Comments
 (0)