feat: Implement location logs repository with CRUD operations

- Added LocationLogsRepository for managing user location logs.
- Implemented methods to get user location logs, add a new log, get recent logs, and fetch logs within a time range.

feat: Create locations repository for district-based location management

- Added LocationsRepository to handle location data by district ID.
- Implemented methods to fetch locations by district, get location by ID, find nearby locations, and add new locations.

feat: Enhance permissions repository for role-based access control

- Added PermissionsRepository to manage permissions.
- Implemented methods to fetch all permissions, get permissions by role/resource, check user permissions, create and delete permissions.

feat: Update profile repository for user profile management

- Enhanced ProfileRepository to manage user profiles.
- Implemented methods to upload avatars, update profile information, create profiles if they don't exist, and fetch profiles by user ID/NIK.

feat: Develop resources repository for resource management

- Added ResourcesRepository to manage application resources.
- Implemented methods to fetch all resources, get resources by ID/type, create, update, and delete resources.

feat: Create roles repository for role management

- Added RolesRepository to manage user roles.
- Implemented methods to fetch all roles, get roles by ID/name, create and update roles, and fetch permissions for a role.

feat: Enhance users repository for user management

- Updated UsersRepository to manage user data and metadata.
- Implemented methods to check user roles, update user metadata, email, phone, and password, and search users by name/username/email.
- Added functionality to check if a user is banned.
This commit is contained in:
vergiLgood1 2025-05-17 09:02:06 +07:00
parent 8d67e7bbb3
commit 07c53818a5
11 changed files with 1396 additions and 86 deletions

View File

@ -0,0 +1,86 @@
import 'package:get/get.dart';
import 'package:logger/logger.dart';
import 'package:sigap/src/cores/services/supabase_service.dart';
import 'package:sigap/src/features/map/models/cities_model.dart';
class CitiesRepository extends GetxController {
static CitiesRepository get instance => Get.find();
final _supabase = SupabaseService.instance.client;
final _log = Get.find<Logger>();
// Cache cities to reduce database calls
final RxList<CityModel> _citiesCache = <CityModel>[].obs;
// Get all cities
Future<List<CityModel>> getAllCities() async {
try {
// If cache exists, return it
if (_citiesCache.isNotEmpty) {
return _citiesCache;
}
final response = await _supabase
.from('cities')
.select('*, districts(*), units(*)')
.order('name');
final data = response.data as List;
final cities = data.map((json) => CityModel.fromJson(json)).toList();
// Update cache
_citiesCache.value = cities;
return cities;
} catch (e) {
_log.e('Error fetching cities: $e');
throw Exception('Failed to load cities: $e');
}
}
// Get city by ID
Future<CityModel> getCityById(String cityId) async {
try {
// Check cache first
final cachedCity = _citiesCache.firstWhereOrNull(
(city) => city.id == cityId,
);
if (cachedCity != null) {
return cachedCity;
}
final response =
await _supabase
.from('cities')
.select('*, districts(*), units(*)')
.eq('id', cityId)
.single();
return CityModel.fromJson(response.data);
} catch (e) {
_log.e('Error fetching city $cityId: $e');
throw Exception('Failed to load city details: $e');
}
}
// Get default city (Jember)
Future<CityModel> getDefaultCity() async {
try {
final response =
await _supabase
.from('cities')
.select('*, districts(*), units(*)')
.eq('name', 'Jember')
.single();
return CityModel.fromJson(response.data);
} catch (e) {
_log.e('Error fetching default city: $e');
throw Exception('Failed to load default city: $e');
}
}
// Clear cache
void clearCache() {
_citiesCache.clear();
}
}

View File

@ -0,0 +1,159 @@
import 'package:get/get.dart';
import 'package:logger/Logger.dart';
import 'package:sigap/src/cores/services/supabase_service.dart';
import 'package:sigap/src/features/map/models/demographics_model.dart';
class DemographicsRepository extends GetxController {
static DemographicsRepository get instance => Get.find();
final _supabase = SupabaseService.instance.client;
final _log = Get.find<Logger>();
// Get demographic data by district ID
Future<List<DemographicModel>> getDemographicsByDistrictId(
String districtId,
) async {
try {
final response = await _supabase
.from('demographics')
.select('*, districts(*)')
.eq('district_id', districtId)
.order('year', ascending: false);
final data = response as List;
return data.map((json) => DemographicModel.fromJson(json)).toList();
} catch (e) {
_log.e('Error fetching demographic data for district $districtId: $e');
throw Exception('Failed to load demographic data: $e');
}
}
// Get demographic data for specific year
Future<List<DemographicModel>> getDemographicsByYear(int year) async {
try {
final response = await _supabase
.from('demographics')
.select('*, districts(*)')
.eq('year', year);
final data = response.data as List;
return data.map((json) => DemographicModel.fromJson(json)).toList();
} catch (e) {
_log.e('Error fetching demographic data for year $year: $e');
throw Exception('Failed to load demographic data: $e');
}
}
// Get demographic data by district ID and year
Future<DemographicModel?> getDemographicByDistrictAndYear(
String districtId,
int year,
) async {
try {
final response =
await _supabase
.from('demographics')
.select('*, districts(*)')
.eq('district_id', districtId)
.eq('year', year)
.maybeSingle();
if (response == null) return null;
return DemographicModel.fromJson(response);
} catch (e) {
_log.e(
'Error fetching demographic data for district $districtId and year $year: $e',
);
throw Exception('Failed to load demographic data: $e');
}
}
// Get years with available demographic data
Future<List<int>> getAvailableYears() async {
try {
final response =
await _supabase.from('demographics').select('year');
final data = response as List;
return data.map<int>((json) => json['year'] as int).toList()
..sort((a, b) => b.compareTo(a)); // Sort descending
} catch (e) {
_log.e('Error fetching available demographic years: $e');
throw Exception('Failed to load available years: $e');
}
}
// Add or update demographic data
Future<DemographicModel> upsertDemographic({
String? id,
required String districtId,
required int population,
required int numberOfUnemployed,
required double populationDensity,
required int year,
}) async {
try {
final data = {
if (id != null) 'id': id,
'district_id': districtId,
'population': population,
'number_of_unemployed': numberOfUnemployed,
'population_density': populationDensity,
'year': year,
};
final response =
await _supabase
.from('demographics')
.upsert(data)
.select('*, districts(*)')
.single();
return DemographicModel.fromJson(response.data);
} catch (e) {
_log.e('Error saving demographic data: $e');
throw Exception('Failed to save demographic data: $e');
}
}
// Get districts with highest population density
Future<List<DemographicModel>> getHighestPopulationDensityDistricts(
int year, {
int limit = 10,
}) async {
try {
final response = await _supabase
.from('demographics')
.select('*, districts(*)')
.eq('year', year)
.order('population_density', ascending: false)
.limit(limit);
final data = response.data as List;
return data.map((json) => DemographicModel.fromJson(json)).toList();
} catch (e) {
_log.e('Error fetching highest population density districts: $e');
throw Exception('Failed to load population density data: $e');
}
}
// Get districts with highest unemployment rate
Future<List<DemographicModel>> getHighestUnemploymentDistricts(
int year, {
int limit = 10,
}) async {
try {
// Calculate unemployment rate as number_of_unemployed/population
final response = await _supabase.rpc(
'get_unemployment_rates',
params: {'selected_year': year, 'limit_count': limit},
);
final data = response.data as List;
return data.map((json) => DemographicModel.fromJson(json)).toList();
} catch (e) {
_log.e('Error fetching highest unemployment districts: $e');
throw Exception('Failed to load unemployment data: $e');
}
}
}

