393 lines
11 KiB
Dart
393 lines
11 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 GroupManagementService {
|
|
final _supabase = Supabase.instance.client;
|
|
|
|
// Get current user ID
|
|
String? get currentUserId => _supabase.auth.currentUser?.id;
|
|
|
|
// Check if the current user is an admin
|
|
Future<bool> isUserAdmin() async {
|
|
if (currentUserId == null) return false;
|
|
|
|
try {
|
|
final response =
|
|
await _supabase
|
|
.from('user_roles')
|
|
.select('role')
|
|
.eq('user_id', currentUserId!)
|
|
.maybeSingle();
|
|
|
|
if (response == null) return false;
|
|
return response['role'] == 'admin';
|
|
} catch (e) {
|
|
print('[ERROR] Failed to check if user is admin: $e');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Get all groups (admin only)
|
|
Future<List<Group>> getAllGroups() async {
|
|
print('[DEBUG] Starting getAllGroups()');
|
|
final bool isAdmin = await isUserAdmin();
|
|
print('[DEBUG] isUserAdmin result: $isAdmin');
|
|
|
|
if (!isAdmin) {
|
|
print('[ERROR] Unauthorized access to admin function');
|
|
return [];
|
|
}
|
|
|
|
try {
|
|
print('[DEBUG] Fetching groups from database...');
|
|
final response = await _supabase
|
|
.from('groups')
|
|
.select('*, group_members(count)')
|
|
.order('created_at');
|
|
|
|
print('[DEBUG] Raw response from groups query: $response');
|
|
|
|
if (response.isEmpty) {
|
|
print('[DEBUG] No groups found in response');
|
|
return [];
|
|
}
|
|
|
|
final List<Group> groups = [];
|
|
|
|
for (final item in response) {
|
|
print('[DEBUG] Processing group: ${item['name']} (${item['id']})');
|
|
final memberCount = item['group_members']?[0]?['count'] ?? 0;
|
|
final groupData = Map<String, dynamic>.from(item);
|
|
groupData['member_count'] = memberCount;
|
|
|
|
final group = Group.fromMap(groupData);
|
|
groups.add(group);
|
|
}
|
|
|
|
print('[DEBUG] Returned ${groups.length} groups');
|
|
return groups;
|
|
} catch (e) {
|
|
print('[ERROR] Failed to load all groups: $e');
|
|
return [];
|
|
}
|
|
}
|
|
|
|
// Create a new group (admin only)
|
|
Future<Group?> createGroup(Group group) async {
|
|
if (!await isUserAdmin()) {
|
|
print('[ERROR] Unauthorized access to admin function');
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
// Insert group dengan id dari group parameter
|
|
final response =
|
|
await _supabase
|
|
.from('groups')
|
|
.insert({
|
|
'id': group.id,
|
|
'name': group.name,
|
|
'description': group.description,
|
|
'created_by': group.createdBy,
|
|
'created_at': group.createdAt.toIso8601String(),
|
|
'is_default': group.isDefault,
|
|
'is_public': group.isPublic,
|
|
'icon_url': group.iconUrl,
|
|
})
|
|
.select('id')
|
|
.single();
|
|
|
|
print('[INFO] Group created successfully: ${response['id']}');
|
|
|
|
// Ensure we use the ID from response in case it's different
|
|
final createdGroupId = response['id'] as String;
|
|
|
|
// Add creator as admin member
|
|
final member = GroupMember(
|
|
groupId: createdGroupId,
|
|
userId: currentUserId!,
|
|
role: 'admin',
|
|
);
|
|
|
|
await _supabase.from('group_members').insert(member.toMap());
|
|
|
|
// Return group with proper ID
|
|
return group.copyWith(id: createdGroupId);
|
|
} catch (e) {
|
|
print('[ERROR] Failed to create group: $e');
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// Update an existing group (admin only)
|
|
Future<bool> updateGroup(Group group) async {
|
|
if (!await isUserAdmin()) {
|
|
print('[ERROR] Unauthorized access to admin function');
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
await _supabase.from('groups').update(group.toMap()).eq('id', group.id);
|
|
|
|
return true;
|
|
} catch (e) {
|
|
print('[ERROR] Failed to update group: $e');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Delete a group (admin only)
|
|
Future<bool> deleteGroup(String groupId) async {
|
|
if (!await isUserAdmin()) {
|
|
print('[ERROR] Unauthorized access to admin function');
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
// Check if this is the default group
|
|
final response =
|
|
await _supabase
|
|
.from('groups')
|
|
.select('is_default')
|
|
.eq('id', groupId)
|
|
.single();
|
|
|
|
if (response['is_default'] == true) {
|
|
// Cannot delete default group
|
|
return false;
|
|
}
|
|
|
|
// Delete group members first (due to foreign key constraints)
|
|
await _supabase.from('group_members').delete().eq('group_id', groupId);
|
|
|
|
// Delete the group
|
|
await _supabase.from('groups').delete().eq('id', groupId);
|
|
|
|
return true;
|
|
} catch (e) {
|
|
print('[ERROR] Failed to delete group: $e');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Get all members of a group (admin only)
|
|
Future<List<GroupMemberDetail>> getGroupMembers(String groupId) async {
|
|
if (!await isUserAdmin()) {
|
|
print('[ERROR] Unauthorized access to admin function');
|
|
return [];
|
|
}
|
|
|
|
try {
|
|
final response = await _supabase
|
|
.from('group_members')
|
|
.select('*, profiles:user_id(username, avatar_url, email)')
|
|
.eq('group_id', groupId)
|
|
.eq('is_active', true)
|
|
.order('joined_at');
|
|
|
|
final List<GroupMemberDetail> members = [];
|
|
|
|
for (final item in response) {
|
|
final member = GroupMember.fromMap(item);
|
|
final profile = item['profiles'] as Map<String, dynamic>?;
|
|
|
|
members.add(
|
|
GroupMemberDetail(
|
|
member: member,
|
|
username: profile?['username'] as String? ?? 'Unknown User',
|
|
email: profile?['email'] as String?,
|
|
avatarUrl: profile?['avatar_url'] as String?,
|
|
),
|
|
);
|
|
}
|
|
|
|
return members;
|
|
} catch (e) {
|
|
print('[ERROR] Failed to load group members: $e');
|
|
return [];
|
|
}
|
|
}
|
|
|
|
// Remove a user from a group (admin only)
|
|
Future<bool> removeGroupMember(String groupId, String userId) async {
|
|
if (!await isUserAdmin()) {
|
|
print('[ERROR] Unauthorized access to admin function');
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
// Mark as inactive instead of deleting
|
|
await _supabase
|
|
.from('group_members')
|
|
.update({'is_active': false})
|
|
.eq('group_id', groupId)
|
|
.eq('user_id', userId);
|
|
|
|
return true;
|
|
} catch (e) {
|
|
print('[ERROR] Failed to remove group member: $e');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Change a member's role (admin only)
|
|
Future<bool> changeGroupMemberRole(
|
|
String groupId,
|
|
String userId,
|
|
String newRole,
|
|
) async {
|
|
if (!await isUserAdmin()) {
|
|
print('[ERROR] Unauthorized access to admin function');
|
|
return false;
|
|
}
|
|
|
|
// Validate role
|
|
if (!['admin', 'moderator', 'member'].contains(newRole)) {
|
|
print('[ERROR] Invalid role: $newRole');
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
await _supabase
|
|
.from('group_members')
|
|
.update({'role': newRole})
|
|
.eq('group_id', groupId)
|
|
.eq('user_id', userId);
|
|
|
|
return true;
|
|
} catch (e) {
|
|
print('[ERROR] Failed to change member role: $e');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Add all users to a default group (admin only)
|
|
Future<bool> addAllUsersToDefaultGroup(String defaultGroupId) async {
|
|
if (!await isUserAdmin()) {
|
|
print('[ERROR] Unauthorized access to admin function');
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
// Cara langsung menggunakan RPC untuk menghindari masalah RLS
|
|
final result = await _supabase.rpc(
|
|
'add_all_users_to_group',
|
|
params: {'group_id_param': defaultGroupId},
|
|
);
|
|
|
|
print('[INFO] Added users to default group: $result');
|
|
return result == true;
|
|
} catch (e) {
|
|
print('[ERROR] Failed to add all users to default group: $e');
|
|
|
|
// Fallback jika RPC tidak tersedia
|
|
try {
|
|
// Get all user IDs
|
|
final usersResponse = await _supabase
|
|
.from('profiles')
|
|
.select('user_id');
|
|
|
|
// Get existing members of the group
|
|
final membersResponse = await _supabase
|
|
.from('group_members')
|
|
.select('user_id')
|
|
.eq('group_id', defaultGroupId);
|
|
|
|
final existingMemberIds =
|
|
membersResponse.map((m) => m['user_id'] as String).toSet();
|
|
|
|
// Create members for users who aren't already in the group
|
|
final List<Map<String, dynamic>> newMembers = [];
|
|
|
|
for (final user in usersResponse) {
|
|
final userId = user['user_id'] as String;
|
|
if (!existingMemberIds.contains(userId)) {
|
|
newMembers.add(
|
|
GroupMember(groupId: defaultGroupId, userId: userId).toMap(),
|
|
);
|
|
}
|
|
}
|
|
|
|
if (newMembers.isNotEmpty) {
|
|
await _supabase.from('group_members').insert(newMembers);
|
|
}
|
|
|
|
print(
|
|
'[INFO] Added ${newMembers.length} users to default group (fallback method)',
|
|
);
|
|
return true;
|
|
} catch (fallbackError) {
|
|
print('[ERROR] Fallback method also failed: $fallbackError');
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create a new group (admin only) menggunakan RPC
|
|
Future<Group?> createGroupViaRPC({
|
|
required String name,
|
|
required String description,
|
|
required bool isPublic,
|
|
required bool isDefault,
|
|
String? iconUrl,
|
|
}) async {
|
|
if (currentUserId == null) {
|
|
print('[ERROR] User not authenticated');
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
// Panggil fungsi RPC di database
|
|
final response = await _supabase.rpc(
|
|
'create_group_with_creator',
|
|
params: {
|
|
'name': name,
|
|
'description': description,
|
|
'created_by': currentUserId,
|
|
'is_public': isPublic,
|
|
'is_default': isDefault,
|
|
'icon_url': iconUrl,
|
|
},
|
|
);
|
|
|
|
print('[INFO] Group created via RPC: $response');
|
|
|
|
if (response != null) {
|
|
// Buat objek Group dari hasil
|
|
final createdGroup = Group(
|
|
id: response as String,
|
|
name: name,
|
|
description: description,
|
|
createdBy: currentUserId!,
|
|
isPublic: isPublic,
|
|
isDefault: isDefault,
|
|
iconUrl: iconUrl,
|
|
);
|
|
|
|
return createdGroup;
|
|
}
|
|
|
|
return null;
|
|
} catch (e) {
|
|
print('[ERROR] Failed to create group via RPC: $e');
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Helper class for group member details
|
|
class GroupMemberDetail {
|
|
final GroupMember member;
|
|
final String username;
|
|
final String? email;
|
|
final String? avatarUrl;
|
|
|
|
GroupMemberDetail({
|
|
required this.member,
|
|
required this.username,
|
|
this.email,
|
|
this.avatarUrl,
|
|
});
|
|
}
|