diff --git a/src/support_sphere/lib/data/models/clusters.dart b/src/support_sphere/lib/data/models/clusters.dart new file mode 100644 index 0000000..32620c5 --- /dev/null +++ b/src/support_sphere/lib/data/models/clusters.dart @@ -0,0 +1,46 @@ +import 'package:equatable/equatable.dart'; +import 'package:support_sphere/data/models/person.dart'; + +class Cluster extends Equatable { + + const Cluster({ + required this.id, + this.name = '', + this.meetingPlace = '', + this.captains, + }); + + /// The current user's id, which matches the auth user id + final String id; + final String? name; + final String? meetingPlace; + final Captains? captains; + + @override + List get props => [id, name, meetingPlace, captains]; + + copyWith({ + String? id, + String? name, + String? meetingPlace, + Captains? captains, + }) { + return Cluster( + id: id ?? this.id, + name: name ?? this.name, + meetingPlace: meetingPlace ?? this.meetingPlace, + captains: captains ?? this.captains, + ); + } +} + +class Captains extends Equatable { + const Captains({ + this.people = const [], + }); + + final List people; + + @override + List get props => [people]; +} diff --git a/src/support_sphere/lib/data/models/households.dart b/src/support_sphere/lib/data/models/households.dart index 275d96b..5ecd310 100644 --- a/src/support_sphere/lib/data/models/households.dart +++ b/src/support_sphere/lib/data/models/households.dart @@ -5,6 +5,7 @@ class Household extends Equatable { const Household({ required this.id, + required this.cluster_id, this.name = '', this.address = '', this.notes = '', @@ -15,6 +16,7 @@ class Household extends Equatable { /// The current user's id, which matches the auth user id final String id; + final String cluster_id; final String? name; final String? address; final String? notes; @@ -23,7 +25,7 @@ class Household extends Equatable { final HouseHoldMembers? houseHoldMembers; @override - List get props => [id, name, address, notes, pets, accessibility_needs, houseHoldMembers]; + List get props => [id, name, address, notes, pets, accessibility_needs, houseHoldMembers, cluster_id]; copyWith({ String? id, @@ -33,6 +35,7 @@ class Household extends Equatable { String? pets, String? accessibility_needs, HouseHoldMembers? houseHoldMembers, + String? cluster_id, }) { return Household( id: id ?? this.id, @@ -42,6 +45,7 @@ class Household extends Equatable { pets: pets ?? this.pets, accessibility_needs: accessibility_needs ?? this.accessibility_needs, houseHoldMembers: houseHoldMembers ?? this.houseHoldMembers, + cluster_id: cluster_id ?? this.cluster_id, ); } } diff --git a/src/support_sphere/lib/data/repositories/user.dart b/src/support_sphere/lib/data/repositories/user.dart index de8b13c..9235334 100644 --- a/src/support_sphere/lib/data/repositories/user.dart +++ b/src/support_sphere/lib/data/repositories/user.dart @@ -2,14 +2,17 @@ import 'dart:async'; import 'package:supabase_flutter/supabase_flutter.dart' as supabase_flutter; import 'package:support_sphere/data/models/auth_user.dart'; +import 'package:support_sphere/data/models/clusters.dart'; import 'package:support_sphere/data/models/households.dart'; import 'package:support_sphere/data/models/person.dart'; +import 'package:support_sphere/data/services/cluster_service.dart'; import 'package:support_sphere/data/services/user_service.dart'; /// Repository for user interactions. /// This class is responsible for handling user-related data operations. class UserRepository { final UserService _userService = UserService(); + final ClusterService _clusterService = ClusterService(); /// Get the household members by household id. /// Returns a [HouseHoldMembers] object if the household members exist. @@ -60,6 +63,7 @@ class UserRepository { notes: householdData["notes"], pets: householdData["pets"], accessibility_needs: householdData["accessibility_needs"], + cluster_id: householdData["cluster_id"], ); } return null; @@ -88,6 +92,46 @@ class UserRepository { return null; } + /// Get the cluster by cluster id retrieved from [Household]. + /// Returns a [Cluster] object + Future getClusterById({ + required String clusterId, + }) async { + final data = await _clusterService.getClusterById(clusterId); + + if (data != null) { + return Cluster( + id: data["id"], + name: data["name"], + meetingPlace: data["meeting_place"], + ); + } + return null; + } + + Future getCaptainsByClusterId({ + required String clusterId, + }) async { + final data = await _clusterService.getCaptainsByClusterId(clusterId); + + if (data != null) { + List captains = []; + + for (var record in data) { + Map captainData = record["captain"]["user_profile"]["person"]; + + captains.add(Person( + id: captainData["id"], + givenName: captainData["given_name"], + familyName: captainData["family_name"], + )); + } + + return Captains(people: captains); + } + return null; + } + /// Create a new user with the given user info. /// This will perform two operations: diff --git a/src/support_sphere/lib/data/services/cluster_service.dart b/src/support_sphere/lib/data/services/cluster_service.dart new file mode 100644 index 0000000..075ab4c --- /dev/null +++ b/src/support_sphere/lib/data/services/cluster_service.dart @@ -0,0 +1,36 @@ +import 'package:supabase_flutter/supabase_flutter.dart'; +import 'package:support_sphere/utils/supabase.dart'; +import 'package:uuid/v4.dart'; + +class ClusterService { + final SupabaseClient _supabaseClient = supabase; + + /// Retrieves the cluster by cluster id. + /// Returns a [Cluster] object if the cluster exist. + /// Returns null if the cluster does not exist. + Future getClusterById(String clusterId) async { + /// This query will perform a join on the user_profiles and people tables + return await _supabaseClient.from('clusters').select(''' + id, + name, + meeting_place + ''').eq('id', clusterId).maybeSingle(); + } + + Future getCaptainsByClusterId(String clusterId) async { + return await _supabaseClient.from('user_captain_clusters') + .select(''' + captain:user_roles ( + user_profile:user_profiles ( + person:people ( + id, + given_name, + family_name + ) + ) + ) + ''') + .eq('cluster_id', clusterId) + .eq('user_roles.role', 'SUBCOM_AGENT'); + } +} diff --git a/src/support_sphere/lib/data/services/user_service.dart b/src/support_sphere/lib/data/services/user_service.dart index 9f690d9..a8c6f4a 100644 --- a/src/support_sphere/lib/data/services/user_service.dart +++ b/src/support_sphere/lib/data/services/user_service.dart @@ -17,7 +17,8 @@ class UserService { address, notes, pets, - accessibility_needs + accessibility_needs, + cluster_id ) ''').eq('people_id', personId).maybeSingle(); } diff --git a/src/support_sphere/lib/logic/cubit/profile_cubit.dart b/src/support_sphere/lib/logic/cubit/profile_cubit.dart index 602ddaf..b27e4bf 100644 --- a/src/support_sphere/lib/logic/cubit/profile_cubit.dart +++ b/src/support_sphere/lib/logic/cubit/profile_cubit.dart @@ -1,6 +1,7 @@ import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; import 'package:support_sphere/data/models/auth_user.dart'; +import 'package:support_sphere/data/models/clusters.dart'; import 'package:support_sphere/data/models/households.dart'; import 'package:support_sphere/data/models/person.dart'; import 'package:support_sphere/data/repositories/user.dart'; @@ -28,6 +29,10 @@ class ProfileCubit extends Cubit { emit(state.copyWith(household: household)); } + void clusterChanged(Cluster? cluster) { + emit(state.copyWith(cluster: cluster)); + } + Future fetchProfile() async { try { /// Get the user profile by user id @@ -54,9 +59,28 @@ class ProfileCubit extends Cubit { } else { throw Exception('Household not found'); } + + /// Get the cluster and its captains information + Cluster? cluster = household == null + ? null + : await _userRepository.getClusterById(clusterId: household.cluster_id); + + if (cluster != null) { + /// Get the captains of the cluster + final Captains? captains = await _userRepository.getCaptainsByClusterId(clusterId: cluster.id); + + if (captains != null) { + cluster = cluster.copyWith(captains: captains); + } + clusterChanged(cluster); + } else { + throw Exception('Cluster not found'); + } } catch (_) { /// TODO: Handle error if there are no user profile or household for some reason profileChanged(null); + householdChanged(null); + clusterChanged(null); } } } diff --git a/src/support_sphere/lib/logic/cubit/profile_state.dart b/src/support_sphere/lib/logic/cubit/profile_state.dart index 102d36b..0867443 100644 --- a/src/support_sphere/lib/logic/cubit/profile_state.dart +++ b/src/support_sphere/lib/logic/cubit/profile_state.dart @@ -5,24 +5,28 @@ class ProfileState extends Equatable { this.userProfile, this.household, this.authUser, + this.cluster, }); final Person? userProfile; final AuthUser? authUser; final Household? household; + final Cluster? cluster; @override - List get props => [userProfile, authUser, household]; + List get props => [userProfile, authUser, household, cluster]; ProfileState copyWith({ Person? userProfile, AuthUser? authUser, Household? household, + Cluster? cluster, }) { return ProfileState( userProfile: userProfile ?? this.userProfile, authUser: authUser ?? this.authUser, household: household ?? this.household, + cluster: cluster ?? this.cluster, ); } } diff --git a/src/support_sphere/lib/presentation/pages/main_app/profile/profile_body.dart b/src/support_sphere/lib/presentation/pages/main_app/profile/profile_body.dart index 9d1e6aa..4076d2a 100644 --- a/src/support_sphere/lib/presentation/pages/main_app/profile/profile_body.dart +++ b/src/support_sphere/lib/presentation/pages/main_app/profile/profile_body.dart @@ -3,6 +3,7 @@ import 'package:ionicons/ionicons.dart'; import 'package:modal_bottom_sheet/modal_bottom_sheet.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:support_sphere/data/models/auth_user.dart'; +import 'package:support_sphere/data/models/clusters.dart'; import 'package:support_sphere/data/models/households.dart'; import 'package:support_sphere/data/models/person.dart'; import 'package:support_sphere/logic/bloc/auth/authentication_bloc.dart'; @@ -288,39 +289,53 @@ class _ClusterInformation extends StatelessWidget { @override Widget build(BuildContext context) { - // Cluster Information - return _ProfileSection( - title: "Cluster Information", - readOnly: true, - children: [ - const Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text("Name"), - Text("Cluster 1"), - ], - ), - const Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text("Meeting place"), - Text("410 Example Street"), - ], - ), - const Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + return BlocBuilder( + buildWhen: (previous, current) => previous.cluster != current.cluster, + builder: (context, state) { + Cluster? cluster = state.cluster; + String name = cluster?.name ?? ''; + String meetingPlace = cluster?.meetingPlace ?? ''; + List captains = cluster?.captains?.people ?? []; + List captainsNames = captains.map((captain) { + String givenName = captain?.givenName ?? ''; + String familyName = captain?.familyName ?? ''; + String fullName = '$givenName $familyName'; + return fullName; + }).toList(); + return _ProfileSection( + title: "Cluster Information", + readOnly: true, children: [ - Text("Captain(s)"), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Name"), + Text(name), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Meeting place"), + Text(meetingPlace), + ], + ), + const Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Captain(s)"), + ], + ), + Container( + height: 50.0, + child: ListView( + shrinkWrap: true, + children: captainsNames.map((n) => Text(n)).toList(), + ), + ), ], - ), - Container( - height: 50.0, - child: ListView(shrinkWrap: true, children: const [ - Text("Jane Smith"), - Text("John Smith"), - ]), - ), - ], + ); + }, ); } } diff --git a/src/support_sphere_py/tests/resources/scripts/role_based_access_control.py b/src/support_sphere_py/tests/resources/scripts/role_based_access_control.py index bd2c576..4360e03 100644 --- a/src/support_sphere_py/tests/resources/scripts/role_based_access_control.py +++ b/src/support_sphere_py/tests/resources/scripts/role_based_access_control.py @@ -52,12 +52,6 @@ GRANT ALL ON TABLE public.user_roles TO supabase_auth_admin; - REVOKE ALL ON TABLE public.user_roles FROM authenticated, anon, public; - - CREATE POLICY "Allow auth admin to read user roles" ON public.user_roles AS PERMISSIVE FOR SELECT TO supabase_auth_admin USING (true); - - ALTER TABLE public.user_roles ENABLE ROW LEVEL SECURITY; - COMMIT; """