View File

@ -0,0 +1,103 @@
import 'package:get/get.dart';
import 'package:logger/logger.dart';
import 'package:sigap/src/cores/services/supabase_service.dart';
import 'package:sigap/src/features/map/models/districts_model.dart';
class DistrictsRepository extends GetxController {
static DistrictsRepository get instance => Get.find();
final _supabase = SupabaseService.instance.client;
final _log = Get.find<Logger>();
// Cache districts to reduce database calls
final RxMap<String, List<DistrictModel>> _districtsByCity =
<String, List<DistrictModel>>{}.obs;
// Get all districts for a city
Future<List<DistrictModel>> getDistrictsByCityId(String cityId) async {
try {
// If cache exists, return it
if (_districtsByCity.containsKey(cityId)) {
return _districtsByCity[cityId]!;
}
final response = await _supabase
.from('districts')
.select('*, cities(*), units(*)')
.eq('city_id', cityId)
.order('name');
final data = response.data as List;
final districts =
data.map((json) => DistrictModel.fromJson(json)).toList();
// Update cache
_districtsByCity[cityId] = districts;
return districts;
} catch (e) {
_log.e('Error fetching districts for city $cityId: $e');
throw Exception('Failed to load districts: $e');
}
}
// Get district by ID
Future<DistrictModel> getDistrictById(String districtId) async {
try {
// Check cache first
for (final cityDistricts in _districtsByCity.values) {
final cachedDistrict = cityDistricts.firstWhereOrNull(
(d) => d.id == districtId,
);
if (cachedDistrict != null) {
return cachedDistrict;
}
}
final response =
await _supabase
.from('districts')
.select(
'*, cities(*), units(*), crimes(*), demographics(*), geographics(*), locations(*)',
)
.eq('id', districtId)
.single();
return DistrictModel.fromJson(response.data);
} catch (e) {
_log.e('Error fetching district $districtId: $e');
throw Exception('Failed to load district details: $e');
}
}
// Get districts with crime data
Future<List<DistrictModel>> getDistrictsWithCrimeData(
int year,
int? month,
) async {
try {
String query = 'districts.*, cities(*), crimes!inner(*)';
var request = _supabase
.from('districts')
.select(query)
.eq('crimes.year', year);
if (month != null) {
request = request.eq('crimes.month', month);
}
final response = await request;
final data = response.data as List;
return data.map((json) => DistrictModel.fromJson(json)).toList();
} catch (e) {
_log.e('Error fetching districts with crime data: $e');
throw Exception('Failed to load districts with crime data: $e');
}
}
// Clear cache
void clearCache() {
_districtsByCity.clear();
}
}

View File

@ -0,0 +1,117 @@
import 'package:get/get.dart';
import 'package:logger/logger.dart';
import 'package:sigap/src/cores/services/supabase_service.dart';
import 'package:sigap/src/features/map/models/geographics_model.dart';
class GeographicsRepository extends GetxController {
static GeographicsRepository get instance => Get.find();
final _supabase = SupabaseService.instance.client;
final _log = Get.find<Logger>();
// Get geographic data by district ID
Future<List<GeographicModel>> getGeographicsByDistrictId(
String districtId,
) async {
try {
final response = await _supabase
.from('geographics')
.select('*, districts(*)')
.eq('district_id', districtId);
final data = response.data as List;
return data.map((json) => GeographicModel.fromJson(json)).toList();
} catch (e) {
_log.e('Error fetching geographic data for district $districtId: $e');
throw Exception('Failed to load geographic data: $e');
}
}
// Get geographic data by ID
Future<GeographicModel> getGeographicById(String geographicId) async {
try {
final response =
await _supabase
.from('geographics')
.select('*, districts(*)')
.eq('id', geographicId)
.single();
return GeographicModel.fromJson(response.data);
} catch (e) {
_log.e('Error fetching geographic data $geographicId: $e');
throw Exception('Failed to load geographic details: $e');
}
}
// Get geographic data by type
Future<List<GeographicModel>> getGeographicsByType(String type) async {
try {
final response = await _supabase
.from('geographics')
.select('*, districts(*)')
.eq('type', type);
final data = response.data as List;
return data.map((json) => GeographicModel.fromJson(json)).toList();
} catch (e) {
_log.e('Error fetching geographic data of type $type: $e');
throw Exception('Failed to load geographic data: $e');
}
}
// Get geographic data for a specific year
Future<List<GeographicModel>> getGeographicsForYear(int year) async {
try {
final response = await _supabase
.from('geographics')
.select('*, districts(*)')
.eq('year', year);
final data = response.data as List;
return data.map((json) => GeographicModel.fromJson(json)).toList();
} catch (e) {
_log.e('Error fetching geographic data for year $year: $e');
throw Exception('Failed to load geographic data: $e');
}
}
// Add or update geographic data
Future<GeographicModel> upsertGeographic({
String? id,
required String districtId,
required double latitude,
required double longitude,
String? description,
String? type,
String? address,
double? landArea,
int? year,
}) async {
try {
final data = {
if (id != null) 'id': id,
'district_id': districtId,
'latitude': latitude,
'longitude': longitude,
'description': description,
'type': type,
'address': address,
'land_area': landArea,
'year': year,
};
final response =
await _supabase
.from('geographics')
.upsert(data)
.select('*, districts(*)')
.single();
return GeographicModel.fromJson(response.data);
} catch (e) {
_log.e('Error saving geographic data: $e');
throw Exception('Failed to save geographic data: $e');
}
}
}

