@@ -11,10 +11,7 @@ use infera_management_core::{
1111 UserEmail , UserEmailVerificationToken , UserSession ,
1212 } ,
1313 error:: Error as CoreError ,
14- hash_password, verify_password, IdGenerator , OrganizationMemberRepository ,
15- OrganizationRepository , UserEmailRepository , UserEmailVerificationTokenRepository ,
16- UserPasswordResetToken , UserPasswordResetTokenRepository , UserRepository ,
17- UserSessionRepository ,
14+ hash_password, verify_password, IdGenerator , RepositoryContext , UserPasswordResetToken ,
1815} ;
1916use infera_management_grpc:: ServerApiClient ;
2017use infera_management_storage:: Backend ;
@@ -258,6 +255,9 @@ pub async fn register(
258255 jar : CookieJar ,
259256 Json ( payload) : Json < RegisterRequest > ,
260257) -> Result < ( CookieJar , Json < RegisterResponse > ) > {
258+ // Initialize repository context
259+ let repos = RepositoryContext :: new ( ( * state. storage ) . clone ( ) ) ;
260+
261261 // Validate inputs
262262 if payload. name . trim ( ) . is_empty ( ) {
263263 return Err ( CoreError :: Validation ( "Name cannot be empty" . to_string ( ) ) . into ( ) ) ;
@@ -268,16 +268,14 @@ pub async fn register(
268268 }
269269
270270 // Check if email is already in use
271- let email_repo = UserEmailRepository :: new ( ( * state. storage ) . clone ( ) ) ;
272- if email_repo. is_email_in_use ( & payload. email ) . await ? {
271+ if repos. user_email . is_email_in_use ( & payload. email ) . await ? {
273272 return Err (
274273 CoreError :: Validation ( format ! ( "Email '{}' is already in use" , payload. email) ) . into ( ) ,
275274 ) ;
276275 }
277276
278277 // Check if name is available
279- let user_repo = UserRepository :: new ( ( * state. storage ) . clone ( ) ) ;
280- if !user_repo. is_name_available ( & payload. name ) . await ? {
278+ if !repos. user . is_name_available ( & payload. name ) . await ? {
281279 return Err (
282280 CoreError :: Validation ( format ! ( "Name '{}' is already taken" , payload. name) ) . into ( ) ,
283281 ) ;
@@ -294,18 +292,20 @@ pub async fn register(
294292 // Create user
295293 let mut user = User :: new ( user_id, payload. name . clone ( ) , Some ( password_hash) ) ?;
296294 user. accept_tos ( ) ; // Auto-accept TOS on registration
297- user_repo . create ( user) . await ?;
295+ repos . user . create ( user) . await ?;
298296
299297 // Create email (unverified)
300298 let email = UserEmail :: new ( email_id, user_id, payload. email . clone ( ) , true ) ?;
301- email_repo . create ( email. clone ( ) ) . await ?;
299+ repos . user_email . create ( email. clone ( ) ) . await ?;
302300
303301 // Create email verification token
304302 let token_id = IdGenerator :: next_id ( ) ;
305303 let token_string = UserEmailVerificationToken :: generate_token ( ) ;
306304 let verification_token = UserEmailVerificationToken :: new ( token_id, email_id, token_string) ?;
307- let verification_repo = UserEmailVerificationTokenRepository :: new ( ( * state. storage ) . clone ( ) ) ;
308- verification_repo. create ( verification_token. clone ( ) ) . await ?;
305+ repos
306+ . user_email_verification_token
307+ . create ( verification_token. clone ( ) )
308+ . await ?;
309309
310310 // Send verification email (fire-and-forget - don't block registration)
311311 if let Some ( email_service) = & state. email_service {
@@ -348,22 +348,19 @@ pub async fn register(
348348
349349 // Create session
350350 let session = UserSession :: new ( session_id, user_id, SessionType :: Web , None , None ) ;
351- let session_repo = UserSessionRepository :: new ( ( * state. storage ) . clone ( ) ) ;
352- session_repo. create ( session) . await ?;
351+ repos. user_session . create ( session) . await ?;
353352
354353 // Create default organization with same name as user
355354 let org_id = IdGenerator :: next_id ( ) ;
356355 let member_id = IdGenerator :: next_id ( ) ;
357356
358357 let organization =
359358 Organization :: new ( org_id, payload. name . clone ( ) , OrganizationTier :: TierDevV1 ) ?;
360- let org_repo = OrganizationRepository :: new ( ( * state. storage ) . clone ( ) ) ;
361- org_repo. create ( organization) . await ?;
359+ repos. org . create ( organization) . await ?;
362360
363361 // Create organization member (owner role)
364362 let member = OrganizationMember :: new ( member_id, org_id, user_id, OrganizationRole :: Owner ) ;
365- let member_repo = OrganizationMemberRepository :: new ( ( * state. storage ) . clone ( ) ) ;
366- member_repo. create ( member) . await ?;
363+ repos. org_member . create ( member) . await ?;
367364
368365 // Set session cookie
369366 let cookie = Cookie :: build ( ( SESSION_COOKIE_NAME , session_id. to_string ( ) ) )
@@ -398,16 +395,18 @@ pub async fn login(
398395 jar : CookieJar ,
399396 Json ( payload) : Json < LoginRequest > ,
400397) -> Result < ( CookieJar , Json < LoginResponse > ) > {
398+ let repos = RepositoryContext :: new ( ( * state. storage ) . clone ( ) ) ;
399+
401400 // Find user by email
402- let email_repo = UserEmailRepository :: new ( ( * state . storage ) . clone ( ) ) ;
403- let email = email_repo
401+ let email = repos
402+ . user_email
404403 . get_by_email ( & payload. email )
405404 . await ?
406405 . ok_or_else ( || CoreError :: Auth ( "Invalid email or password" . to_string ( ) ) ) ?;
407406
408407 // Get user
409- let user_repo = UserRepository :: new ( ( * state . storage ) . clone ( ) ) ;
410- let user = user_repo
408+ let user = repos
409+ . user
411410 . get ( email. user_id )
412411 . await ?
413412 . ok_or_else ( || CoreError :: Auth ( "Invalid email or password" . to_string ( ) ) ) ?;
@@ -422,8 +421,7 @@ pub async fn login(
422421 // Create session
423422 let session_id = IdGenerator :: next_id ( ) ;
424423 let session = UserSession :: new ( session_id, user. id , SessionType :: Web , None , None ) ;
425- let session_repo = UserSessionRepository :: new ( ( * state. storage ) . clone ( ) ) ;
426- session_repo. create ( session) . await ?;
424+ repos. user_session . create ( session) . await ?;
427425
428426 // Set session cookie
429427 let cookie = Cookie :: build ( ( SESSION_COOKIE_NAME , session_id. to_string ( ) ) )
@@ -458,10 +456,10 @@ pub async fn logout(
458456 // Get session ID from cookie
459457 if let Some ( cookie) = jar. get ( SESSION_COOKIE_NAME ) {
460458 if let Ok ( session_id) = cookie. value ( ) . parse :: < i64 > ( ) {
459+ let repos = RepositoryContext :: new ( ( * state. storage ) . clone ( ) ) ;
461460 // Revoke session
462- let session_repo = UserSessionRepository :: new ( ( * state. storage ) . clone ( ) ) ;
463461 // Ignore errors if session doesn't exist
464- let _ = session_repo . revoke ( session_id) . await ;
462+ let _ = repos . user_session . revoke ( session_id) . await ;
465463 }
466464 }
467465
@@ -501,10 +499,11 @@ pub async fn verify_email(
501499 State ( state) : State < AppState > ,
502500 Json ( payload) : Json < VerifyEmailRequest > ,
503501) -> Result < Json < VerifyEmailResponse > > {
504- let token_repo = UserEmailVerificationTokenRepository :: new ( ( * state. storage ) . clone ( ) ) ;
502+ let repos = RepositoryContext :: new ( ( * state. storage ) . clone ( ) ) ;
505503
506504 // Get token
507- let mut token = token_repo
505+ let mut token = repos
506+ . user_email_verification_token
508507 . get_by_token ( & payload. token )
509508 . await ?
510509 . ok_or_else ( || {
@@ -519,8 +518,8 @@ pub async fn verify_email(
519518 }
520519
521520 // Get the email
522- let email_repo = UserEmailRepository :: new ( ( * state . storage ) . clone ( ) ) ;
523- let mut email = email_repo
521+ let mut email = repos
522+ . user_email
524523 . get ( token. user_email_id )
525524 . await ?
526525 . ok_or_else ( || CoreError :: NotFound ( "Email not found" . to_string ( ) ) ) ?;
@@ -535,11 +534,11 @@ pub async fn verify_email(
535534
536535 // Mark email as verified
537536 email. verify ( ) ;
538- email_repo . update ( email. clone ( ) ) . await ?;
537+ repos . user_email . update ( email. clone ( ) ) . await ?;
539538
540539 // Mark token as used
541540 token. mark_used ( ) ;
542- token_repo . update ( token) . await ?;
541+ repos . user_email_verification_token . update ( token) . await ?;
543542
544543 Ok ( Json ( VerifyEmailResponse {
545544 message : "Email verified successfully" . to_string ( ) ,
@@ -570,11 +569,11 @@ pub async fn request_password_reset(
570569 State ( state) : State < AppState > ,
571570 Json ( payload) : Json < PasswordResetRequestRequest > ,
572571) -> Result < Json < PasswordResetRequestResponse > > {
573- let email_repo = UserEmailRepository :: new ( ( * state. storage ) . clone ( ) ) ;
574- let user_repo = UserRepository :: new ( ( * state. storage ) . clone ( ) ) ;
572+ let repos = RepositoryContext :: new ( ( * state. storage ) . clone ( ) ) ;
575573
576574 // Find the email
577- let email = email_repo
575+ let email = repos
576+ . user_email
578577 . get_by_email ( & payload. email )
579578 . await ?
580579 . ok_or_else ( || {
@@ -590,7 +589,8 @@ pub async fn request_password_reset(
590589 }
591590
592591 // Get the user to ensure they exist and aren't deleted
593- let user = user_repo
592+ let user = repos
593+ . user
594594 . get ( email. user_id )
595595 . await ?
596596 . ok_or_else ( || CoreError :: NotFound ( "User not found" . to_string ( ) ) ) ?;
@@ -600,13 +600,12 @@ pub async fn request_password_reset(
600600 }
601601
602602 // Generate password reset token
603- let token_repo = UserPasswordResetTokenRepository :: new ( ( * state. storage ) . clone ( ) ) ;
604603 let token_id = IdGenerator :: next_id ( ) ;
605604 let token_string = UserPasswordResetToken :: generate_token ( ) ;
606605 let reset_token = UserPasswordResetToken :: new ( token_id, user. id , token_string. clone ( ) ) ?;
607606
608607 // Store the token
609- token_repo . create ( reset_token) . await ?;
608+ repos . user_password_reset_token . create ( reset_token) . await ?;
610609
611610 // Send password reset email (fire-and-forget - don't block request)
612611 if let Some ( email_service) = & state. email_service {
@@ -687,10 +686,11 @@ pub async fn confirm_password_reset(
687686 . into ( ) ) ;
688687 }
689688
690- let token_repo = UserPasswordResetTokenRepository :: new ( ( * state. storage ) . clone ( ) ) ;
689+ let repos = RepositoryContext :: new ( ( * state. storage ) . clone ( ) ) ;
691690
692691 // Get token
693- let mut token = token_repo
692+ let mut token = repos
693+ . user_password_reset_token
694694 . get_by_token ( & payload. token )
695695 . await ?
696696 . ok_or_else ( || CoreError :: Validation ( "Invalid or expired reset token" . to_string ( ) ) ) ?;
@@ -701,8 +701,8 @@ pub async fn confirm_password_reset(
701701 }
702702
703703 // Get the user
704- let user_repo = UserRepository :: new ( ( * state . storage ) . clone ( ) ) ;
705- let mut user = user_repo
704+ let mut user = repos
705+ . user
706706 . get ( token. user_id )
707707 . await ?
708708 . ok_or_else ( || CoreError :: NotFound ( "User not found" . to_string ( ) ) ) ?;
@@ -716,16 +716,15 @@ pub async fn confirm_password_reset(
716716
717717 // Update user's password
718718 user. set_password_hash ( password_hash) ;
719- user_repo . update ( user) . await ?;
719+ repos . user . update ( user) . await ?;
720720
721721 // Mark token as used
722722 let user_id = token. user_id ;
723723 token. mark_used ( ) ;
724- token_repo . update ( token) . await ?;
724+ repos . user_password_reset_token . update ( token) . await ?;
725725
726726 // Invalidate all user sessions for security
727- let session_repo = UserSessionRepository :: new ( ( * state. storage ) . clone ( ) ) ;
728- session_repo. revoke_user_sessions ( user_id) . await ?;
727+ repos. user_session . revoke_user_sessions ( user_id) . await ?;
729728 tracing:: info!(
730729 "Password reset successfully for user {} - all sessions revoked" ,
731730 user_id
@@ -942,15 +941,16 @@ mod tests {
942941 app. clone ( ) . oneshot ( register_request) . await . unwrap ( ) ;
943942
944943 // Manually verify the email since we don't have email sending
945- let email_repo = UserEmailRepository :: new ( ( * storage) . clone ( ) ) ;
946- let mut email = email_repo
944+ let repos = RepositoryContext :: new ( ( * storage) . clone ( ) ) ;
945+ let mut email = repos
946+ . user_email
947947 . get_by_email ( "alice@example.com" )
948948 . await
949949 . unwrap ( )
950950 . unwrap ( ) ;
951951 let user_id = email. user_id ;
952952 email. verify ( ) ;
953- email_repo . update ( email) . await . unwrap ( ) ;
953+ repos . user_email . update ( email) . await . unwrap ( ) ;
954954
955955 // Request password reset
956956 let reset_request = axum:: http:: Request :: builder ( )
@@ -969,8 +969,11 @@ mod tests {
969969 assert_eq ! ( response. status( ) , StatusCode :: OK ) ;
970970
971971 // Get the reset token from the repository
972- let token_repo = UserPasswordResetTokenRepository :: new ( ( * storage) . clone ( ) ) ;
973- let tokens = token_repo. get_by_user ( user_id) . await . unwrap ( ) ;
972+ let tokens = repos
973+ . user_password_reset_token
974+ . get_by_user ( user_id)
975+ . await
976+ . unwrap ( ) ;
974977 assert_eq ! ( tokens. len( ) , 1 ) ;
975978 let reset_token = tokens[ 0 ] . token . clone ( ) ;
976979
@@ -1077,18 +1080,18 @@ mod tests {
10771080 app. clone ( ) . oneshot ( register_request) . await . unwrap ( ) ;
10781081
10791082 // Manually verify the email
1080- let email_repo = UserEmailRepository :: new ( ( * storage) . clone ( ) ) ;
1081- let mut email = email_repo
1083+ let repos = RepositoryContext :: new ( ( * storage) . clone ( ) ) ;
1084+ let mut email = repos
1085+ . user_email
10821086 . get_by_email ( "alice@example.com" )
10831087 . await
10841088 . unwrap ( )
10851089 . unwrap ( ) ;
10861090 let user_id = email. user_id ;
10871091 email. verify ( ) ;
1088- email_repo . update ( email) . await . unwrap ( ) ;
1092+ repos . user_email . update ( email) . await . unwrap ( ) ;
10891093
10901094 // Create additional sessions to verify they all get revoked
1091- let session_repo = UserSessionRepository :: new ( ( * storage) . clone ( ) ) ;
10921095 let session2 = UserSession :: new (
10931096 IdGenerator :: next_id ( ) ,
10941097 user_id,
@@ -1103,11 +1106,11 @@ mod tests {
11031106 None ,
11041107 None ,
11051108 ) ;
1106- session_repo . create ( session2. clone ( ) ) . await . unwrap ( ) ;
1107- session_repo . create ( session3. clone ( ) ) . await . unwrap ( ) ;
1109+ repos . user_session . create ( session2. clone ( ) ) . await . unwrap ( ) ;
1110+ repos . user_session . create ( session3. clone ( ) ) . await . unwrap ( ) ;
11081111
11091112 // Verify we have 3 active sessions (1 from registration + 2 created)
1110- let sessions_before = session_repo . get_user_sessions ( user_id) . await . unwrap ( ) ;
1113+ let sessions_before = repos . user_session . get_user_sessions ( user_id) . await . unwrap ( ) ;
11111114 let active_before: Vec < _ > = sessions_before. iter ( ) . filter ( |s| s. is_active ( ) ) . collect ( ) ;
11121115 assert_eq ! (
11131116 active_before. len( ) ,
@@ -1131,8 +1134,11 @@ mod tests {
11311134 app. clone ( ) . oneshot ( reset_request) . await . unwrap ( ) ;
11321135
11331136 // Get the reset token
1134- let token_repo = UserPasswordResetTokenRepository :: new ( ( * storage) . clone ( ) ) ;
1135- let tokens = token_repo. get_by_user ( user_id) . await . unwrap ( ) ;
1137+ let tokens = repos
1138+ . user_password_reset_token
1139+ . get_by_user ( user_id)
1140+ . await
1141+ . unwrap ( ) ;
11361142 let reset_token = tokens[ 0 ] . token . clone ( ) ;
11371143
11381144 // Confirm password reset
@@ -1153,7 +1159,7 @@ mod tests {
11531159 assert_eq ! ( response. status( ) , StatusCode :: OK ) ;
11541160
11551161 // Verify ALL sessions are now revoked
1156- let sessions_after = session_repo . get_user_sessions ( user_id) . await . unwrap ( ) ;
1162+ let sessions_after = repos . user_session . get_user_sessions ( user_id) . await . unwrap ( ) ;
11571163 let active_after: Vec < _ > = sessions_after. iter ( ) . filter ( |s| s. is_active ( ) ) . collect ( ) ;
11581164 assert_eq ! (
11591165 active_after. len( ) ,
0 commit comments