302 lines
8.3 KiB
Dart
302 lines
8.3 KiB
Dart
import 'package:supabase_flutter/supabase_flutter.dart';
|
|
import 'package:tugas_akhir_supabase/screens/community/models/group.dart';
|
|
import 'package:tugas_akhir_supabase/screens/community/models/group_member.dart';
|
|
|
|
class GroupService {
|
|
final _supabase = Supabase.instance.client;
|
|
|
|
// Cache
|
|
final Map<String, Group> _groupCache = {};
|
|
|
|
// Get current user ID
|
|
String? get currentUserId => _supabase.auth.currentUser?.id;
|
|
|
|
// Load all groups that the current user is a member of
|
|
Future<List<Group>> getUserGroups() async {
|
|
if (currentUserId == null) {
|
|
print('[ERROR] getUserGroups: currentUserId is null');
|
|
return [];
|
|
}
|
|
|
|
try {
|
|
print(
|
|
'[DEBUG] getUserGroups: Querying group_members for user: $currentUserId',
|
|
);
|
|
|
|
// Join group_members with groups to get all groups for current user
|
|
final response = await _supabase
|
|
.from('group_members')
|
|
.select('groups(*)')
|
|
.eq('user_id', currentUserId!)
|
|
.eq('is_active', true)
|
|
.order('joined_at');
|
|
|
|
print('[DEBUG] getUserGroups: Raw response: $response');
|
|
|
|
if (response.isEmpty) {
|
|
print(
|
|
'[DEBUG] getUserGroups: No groups found in response, trying fallback',
|
|
);
|
|
|
|
// Fallback: Try to get default group directly
|
|
return await _getDefaultGroup();
|
|
}
|
|
|
|
final List<Group> groups = [];
|
|
|
|
for (final item in response) {
|
|
if (item['groups'] == null) {
|
|
print('[WARNING] getUserGroups: Null groups data in item: $item');
|
|
continue;
|
|
}
|
|
|
|
final groupData = item['groups'] as Map<String, dynamic>;
|
|
print('[DEBUG] getUserGroups: Processing group: ${groupData['name']}');
|
|
|
|
final group = Group.fromMap(groupData);
|
|
|
|
// Update cache
|
|
_groupCache[group.id] = group;
|
|
groups.add(group);
|
|
}
|
|
|
|
print('[INFO] getUserGroups: Found ${groups.length} groups for user');
|
|
|
|
if (groups.isEmpty) {
|
|
// If no groups found via membership, try to get default group
|
|
print(
|
|
'[DEBUG] getUserGroups: No groups found via membership, trying to get default group',
|
|
);
|
|
return await _getDefaultGroup();
|
|
}
|
|
|
|
return groups;
|
|
} catch (e) {
|
|
print('[ERROR] Failed to load user groups: $e');
|
|
|
|
// Try fallback on error
|
|
return await _getDefaultGroup();
|
|
}
|
|
}
|
|
|
|
// Fallback method to get at least the default group
|
|
Future<List<Group>> _getDefaultGroup() async {
|
|
try {
|
|
print('[DEBUG] Trying to get default group directly');
|
|
final response = await _supabase
|
|
.from('groups')
|
|
.select('*')
|
|
.eq('is_default', true)
|
|
.limit(1);
|
|
|
|
print('[DEBUG] Default group response: $response');
|
|
|
|
if (response.isNotEmpty) {
|
|
final groupData = response[0];
|
|
final group = Group.fromMap(groupData);
|
|
|
|
print('[INFO] Found default group: ${group.name}');
|
|
|
|
// Auto-join user to this group if needed
|
|
if (currentUserId != null) {
|
|
_joinGroupIfNotMember(group.id);
|
|
}
|
|
|
|
// Update cache
|
|
_groupCache[group.id] = group;
|
|
return [group];
|
|
}
|
|
|
|
print('[WARNING] No default group found');
|
|
return [];
|
|
} catch (e) {
|
|
print('[ERROR] Failed to get default group: $e');
|
|
return [];
|
|
}
|
|
}
|
|
|
|
// Utility to join user to group if not already a member
|
|
Future<void> _joinGroupIfNotMember(String groupId) async {
|
|
try {
|
|
final isMember = await isUserGroupMember(groupId);
|
|
if (!isMember && currentUserId != null) {
|
|
print('[INFO] Auto-joining user to group: $groupId');
|
|
final member = GroupMember(groupId: groupId, userId: currentUserId!);
|
|
await _supabase.from('group_members').insert(member.toMap());
|
|
}
|
|
} catch (e) {
|
|
print('[ERROR] Failed to auto-join group: $e');
|
|
}
|
|
}
|
|
|
|
// Get details of a specific group
|
|
Future<Group?> getGroupDetails(String groupId) async {
|
|
// Try cache first
|
|
if (_groupCache.containsKey(groupId)) {
|
|
return _groupCache[groupId];
|
|
}
|
|
|
|
try {
|
|
final response =
|
|
await _supabase
|
|
.from('groups')
|
|
.select('*, group_members(count)')
|
|
.eq('id', groupId)
|
|
.single();
|
|
|
|
final memberCount = response['group_members']?[0]?['count'] ?? 0;
|
|
final groupData = Map<String, dynamic>.from(response);
|
|
groupData['member_count'] = memberCount;
|
|
|
|
final group = Group.fromMap(groupData);
|
|
|
|
// Update cache
|
|
_groupCache[group.id] = group;
|
|
|
|
return group;
|
|
} catch (e) {
|
|
print('[ERROR] Failed to get group details: $e');
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// Check if user is a member of a group
|
|
Future<bool> isUserGroupMember(String groupId, {String? userId}) async {
|
|
final targetUserId = userId ?? currentUserId;
|
|
if (targetUserId == null) return false;
|
|
|
|
try {
|
|
final response =
|
|
await _supabase
|
|
.from('group_members')
|
|
.select('id')
|
|
.eq('group_id', groupId)
|
|
.eq('user_id', targetUserId)
|
|
.eq('is_active', true)
|
|
.maybeSingle();
|
|
|
|
return response != null;
|
|
} catch (e) {
|
|
print('[ERROR] Failed to check group membership: $e');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Join a group
|
|
Future<bool> joinGroup(String groupId) async {
|
|
if (currentUserId == null) return false;
|
|
|
|
// Check if already a member
|
|
final isMember = await isUserGroupMember(groupId);
|
|
if (isMember) return true; // Already a member
|
|
|
|
try {
|
|
// Create new group member
|
|
final member = GroupMember(groupId: groupId, userId: currentUserId!);
|
|
|
|
await _supabase.from('group_members').insert(member.toMap());
|
|
|
|
// Clear cache for this group to refresh member count
|
|
_groupCache.remove(groupId);
|
|
|
|
return true;
|
|
} catch (e) {
|
|
print('[ERROR] Failed to join group: $e');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Leave a group
|
|
Future<bool> leaveGroup(String groupId) async {
|
|
if (currentUserId == null) return false;
|
|
|
|
try {
|
|
// Check if this is the default group
|
|
final group = await getGroupDetails(groupId);
|
|
if (group?.isDefault == true) {
|
|
// Can't leave default group
|
|
return false;
|
|
}
|
|
|
|
// Mark as inactive instead of deleting
|
|
await _supabase
|
|
.from('group_members')
|
|
.update({'is_active': false})
|
|
.eq('group_id', groupId)
|
|
.eq('user_id', currentUserId!);
|
|
|
|
// Clear cache for this group
|
|
_groupCache.remove(groupId);
|
|
|
|
return true;
|
|
} catch (e) {
|
|
print('[ERROR] Failed to leave group: $e');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Get all public groups
|
|
Future<List<Group>> getPublicGroups() async {
|
|
try {
|
|
final response = await _supabase
|
|
.from('groups')
|
|
.select('*, group_members(count)')
|
|
.eq('is_public', true)
|
|
.order('created_at');
|
|
|
|
final List<Group> groups = [];
|
|
|
|
for (final item in response) {
|
|
final memberCount = item['group_members']?[0]?['count'] ?? 0;
|
|
final groupData = Map<String, dynamic>.from(item);
|
|
groupData['member_count'] = memberCount;
|
|
|
|
final group = Group.fromMap(groupData);
|
|
|
|
// Update cache
|
|
_groupCache[group.id] = group;
|
|
groups.add(group);
|
|
}
|
|
|
|
return groups;
|
|
} catch (e) {
|
|
print('[ERROR] Failed to load public groups: $e');
|
|
return [];
|
|
}
|
|
}
|
|
|
|
// Get group member role for current user
|
|
Future<String> getUserRole(String groupId) async {
|
|
if (currentUserId == null) return 'none';
|
|
|
|
try {
|
|
final response =
|
|
await _supabase
|
|
.from('group_members')
|
|
.select('role')
|
|
.eq('group_id', groupId)
|
|
.eq('user_id', currentUserId!)
|
|
.eq('is_active', true)
|
|
.maybeSingle();
|
|
|
|
if (response == null) return 'none';
|
|
return response['role'] as String? ?? 'member';
|
|
} catch (e) {
|
|
print('[ERROR] Failed to get user role: $e');
|
|
return 'none';
|
|
}
|
|
}
|
|
|
|
// Check if user has admin permissions
|
|
Future<bool> isGroupAdmin(String groupId) async {
|
|
final role = await getUserRole(groupId);
|
|
return role == 'admin';
|
|
}
|
|
|
|
// Check if user has moderator permissions
|
|
Future<bool> isGroupModerator(String groupId) async {
|
|
final role = await getUserRole(groupId);
|
|
return role == 'admin' || role == 'moderator';
|
|
}
|
|
}
|