View File

@ -0,0 +1,102 @@
import 'package:get/get.dart';
import 'package:logger/logger.dart';
import 'package:sigap/src/cores/services/supabase_service.dart';
import 'package:sigap/src/features/map/models/location_logs_model.dart';
class LocationLogsRepository extends GetxController {
static LocationLogsRepository get instance => Get.find();
final _supabase = SupabaseService.instance.client;
final _log = Get.find<Logger>();
// Get location logs for a specific user
Future<List<LocationLogModel>> getUserLocationLogs(
String userId, {
int limit = 50,
}) async {
try {
final response = await _supabase
.from('location_logs')
.select('*, users(*)')
.eq('user_id', userId)
.order('timestamp', ascending: false)
.limit(limit);
final data = response.data as List;
return data.map((json) => LocationLogModel.fromJson(json)).toList();
} catch (e) {
_log.e('Error fetching location logs for user $userId: $e');
throw Exception('Failed to load location logs: $e');
}
}
// Add a new location log
Future<LocationLogModel> addLocationLog({
required String userId,
required double latitude,
required double longitude,
String? description,
}) async {
try {
final data = {
'user_id': userId,
'latitude': latitude,
'longitude': longitude,
'timestamp': DateTime.now().toIso8601String(),
'description': description,
};
final response =
await _supabase
.from('location_logs')
.insert(data)
.select('*, users(*)')
.single();
return LocationLogModel.fromJson(response.data);
} catch (e) {
_log.e('Error adding location log: $e');
throw Exception('Failed to add location log: $e');
}
}
// Get recent location logs for all users
Future<List<LocationLogModel>> getRecentLocationLogs({
int limit = 100,
}) async {
try {
final response = await _supabase
.from('location_logs')
.select('*, users(*)')
.order('timestamp', ascending: false)
.limit(limit);
final data = response.data as List;
return data.map((json) => LocationLogModel.fromJson(json)).toList();
} catch (e) {
_log.e('Error fetching recent location logs: $e');
throw Exception('Failed to load recent location logs: $e');
}
}
// Get location logs within a time range
Future<List<LocationLogModel>> getLocationLogsInRange(
DateTime start,
DateTime end,
) async {
try {
final response = await _supabase
.from('location_logs')
.select('*, users(*)')
.gte('timestamp', start.toIso8601String())
.lte('timestamp', end.toIso8601String())
.order('timestamp', ascending: true);
final data = response.data as List;
return data.map((json) => LocationLogModel.fromJson(json)).toList();
} catch (e) {
_log.e('Error fetching location logs in range: $e');
throw Exception('Failed to load location logs in range: $e');
}
}
}

View File

@ -0,0 +1,108 @@
import 'package:get/get.dart';
import 'package:logger/logger.dart';
import 'package:sigap/src/cores/services/supabase_service.dart';
import 'package:sigap/src/features/map/models/locations_model.dart';
class LocationsRepository extends GetxController {
static LocationsRepository get instance => Get.find();
final _supabase = SupabaseService.instance.client;
final _log = Get.find<Logger>();
// Get locations by district ID
Future<List<LocationModel>> getLocationsByDistrictId(
String districtId,
) async {
try {
final response = await _supabase
.from('locations')
.select(
'*, event(*), incident_logs(*), crime_incidents(*), patrol_units(*)',
)
.eq('district_id', districtId);
final data = response.data as List;
return data.map((json) => LocationModel.fromJson(json)).toList();
} catch (e) {
_log.e('Error fetching locations for district $districtId: $e');
throw Exception('Failed to load locations: $e');
}
}
// Get location by ID
Future<LocationModel> getLocationById(String locationId) async {
try {
final response =
await _supabase
.from('locations')
.select(
'*, event(*), incident_logs(*), crime_incidents(*), patrol_units(*)',
)
.eq('id', locationId)
.single();
return LocationModel.fromJson(response.data);
} catch (e) {
_log.e('Error fetching location $locationId: $e');
throw Exception('Failed to load location details: $e');
}
}
// Get locations within a radius (kilometers) of a point
Future<List<LocationModel>> getNearbyLocations(
double latitude,
double longitude,
double radiusKm,
) async {
try {
// Use PostGIS to find locations within the radius
final response = await _supabase.rpc(
'locations_within_radius',
params: {
'lat': latitude,
'lng': longitude,
'radius': radiusKm * 1000, // Convert to meters
},
);
final data = response.data as List;
return data.map((json) => LocationModel.fromJson(json)).toList();
} catch (e) {
_log.e('Error fetching nearby locations: $e');
throw Exception('Failed to load nearby locations: $e');
}
}
// Add a new location
Future<LocationModel> addLocation({
required String districtId,
required String eventId,
required double latitude,
required double longitude,
String? address,
String? type,
}) async {
try {
final data = {
'district_id': districtId,
'event_id': eventId,
'latitude': latitude,
'longitude': longitude,
'address': address,
'type': type,
};
final response =
await _supabase
.from('locations')
.insert(data)
.select('*, event(*)')
.single();
return LocationModel.fromJson(response.data);
} catch (e) {
_log.e('Error adding location: $e');
throw Exception('Failed to add location: $e');
}
}
}

View File

