@@ -7,7 +7,9 @@ use entity::users::{ActiveModel, Column, Entity, Model};
77use entity:: { roles, user_roles, Id } ;
88use log:: * ;
99use password_auth;
10- use sea_orm:: { entity:: prelude:: * , ConnectionTrait , DatabaseConnection , Set , TransactionTrait } ;
10+ use sea_orm:: {
11+ entity:: prelude:: * , Condition , ConnectionTrait , DatabaseConnection , Set , TransactionTrait ,
12+ } ;
1113use serde:: Deserialize ;
1214use std:: sync:: Arc ;
1315use utoipa:: { IntoParams , ToSchema } ;
@@ -127,6 +129,42 @@ pub async fn find_by_organization(
127129 . collect ( ) )
128130}
129131
132+ /// Checks if a user has admin privileges for an organization.
133+ ///
134+ /// Returns `true` if the user is:
135+ /// - A SuperAdmin (has `SuperAdmin` role with `organization_id = NULL`), OR
136+ /// - An Admin for the specific organization (has `Admin` role for the given organization)
137+ ///
138+ /// This function encapsulates the SeaORM query logic for role checking,
139+ /// keeping database-specific implementation details out of the domain layer.
140+ pub async fn has_admin_access (
141+ db : & impl ConnectionTrait ,
142+ user_id : Id ,
143+ organization_id : Id ,
144+ ) -> Result < bool , Error > {
145+ let admin_role = user_roles:: Entity :: find ( )
146+ . filter ( user_roles:: Column :: UserId . eq ( user_id) )
147+ . filter (
148+ Condition :: any ( )
149+ // SuperAdmin with organization_id = NULL
150+ . add (
151+ Condition :: all ( )
152+ . add ( user_roles:: Column :: Role . eq ( Role :: SuperAdmin ) )
153+ . add ( user_roles:: Column :: OrganizationId . is_null ( ) ) ,
154+ )
155+ // Admin for this specific organization
156+ . add (
157+ Condition :: all ( )
158+ . add ( user_roles:: Column :: Role . eq ( Role :: Admin ) )
159+ . add ( user_roles:: Column :: OrganizationId . eq ( organization_id) ) ,
160+ ) ,
161+ )
162+ . one ( db)
163+ . await ?;
164+
165+ Ok ( admin_role. is_some ( ) )
166+ }
167+
130168pub async fn delete ( db : & impl ConnectionTrait , user_id : Id ) -> Result < ( ) , Error > {
131169 Entity :: delete_by_id ( user_id) . exec ( db) . await ?;
132170 Ok ( ( ) )
@@ -382,4 +420,160 @@ mod test {
382420
383421 Ok ( ( ) )
384422 }
423+
424+ #[ tokio:: test]
425+ async fn has_admin_access_returns_true_for_super_admin ( ) -> Result < ( ) , Error > {
426+ let user_id = Id :: new_v4 ( ) ;
427+ let organization_id = Id :: new_v4 ( ) ;
428+ let role_id = Id :: new_v4 ( ) ;
429+ let now = chrono:: Utc :: now ( ) ;
430+
431+ // Create a SuperAdmin role with NULL organization_id
432+ let super_admin_role = user_roles:: Model {
433+ id : role_id,
434+ user_id,
435+ role : Role :: SuperAdmin ,
436+ organization_id : None ,
437+ created_at : now. into ( ) ,
438+ updated_at : now. into ( ) ,
439+ } ;
440+
441+ let db = MockDatabase :: new ( DatabaseBackend :: Postgres )
442+ . append_query_results ( vec ! [ vec![ super_admin_role] ] )
443+ . into_connection ( ) ;
444+
445+ let result = has_admin_access ( & db, user_id, organization_id) . await ?;
446+
447+ assert ! (
448+ result,
449+ "SuperAdmin should have admin access to any organization"
450+ ) ;
451+
452+ Ok ( ( ) )
453+ }
454+
455+ #[ tokio:: test]
456+ async fn has_admin_access_returns_true_for_organization_admin ( ) -> Result < ( ) , Error > {
457+ let user_id = Id :: new_v4 ( ) ;
458+ let organization_id = Id :: new_v4 ( ) ;
459+ let role_id = Id :: new_v4 ( ) ;
460+ let now = chrono:: Utc :: now ( ) ;
461+
462+ // Create an Admin role for the specific organization
463+ let org_admin_role = user_roles:: Model {
464+ id : role_id,
465+ user_id,
466+ role : Role :: Admin ,
467+ organization_id : Some ( organization_id) ,
468+ created_at : now. into ( ) ,
469+ updated_at : now. into ( ) ,
470+ } ;
471+
472+ let db = MockDatabase :: new ( DatabaseBackend :: Postgres )
473+ . append_query_results ( vec ! [ vec![ org_admin_role] ] )
474+ . into_connection ( ) ;
475+
476+ let result = has_admin_access ( & db, user_id, organization_id) . await ?;
477+
478+ assert ! (
479+ result,
480+ "Organization Admin should have admin access to their organization"
481+ ) ;
482+
483+ Ok ( ( ) )
484+ }
485+
486+ #[ tokio:: test]
487+ async fn has_admin_access_returns_false_for_regular_user ( ) -> Result < ( ) , Error > {
488+ let user_id = Id :: new_v4 ( ) ;
489+ let organization_id = Id :: new_v4 ( ) ;
490+
491+ // Mock returns empty result (no admin roles found)
492+ let db = MockDatabase :: new ( DatabaseBackend :: Postgres )
493+ . append_query_results ( vec ! [ Vec :: <user_roles:: Model >:: new( ) ] )
494+ . into_connection ( ) ;
495+
496+ let result = has_admin_access ( & db, user_id, organization_id) . await ?;
497+
498+ assert ! ( !result, "Regular users should not have admin access" ) ;
499+
500+ Ok ( ( ) )
501+ }
502+
503+ #[ tokio:: test]
504+ async fn has_admin_access_returns_false_for_admin_of_different_organization (
505+ ) -> Result < ( ) , Error > {
506+ let user_id = Id :: new_v4 ( ) ;
507+ let organization_id_a = Id :: new_v4 ( ) ; // Organization being queried
508+ let _organization_id_b = Id :: new_v4 ( ) ; // Organization where user is admin
509+
510+ // Mock returns empty result (no matching admin role for org A)
511+ let db = MockDatabase :: new ( DatabaseBackend :: Postgres )
512+ . append_query_results ( vec ! [ Vec :: <user_roles:: Model >:: new( ) ] )
513+ . into_connection ( ) ;
514+
515+ let result = has_admin_access ( & db, user_id, organization_id_a) . await ?;
516+
517+ assert ! (
518+ !result,
519+ "Admin of different organization should not have access"
520+ ) ;
521+
522+ Ok ( ( ) )
523+ }
524+
525+ #[ tokio:: test]
526+ async fn has_admin_access_returns_false_for_nonexistent_user ( ) -> Result < ( ) , Error > {
527+ let nonexistent_user_id = Id :: new_v4 ( ) ;
528+ let organization_id = Id :: new_v4 ( ) ;
529+
530+ // Mock returns empty result (user doesn't exist)
531+ let db = MockDatabase :: new ( DatabaseBackend :: Postgres )
532+ . append_query_results ( vec ! [ Vec :: <user_roles:: Model >:: new( ) ] )
533+ . into_connection ( ) ;
534+
535+ let result = has_admin_access ( & db, nonexistent_user_id, organization_id) . await ?;
536+
537+ assert ! ( !result, "Nonexistent user should not have admin access" ) ;
538+
539+ Ok ( ( ) )
540+ }
541+
542+ #[ tokio:: test]
543+ async fn has_admin_access_with_admin_role_for_multiple_organizations ( ) -> Result < ( ) , Error > {
544+ let user_id = Id :: new_v4 ( ) ;
545+ let organization_id_a = Id :: new_v4 ( ) ;
546+ let organization_id_b = Id :: new_v4 ( ) ;
547+ let role_id = Id :: new_v4 ( ) ;
548+ let now = chrono:: Utc :: now ( ) ;
549+
550+ // Create an Admin role for organization A
551+ let org_admin_role = user_roles:: Model {
552+ id : role_id,
553+ user_id,
554+ role : Role :: Admin ,
555+ organization_id : Some ( organization_id_a) ,
556+ created_at : now. into ( ) ,
557+ updated_at : now. into ( ) ,
558+ } ;
559+
560+ let db = MockDatabase :: new ( DatabaseBackend :: Postgres )
561+ . append_query_results ( vec ! [ vec![ org_admin_role] ] )
562+ . into_connection ( ) ;
563+
564+ // Should have access to organization A
565+ let result_a = has_admin_access ( & db, user_id, organization_id_a) . await ?;
566+ assert ! ( result_a, "Should have admin access to organization A" ) ;
567+
568+ // Create new mock for organization B query (no matching role)
569+ let db_b = MockDatabase :: new ( DatabaseBackend :: Postgres )
570+ . append_query_results ( vec ! [ Vec :: <user_roles:: Model >:: new( ) ] )
571+ . into_connection ( ) ;
572+
573+ // Should NOT have access to organization B
574+ let result_b = has_admin_access ( & db_b, user_id, organization_id_b) . await ?;
575+ assert ! ( !result_b, "Should not have admin access to organization B" ) ;
576+
577+ Ok ( ( ) )
578+ }
385579}
0 commit comments