11use 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 } ;
34use crate :: { api:: v1:: auth:: models:: UserData , auth:: SessionAuth } ;
45use actix_session:: Session ;
56use 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 } ;
1212use serde_json:: json;
13- use sqlx:: { query, query_as } ;
14- use utoipa:: { OpenApi , ToSchema } ;
13+ use sqlx:: query;
14+ use utoipa:: OpenApi ;
1515
1616use 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) ]
3434pub 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
11545async 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" ) ]
187101async 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" ) ]
225121async 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