@ -0,0 +1,169 @@
import 'package:get/get.dart';
import 'package:logger/Logger.dart';
import 'package:sigap/src/cores/services/supabase_service.dart';
import 'package:sigap/src/features/personalization/models/permissions_model.dart';
import 'package:sigap/src/utils/exceptions/exceptions.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
class PermissionsRepository extends GetxController {
static PermissionsRepository get instance => Get.find();
final _supabase = SupabaseService.instance.client;
final _logger = Get.find<Logger>();
// Get all permissions
Future<List<PermissionModel>> getAllPermissions() async {
try {
final permissions = await _supabase
.from('permissions')
.select('*, resource(*), role(*)')
.order('action');
return (permissions as List)
.map((permission) => PermissionModel.fromJson(permission))
.toList();
} on PostgrestException catch (error) {
_logger.e('PostgrestException in getAllPermissions: ${error.message}');
throw TExceptions.fromCode(error.code ?? 'unknown-error');
} catch (e) {
_logger.e('Exception in getAllPermissions: $e');
throw 'Failed to fetch permissions: ${e.toString()}';
}
}
// Get permissions by role ID
Future<List<PermissionModel>> getPermissionsByRole(String roleId) async {
try {
final permissions = await _supabase
.from('permissions')
.select('*, resource(*), role(*)')
.eq('role_id', roleId);
return (permissions as List)
.map((permission) => PermissionModel.fromJson(permission))
.toList();
} on PostgrestException catch (error) {
_logger.e('PostgrestException in getPermissionsByRole: ${error.message}');
throw TExceptions.fromCode(error.code ?? 'unknown-error');
} catch (e) {
_logger.e('Exception in getPermissionsByRole: $e');
throw 'Failed to fetch role permissions: ${e.toString()}';
}
}
// Get permissions by resource ID
Future<List<PermissionModel>> getPermissionsByResource(
String resourceId,
) async {
try {
final permissions = await _supabase
.from('permissions')
.select('*, resource(*), role(*)')
.eq('resource_id', resourceId);
return (permissions as List)
.map((permission) => PermissionModel.fromJson(permission))
.toList();
} on PostgrestException catch (error) {
_logger.e(
'PostgrestException in getPermissionsByResource: ${error.message}',
);
throw TExceptions.fromCode(error.code ?? 'unknown-error');
} catch (e) {
_logger.e('Exception in getPermissionsByResource: $e');
throw 'Failed to fetch resource permissions: ${e.toString()}';
}
}
// Check if the current user has a specific permission
Future<bool> checkPermission({
required String action,
required String resourceName,
}) async {
try {
// Get current user's role ID
final user = _supabase.auth.currentUser;
if (user == null) return false;
final userData =
await _supabase
.from('users')
.select('roles_id')
.eq('id', user.id)
.single();
final roleId = userData['roles_id'] as String;
// Get resource ID by name
final resourceData =
await _supabase
.from('resources')
.select('id')
.eq('name', resourceName)
.single();
final resourceId = resourceData['id'] as String;
// Check if permission exists
final permissions = await _supabase
.from('permissions')
.select()
.eq('role_id', roleId)
.eq('resource_id', resourceId)
.eq('action', action);
return (permissions as List).isNotEmpty;
} on PostgrestException catch (error) {
_logger.e('PostgrestException in checkPermission: ${error.message}');
// If no rows found, return false instead of throwing an exception
if (error.code == 'PGRST116') return false;
throw TExceptions.fromCode(error.code ?? 'unknown-error');
} catch (e) {
_logger.e('Exception in checkPermission: $e');
return false; // Default to no permission on error
}
}
// Create a new permission
Future<PermissionModel> createPermission({
required String action,
required String resourceId,
required String roleId,
}) async {
try {
final data = {
'action': action,
'resource_id': resourceId,
'role_id': roleId,
};
final createdPermission =
await _supabase
.from('permissions')
.insert(data)
.select('*, resource(*), role(*)')
.single();
return PermissionModel.fromJson(createdPermission);
} on PostgrestException catch (error) {
_logger.e('PostgrestException in createPermission: ${error.message}');
throw TExceptions.fromCode(error.code ?? 'unknown-error');
} catch (e) {
_logger.e('Exception in createPermission: $e');
throw 'Failed to create permission: ${e.toString()}';
}
}
// Delete a permission
Future<void> deletePermission(String permissionId) async {
try {
await _supabase.from('permissions').delete().eq('id', permissionId);
} on PostgrestException catch (error) {
_logger.e('PostgrestException in deletePermission: ${error.message}');
throw TExceptions.fromCode(error.code ?? 'unknown-error');
} catch (e) {
_logger.e('Exception in deletePermission: $e');
throw 'Failed to delete permission: ${e.toString()}';
}
}
}

View File

@ -2,6 +2,8 @@ import 'dart:io';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:logger/Logger.dart';
import 'package:sigap/src/cores/services/supabase_service.dart';
import 'package:sigap/src/features/personalization/models/profile_model.dart'; import 'package:sigap/src/features/personalization/models/profile_model.dart';
import 'package:sigap/src/utils/exceptions/exceptions.dart'; import 'package:sigap/src/utils/exceptions/exceptions.dart';
import 'package:sigap/src/utils/exceptions/format_exceptions.dart'; import 'package:sigap/src/utils/exceptions/format_exceptions.dart';
@ -11,7 +13,8 @@ import 'package:supabase_flutter/supabase_flutter.dart';
class ProfileRepository extends GetxController { class ProfileRepository extends GetxController {
static ProfileRepository get instance => Get.find(); static ProfileRepository get instance => Get.find();
final _supabase = Supabase.instance.client; final _supabase = SupabaseService.instance.client;
final _logger = Get.find<Logger>();
// Get current user ID // Get current user ID
String? get currentUserId => _supabase.auth.currentUser?.id; String? get currentUserId => _supabase.auth.currentUser?.id;
@ -32,17 +35,19 @@ class ProfileRepository extends GetxController {
return ProfileModel.fromJson(profileData); return ProfileModel.fromJson(profileData);
} on PostgrestException catch (error) { } on PostgrestException catch (error) {
throw TExceptions.fromCode(error.code!); _logger.e('PostgrestException in getProfileData: ${error.message}');
throw TExceptions.fromCode(error.code ?? 'unknown-error');
} on FormatException catch (_) { } on FormatException catch (_) {
throw const TFormatException(); throw const TFormatException();
} on PlatformException catch (e) { } on PlatformException catch (e) {
throw TPlatformException(e.code).message; throw TPlatformException(e.code).message;
} catch (e) { } catch (e) {
_logger.e('Exception in getProfileData: $e');
throw 'Failed to fetch profile data: ${e.toString()}'; throw 'Failed to fetch profile data: ${e.toString()}';
} }
} }
// Update avatar // Upload avatar
Future<String> uploadAvatar(String filePath) async { Future<String> uploadAvatar(String filePath) async {
try { try {
if (currentUserId == null) { if (currentUserId == null) {
@ -51,7 +56,7 @@ class ProfileRepository extends GetxController {
final fileName = final fileName =
'${currentUserId}_${DateTime.now().millisecondsSinceEpoch}.jpg'; '${currentUserId}_${DateTime.now().millisecondsSinceEpoch}.jpg';
final storageResponse = await _supabase.storage await _supabase.storage
.from('avatars') .from('avatars')
.upload(fileName, File(filePath)); .upload(fileName, File(filePath));
@ -67,36 +72,109 @@ class ProfileRepository extends GetxController {
return avatarUrl; return avatarUrl;
} on StorageException catch (e) { } on StorageException catch (e) {
_logger.e('StorageException in uploadAvatar: ${e.message}');
throw 'Storage error: ${e.message}'; throw 'Storage error: ${e.message}';
} on PostgrestException catch (error) { } on PostgrestException catch (error) {
throw TExceptions.fromCode(error.code!); _logger.e('PostgrestException in uploadAvatar: ${error.message}');
throw TExceptions.fromCode(error.code ?? 'unknown-error');
} catch (e) { } catch (e) {
_logger.e('Exception in uploadAvatar: $e');
throw 'Failed to upload avatar: ${e.toString()}'; throw 'Failed to upload avatar: ${e.toString()}';
} }
} }
// Update profile information // Update profile
Future<ProfileModel> updateProfile(ProfileModel profile) async { Future<ProfileModel> updateProfile({
String? firstName,
String? lastName,
String? bio,
Map<String, dynamic>? address,
DateTime? birthDate,
String? avatar,
String? username,
}) async {
try { try {
if (currentUserId == null) { if (currentUserId == null) {
throw 'User not authenticated'; throw 'User not authenticated';
} }
final updatedProfileData = profile.toJson(); // Build update data object
final Map<String, dynamic> updateData = {};
await _supabase if (firstName != null) updateData['first_name'] = firstName;
.from('profiles') if (lastName != null) updateData['last_name'] = lastName;
.update(updatedProfileData) if (bio != null) updateData['bio'] = bio;
.eq('user_id', currentUserId!); if (address != null) updateData['address'] = address;
if (birthDate != null)
updateData['birth_date'] = birthDate.toIso8601String();
if (avatar != null) updateData['avatar'] = avatar;
if (username != null) updateData['username'] = username;
// Only update if there's data to update
if (updateData.isNotEmpty) {
await _supabase
.from('profiles')
.update(updateData)
.eq('user_id', currentUserId!);
}
// Fetch and return updated profile
return await getProfileData(); return await getProfileData();
} on PostgrestException catch (error) { } on PostgrestException catch (error) {
throw TExceptions.fromCode(error.code!); _logger.e('PostgrestException in updateProfile: ${error.message}');
throw TExceptions.fromCode(error.code ?? 'unknown-error');
} catch (e) { } catch (e) {
_logger.e('Exception in updateProfile: $e');
throw 'Failed to update profile: ${e.toString()}'; throw 'Failed to update profile: ${e.toString()}';
} }
} }
// Create profile if it doesn't exist
Future<ProfileModel> createProfileIfNotExists({
required String userId,
required String nik,
String? firstName,
String? lastName,
}) async {
try {
// Check if profile exists
final existingProfiles = await _supabase
.from('profiles')
.select()
.eq('user_id', userId);
// If profile exists, return it
if ((existingProfiles as List).isNotEmpty) {
return ProfileModel.fromJson(existingProfiles.first);
}
// Create new profile
final profileData = {
'user_id': userId,
'nik': nik,
'first_name': firstName ?? '',
'last_name': lastName,
};
final createdProfile =
await _supabase
.from('profiles')
.insert(profileData)
.select()
.single();
return ProfileModel.fromJson(createdProfile);
} on PostgrestException catch (error) {
_logger.e(
'PostgrestException in createProfileIfNotExists: ${error.message}',
);
throw TExceptions.fromCode(error.code ?? 'unknown-error');
} catch (e) {
_logger.e('Exception in createProfileIfNotExists: $e');
throw 'Failed to create profile: ${e.toString()}';
}
}
// Get profile by user ID // Get profile by user ID
Future<ProfileModel> getProfileByUserId(String userId) async { Future<ProfileModel> getProfileByUserId(String userId) async {
try { try {
@ -109,9 +187,34 @@ class ProfileRepository extends GetxController {
return ProfileModel.fromJson(profileData); return ProfileModel.fromJson(profileData);
} on PostgrestException catch (error) { } on PostgrestException catch (error) {
throw TExceptions.fromCode(error.code!); _logger.e('PostgrestException in getProfileByUserId: ${error.message}');
throw TExceptions.fromCode(error.code ?? 'unknown-error');
} catch (e) { } catch (e) {
_logger.e('Exception in getProfileByUserId: $e');
throw 'Failed to fetch profile data: ${e.toString()}'; throw 'Failed to fetch profile data: ${e.toString()}';
} }
} }
// Get profile by NIK
Future<ProfileModel?> getProfileByNIK(String nik) async {
try {
final profiles =
await _supabase
.from('profiles')
.select()
.eq('nik', nik)
.maybeSingle();
if (profiles == null) return null;
return ProfileModel.fromJson(profiles);
} on PostgrestException catch (error) {
_logger.e('PostgrestException in getProfileByNIK: ${error.message}');
// If no rows found, return null instead of throwing an exception
if (error.code == 'PGRST116') return null;
throw TExceptions.fromCode(error.code ?? 'unknown-error');
} catch (e) {
_logger.e('Exception in getProfileByNIK: $e');
throw 'Failed to fetch profile by NIK: ${e.toString()}';
}
}
} }

View File

@ -0,0 +1,128 @@
import 'package:get/get.dart';
import 'package:logger/logger.dart';
import 'package:sigap/src/cores/services/supabase_service.dart';
import 'package:sigap/src/features/personalization/models/resources_model.dart';
import 'package:sigap/src/utils/exceptions/exceptions.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
class ResourcesRepository extends GetxController {
static ResourcesRepository get instance => Get.find();
final _supabase = SupabaseService.instance.client;
final _logger = Get.find<Logger>();
// Get all resources
Future<List<ResourceModel>> getAllResources() async {
try {
final resources = await _supabase
.from('resources')
.select('*, permissions(*)')
.order('name');
return (resources as List)
.map((resource) => ResourceModel.fromJson(resource))
.toList();
} on PostgrestException catch (error) {
_logger.e('PostgrestException in getAllResources: ${error.message}');
throw TExceptions.fromCode(error.code ?? 'unknown-error');
} catch (e) {
_logger.e('Exception in getAllResources: $e');
throw 'Failed to fetch resources: ${e.toString()}';
}
}
// Get resource by ID
Future<ResourceModel> getResourceById(String resourceId) async {
try {
final resource =
await _supabase
.from('resources')
.select('*, permissions(*)')
.eq('id', resourceId)
.single();
return ResourceModel.fromJson(resource);
} on PostgrestException catch (error) {
_logger.e('PostgrestException in getResourceById: ${error.message}');
throw TExceptions.fromCode(error.code ?? 'unknown-error');
} catch (e) {
_logger.e('Exception in getResourceById: $e');
throw 'Failed to fetch resource: ${e.toString()}';
}
}
// Get resources by type
Future<List<ResourceModel>> getResourcesByType(String type) async {
try {
final resources = await _supabase
.from('resources')
.select('*, permissions(*)')
.eq('type', type)
.order('name');
return (resources as List)
.map((resource) => ResourceModel.fromJson(resource))
.toList();
} on PostgrestException catch (error) {
_logger.e('PostgrestException in getResourcesByType: ${error.message}');
throw TExceptions.fromCode(error.code ?? 'unknown-error');
} catch (e) {
_logger.e('Exception in getResourcesByType: $e');
throw 'Failed to fetch resources by type: ${e.toString()}';
}
}
// Create a new resource
Future<ResourceModel> createResource(ResourceModel resource) async {
try {
final createdResource =
await _supabase
.from('resources')
.insert(resource.toJson())
.select()
.single();
return ResourceModel.fromJson(createdResource);
} on PostgrestException catch (error) {
_logger.e('PostgrestException in createResource: ${error.message}');
throw TExceptions.fromCode(error.code ?? 'unknown-error');
} catch (e) {
_logger.e('Exception in createResource: $e');
throw 'Failed to create resource: ${e.toString()}';
}
}
// Update a resource
Future<ResourceModel> updateResource(ResourceModel resource) async {
try {
final updatedResource =
await _supabase
.from('resources')
.update(resource.toJson())
.eq('id', resource.id)
.select()
.single();
return ResourceModel.fromJson(updatedResource);
} on PostgrestException catch (error) {
_logger.e('PostgrestException in updateResource: ${error.message}');
throw TExceptions.fromCode(error.code ?? 'unknown-error');
} catch (e) {
_logger.e('Exception in updateResource: $e');
throw 'Failed to update resource: ${e.toString()}';
}
}
// Delete a resource
Future<void> deleteResource(String resourceId) async {
try {
await _supabase.from('resources').delete().eq('id', resourceId);
} on PostgrestException catch (error) {
_logger.e('PostgrestException in deleteResource: ${error.message}');
throw TExceptions.fromCode(error.code ?? 'unknown-error');
} catch (e) {
_logger.e('Exception in deleteResource: $e');
throw 'Failed to delete resource: ${e.toString()}';
}
}
}

View File

@ -1,4 +1,7 @@
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:logger/Logger.dart';
import 'package:sigap/src/cores/services/supabase_service.dart';
import 'package:sigap/src/features/personalization/models/permissions_model.dart';
import 'package:sigap/src/features/personalization/models/roles_model.dart'; import 'package:sigap/src/features/personalization/models/roles_model.dart';
import 'package:sigap/src/utils/exceptions/exceptions.dart'; import 'package:sigap/src/utils/exceptions/exceptions.dart';
import 'package:supabase_flutter/supabase_flutter.dart'; import 'package:supabase_flutter/supabase_flutter.dart';
@ -6,17 +9,23 @@ import 'package:supabase_flutter/supabase_flutter.dart';
class RolesRepository extends GetxController { class RolesRepository extends GetxController {
static RolesRepository get instance => Get.find(); static RolesRepository get instance => Get.find();
final _supabase = Supabase.instance.client; final _supabase = SupabaseService.instance.client;
final _logger = Get.find<Logger>();
// Get all roles // Get all roles
Future<List<RoleModel>> getAllRoles() async { Future<List<RoleModel>> getAllRoles() async {
try { try {
final roles = await _supabase.from('roles').select().order('name'); final roles = await _supabase
.from('roles')
.select('*, permissions(*)')
.order('name');
return (roles as List).map((role) => RoleModel.fromJson(role)).toList(); return (roles as List).map((role) => RoleModel.fromJson(role)).toList();
} on PostgrestException catch (error) { } on PostgrestException catch (error) {
throw TExceptions.fromCode(error.code!); _logger.e('PostgrestException in getAllRoles: ${error.message}');
throw TExceptions.fromCode(error.code ?? 'unknown-error');
} catch (e) { } catch (e) {
_logger.e('Exception in getAllRoles: $e');
throw 'Failed to fetch roles: ${e.toString()}'; throw 'Failed to fetch roles: ${e.toString()}';
} }
} }
@ -25,12 +34,18 @@ class RolesRepository extends GetxController {
Future<RoleModel> getRoleById(String roleId) async { Future<RoleModel> getRoleById(String roleId) async {
try { try {
final role = final role =
await _supabase.from('roles').select().eq('id', roleId).single(); await _supabase
.from('roles')
.select('*, permissions(*)')
.eq('id', roleId)
.single();
return RoleModel.fromJson(role); return RoleModel.fromJson(role);
} on PostgrestException catch (error) { } on PostgrestException catch (error) {
throw TExceptions.fromCode(error.code!); _logger.e('PostgrestException in getRoleById: ${error.message}');
throw TExceptions.fromCode(error.code ?? 'unknown-error');
} catch (e) { } catch (e) {
_logger.e('Exception in getRoleById: $e');
throw 'Failed to fetch role data: ${e.toString()}'; throw 'Failed to fetch role data: ${e.toString()}';
} }
} }
@ -39,13 +54,82 @@ class RolesRepository extends GetxController {
Future<RoleModel> getRoleByName(String roleName) async { Future<RoleModel> getRoleByName(String roleName) async {
try { try {
final role = final role =
await _supabase.from('roles').select().eq('name', roleName).single(); await _supabase
.from('roles')
.select('*, permissions(*)')
.eq('name', roleName)
.single();
return RoleModel.fromJson(role); return RoleModel.fromJson(role);
} on PostgrestException catch (error) { } on PostgrestException catch (error) {
throw TExceptions.fromCode(error.code!); _logger.e('PostgrestException in getRoleByName: ${error.message}');
throw TExceptions.fromCode(error.code ?? 'unknown-error');
} catch (e) { } catch (e) {
_logger.e('Exception in getRoleByName: $e');
throw 'Failed to fetch role data: ${e.toString()}'; throw 'Failed to fetch role data: ${e.toString()}';
} }
} }
// Create a new role
Future<RoleModel> createRole({
required String name,
String? description,
String? icon,
}) async {
try {
final data = {'name': name, 'description': description, 'icon': icon};
final createdRole =
await _supabase.from('roles').insert(data).select().single();
return RoleModel.fromJson(createdRole);
} on PostgrestException catch (error) {
_logger.e('PostgrestException in createRole: ${error.message}');
throw TExceptions.fromCode(error.code ?? 'unknown-error');
} catch (e) {
_logger.e('Exception in createRole: $e');
throw 'Failed to create role: ${e.toString()}';
}
}
// Update a role
Future<RoleModel> updateRole(RoleModel role) async {
try {
final updatedRole =
await _supabase
.from('roles')
.update(role.toJson())
.eq('id', role.id)
.select()
.single();
return RoleModel.fromJson(updatedRole);
} on PostgrestException catch (error) {
_logger.e('PostgrestException in updateRole: ${error.message}');
throw TExceptions.fromCode(error.code ?? 'unknown-error');
} catch (e) {
_logger.e('Exception in updateRole: $e');
throw 'Failed to update role: ${e.toString()}';
}
}
// Get permissions for a role
Future<List<PermissionModel>> getRolePermissions(String roleId) async {
try {
final permissions = await _supabase
.from('permissions')
.select('*, resource(*), role(*)')
.eq('role_id', roleId);
return (permissions as List)
.map((permission) => PermissionModel.fromJson(permission))
.toList();
} on PostgrestException catch (error) {
_logger.e('PostgrestException in getRolePermissions: ${error.message}');
throw TExceptions.fromCode(error.code ?? 'unknown-error');
} catch (e) {
_logger.e('Exception in getRolePermissions: $e');
throw 'Failed to fetch role permissions: ${e.toString()}';
}
}
} }

View File

@ -1,7 +1,9 @@
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:logger/Logger.dart';
import 'package:sigap/src/cores/services/supabase_service.dart'; import 'package:sigap/src/cores/services/supabase_service.dart';
import 'package:sigap/src/features/personalization/models/index.dart'; import 'package:sigap/src/features/personalization/models/roles_model.dart';
import 'package:sigap/src/features/personalization/models/users_model.dart';
import 'package:sigap/src/utils/exceptions/exceptions.dart'; import 'package:sigap/src/utils/exceptions/exceptions.dart';
import 'package:sigap/src/utils/exceptions/format_exceptions.dart'; import 'package:sigap/src/utils/exceptions/format_exceptions.dart';
import 'package:sigap/src/utils/exceptions/platform_exceptions.dart'; import 'package:sigap/src/utils/exceptions/platform_exceptions.dart';
@ -11,14 +13,18 @@ class UserRepository extends GetxController {
static UserRepository get instance => Get.find(); static UserRepository get instance => Get.find();
final _supabase = SupabaseService.instance.client; final _supabase = SupabaseService.instance.client;
final _logger = Get.find<Logger>();
// Get current user ID // Get current user ID
String? get currentUserId => SupabaseService.instance.currentUserId; String? get currentUserId => SupabaseService.instance.currentUserId;
// Check if user is authenticated
bool get isAuthenticated => currentUserId != null;
// Check if user is an officer // Check if user is an officer
Future<bool> isCurrentUserOfficer() async { Future<bool> isCurrentUserOfficer() async {
try { try {
if (currentUserId == null) return false; if (!isAuthenticated) return false;
final user = _supabase.auth.currentUser; final user = _supabase.auth.currentUser;
if (user == null) return false; if (user == null) return false;
@ -29,29 +35,30 @@ class UserRepository extends GetxController {
return metadata['is_officer'] == true; return metadata['is_officer'] == true;
} }
// If no flag in metadata, check if user exists in officers table // If no flag in metadata, check role information
final officerData = final userData =
await _supabase await _supabase
.from('officers') .from('users')
.select() .select('role:roles(name)')
.eq('id', currentUserId!) .eq('id', currentUserId!)
.single(); .single();
return officerData != null; final roleName =
(userData['role'] as Map<String, dynamic>)['name'] as String;
return roleName.toLowerCase() == 'officer';
} on PostgrestException catch (error) { } on PostgrestException catch (error) {
// PGRST116 means no rows returned - not an officer _logger.e('PostgrestException in isCurrentUserOfficer: ${error.message}');
if (error.code == 'PGRST116') return false; return false; // Default to not an officer
throw TExceptions.fromCode(error.code!);
} catch (e) { } catch (e) {
// Default to not an officer on error _logger.e('Exception in isCurrentUserOfficer: $e');
return false; return false; // Default to not an officer
} }
} }
// Get user data for regular users // Get current user data
Future<UserModel> getUserData() async { Future<UserModel> getCurrentUserData() async {
try { try {
if (currentUserId == null) { if (!isAuthenticated) {
throw 'User not authenticated'; throw 'User not authenticated';
} }
@ -64,70 +71,155 @@ class UserRepository extends GetxController {
return UserModel.fromJson(userData); return UserModel.fromJson(userData);
} on PostgrestException catch (error) { } on PostgrestException catch (error) {
throw TExceptions.fromCode(error.code!); _logger.e('PostgrestException in getCurrentUserData: ${error.message}');
throw TExceptions.fromCode(error.code ?? 'unknown-error');
} on FormatException catch (_) { } on FormatException catch (_) {
throw const TFormatException(); throw const TFormatException();
} on PlatformException catch (e) { } on PlatformException catch (e) {
throw TPlatformException(e.code).message; throw TPlatformException(e.code).message;
} catch (e) { } catch (e) {
_logger.e('Exception in getCurrentUserData: $e');
throw 'Failed to fetch user data: ${e.toString()}'; throw 'Failed to fetch user data: ${e.toString()}';
} }
} }
// Update user profile data // Update user metadata
Future<void> updateUserProfile({ Future<void> updateUserMetadata(Map<String, dynamic> metadata) async {
String? firstName,
String? lastName,
String? bio,
String? address,
String? birthDate,
String? avatar,
String? username,
String? phone,
}) async {
try { try {
if (currentUserId == null) { if (!isAuthenticated) {
throw 'User not authenticated'; throw 'User not authenticated';
} }
// First update user profile await _supabase.auth.updateUser(UserAttributes(data: metadata));
final Map<String, dynamic> profileData = {};
if (firstName != null) profileData['first_name'] = firstName;
if (lastName != null) profileData['last_name'] = lastName;
if (bio != null) profileData['bio'] = bio;
if (address != null) profileData['address'] = address;
if (birthDate != null) profileData['birth_date'] = birthDate;
if (avatar != null) profileData['avatar'] = avatar;
if (username != null) profileData['username'] = username;
if (profileData.isNotEmpty) {
await _supabase
.from('profiles')
.update(profileData)
.eq('user_id', currentUserId!);
}
// Then update user table if needed
if (phone != null) {
await _supabase
.from('users')
.update({'phone': phone})
.eq('id', currentUserId!);
// Also update auth user phone
await _supabase.auth.updateUser(UserAttributes(phone: phone));
}
} on PostgrestException catch (error) {
throw TExceptions.fromCode(error.code!);
} on AuthException catch (e) { } on AuthException catch (e) {
_logger.e('AuthException in updateUserMetadata: ${e.message}');
throw TExceptions(e.message); throw TExceptions(e.message);
} on FormatException catch (_) {
throw const TFormatException();
} on PlatformException catch (e) {
throw TPlatformException(e.code).message;
} catch (e) { } catch (e) {
throw 'Failed to update profile: ${e.toString()}'; _logger.e('Exception in updateUserMetadata: $e');
throw 'Failed to update user metadata: ${e.toString()}';
}
}
// Update user email
Future<void> updateUserEmail(String newEmail) async {
try {
if (!isAuthenticated) {
throw 'User not authenticated';
}
await _supabase.auth.updateUser(UserAttributes(email: newEmail));
// Also update in users table
await _supabase
.from('users')
.update({'email': newEmail})
.eq('id', currentUserId!);
} on AuthException catch (e) {
_logger.e('AuthException in updateUserEmail: ${e.message}');
throw TExceptions(e.message);
} on PostgrestException catch (error) {
_logger.e('PostgrestException in updateUserEmail: ${error.message}');
throw TExceptions.fromCode(error.code ?? 'unknown-error');
} catch (e) {
_logger.e('Exception in updateUserEmail: $e');
throw 'Failed to update user email: ${e.toString()}';
}
}
// Update user phone
Future<void> updateUserPhone(String newPhone) async {
try {
if (!isAuthenticated) {
throw 'User not authenticated';
}
await _supabase.auth.updateUser(UserAttributes(phone: newPhone));
// Also update in users table
await _supabase
.from('users')
.update({'phone': newPhone})
.eq('id', currentUserId!);
} on AuthException catch (e) {
_logger.e('AuthException in updateUserPhone: ${e.message}');
throw TExceptions(e.message);
} on PostgrestException catch (error) {
_logger.e('PostgrestException in updateUserPhone: ${error.message}');
throw TExceptions.fromCode(error.code ?? 'unknown-error');
} catch (e) {
_logger.e('Exception in updateUserPhone: $e');
throw 'Failed to update user phone: ${e.toString()}';
}
}
// Update user password
Future<void> updateUserPassword(String newPassword) async {
try {
if (!isAuthenticated) {
throw 'User not authenticated';
}
await _supabase.auth.updateUser(UserAttributes(password: newPassword));
} on AuthException catch (e) {
_logger.e('AuthException in updateUserPassword: ${e.message}');
throw TExceptions(e.message);
} catch (e) {
_logger.e('Exception in updateUserPassword: $e');
throw 'Failed to update user password: ${e.toString()}';
}
}
// Update user role
Future<void> updateUserRole(String roleId) async {
try {
if (!isAuthenticated) {
throw 'User not authenticated';
}
// Update in users table
await _supabase
.from('users')
.update({'roles_id': roleId})
.eq('id', currentUserId!);
// Get role details
final roleData =
await _supabase.from('roles').select().eq('id', roleId).single();
final role = RoleModel.fromJson(roleData);
// Update metadata with role information
await updateUserMetadata({
'is_officer': role.isOfficer,
'role_name': role.name,
});
} on PostgrestException catch (error) {
_logger.e('PostgrestException in updateUserRole: ${error.message}');
throw TExceptions.fromCode(error.code ?? 'unknown-error');
} catch (e) {
_logger.e('Exception in updateUserRole: $e');
throw 'Failed to update user role: ${e.toString()}';
}
}
// Get users by role ID
Future<List<UserModel>> getUsersByRoleId(String roleId) async {
try {
final users = await _supabase
.from('users')
.select('*, profiles(*), role:roles(*)')
.eq('roles_id', roleId);
return (users as List).map((user) => UserModel.fromJson(user)).toList();
} on PostgrestException catch (error) {
_logger.e('PostgrestException in getUsersByRoleId: ${error.message}');
throw TExceptions.fromCode(error.code ?? 'unknown-error');
} catch (e) {
_logger.e('Exception in getUsersByRoleId: $e');
throw 'Failed to fetch users by role: ${e.toString()}';
} }
} }
@ -143,9 +235,68 @@ class UserRepository extends GetxController {
return UserModel.fromJson(userData); return UserModel.fromJson(userData);
} on PostgrestException catch (error) { } on PostgrestException catch (error) {
throw TExceptions.fromCode(error.code!); _logger.e('PostgrestException in getUserById: ${error.message}');
throw TExceptions.fromCode(error.code ?? 'unknown-error');
} catch (e) { } catch (e) {
_logger.e('Exception in getUserById: $e');
throw 'Failed to fetch user data: ${e.toString()}'; throw 'Failed to fetch user data: ${e.toString()}';
} }
} }
// Search users by name/username/email
Future<List<UserModel>> searchUsers(String query, {int limit = 20}) async {
try {
final users = await _supabase
.from('users')
.select('*, profiles(*), role:roles(*)')
.or(
'email.ilike.%$query%, profiles.first_name.ilike.%$query%, profiles.last_name.ilike.%$query%, profiles.username.ilike.%$query%',
)
.limit(limit);
return (users as List).map((user) => UserModel.fromJson(user)).toList();
} on PostgrestException catch (error) {
_logger.e('PostgrestException in searchUsers: ${error.message}');
throw TExceptions.fromCode(error.code ?? 'unknown-error');
} catch (e) {
_logger.e('Exception in searchUsers: $e');
throw 'Failed to search users: ${e.toString()}';
}
}
// Check if user is banned
Future<bool> isUserBanned({String? userId}) async {
try {
final id = userId ?? currentUserId;
if (id == null) {
throw 'User ID is required';
}
final userData =
await _supabase
.from('users')
.select('is_banned, banned_until')
.eq('id', id)
.single();
// Check if explicitly banned
bool isBanned = userData['is_banned'] == true;
// Check if temporary ban is still active
if (!isBanned && userData['banned_until'] != null) {
final bannedUntil = DateTime.parse(userData['banned_until']);
if (bannedUntil.isAfter(DateTime.now())) {
isBanned = true;
}
}
return isBanned;
} on PostgrestException catch (error) {
_logger.e('PostgrestException in isUserBanned: ${error.message}');
return false; // Default to not banned
} catch (e) {
_logger.e('Exception in isUserBanned: $e');
return false; // Default to not banned
}
}
} }