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:
parent
8d67e7bbb3
commit
07c53818a5
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()}';
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,6 +2,8 @@ import 'dart:io';
|
|||
|
||||
import 'package:flutter/services.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/utils/exceptions/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 {
|
||||
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
|
||||
String? get currentUserId => _supabase.auth.currentUser?.id;
|
||||
|
@ -32,17 +35,19 @@ class ProfileRepository extends GetxController {
|
|||
|
||||
return ProfileModel.fromJson(profileData);
|
||||
} 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 (_) {
|
||||
throw const TFormatException();
|
||||
} on PlatformException catch (e) {
|
||||
throw TPlatformException(e.code).message;
|
||||
} catch (e) {
|
||||
_logger.e('Exception in getProfileData: $e');
|
||||
throw 'Failed to fetch profile data: ${e.toString()}';
|
||||
}
|
||||
}
|
||||
|
||||
// Update avatar
|
||||
// Upload avatar
|
||||
Future<String> uploadAvatar(String filePath) async {
|
||||
try {
|
||||
if (currentUserId == null) {
|
||||
|
@ -51,7 +56,7 @@ class ProfileRepository extends GetxController {
|
|||
|
||||
final fileName =
|
||||
'${currentUserId}_${DateTime.now().millisecondsSinceEpoch}.jpg';
|
||||
final storageResponse = await _supabase.storage
|
||||
await _supabase.storage
|
||||
.from('avatars')
|
||||
.upload(fileName, File(filePath));
|
||||
|
||||
|
@ -67,36 +72,109 @@ class ProfileRepository extends GetxController {
|
|||
|
||||
return avatarUrl;
|
||||
} on StorageException catch (e) {
|
||||
_logger.e('StorageException in uploadAvatar: ${e.message}');
|
||||
throw 'Storage error: ${e.message}';
|
||||
} 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) {
|
||||
_logger.e('Exception in uploadAvatar: $e');
|
||||
throw 'Failed to upload avatar: ${e.toString()}';
|
||||
}
|
||||
}
|
||||
|
||||
// Update profile information
|
||||
Future<ProfileModel> updateProfile(ProfileModel profile) async {
|
||||
// Update profile
|
||||
Future<ProfileModel> updateProfile({
|
||||
String? firstName,
|
||||
String? lastName,
|
||||
String? bio,
|
||||
Map<String, dynamic>? address,
|
||||
DateTime? birthDate,
|
||||
String? avatar,
|
||||
String? username,
|
||||
}) async {
|
||||
try {
|
||||
if (currentUserId == null) {
|
||||
throw 'User not authenticated';
|
||||
}
|
||||
|
||||
final updatedProfileData = profile.toJson();
|
||||
// Build update data object
|
||||
final Map<String, dynamic> updateData = {};
|
||||
|
||||
if (firstName != null) updateData['first_name'] = firstName;
|
||||
if (lastName != null) updateData['last_name'] = lastName;
|
||||
if (bio != null) updateData['bio'] = bio;
|
||||
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(updatedProfileData)
|
||||
.update(updateData)
|
||||
.eq('user_id', currentUserId!);
|
||||
}
|
||||
|
||||
// Fetch and return updated profile
|
||||
return await getProfileData();
|
||||
} 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) {
|
||||
_logger.e('Exception in updateProfile: $e');
|
||||
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
|
||||
Future<ProfileModel> getProfileByUserId(String userId) async {
|
||||
try {
|
||||
|
@ -109,9 +187,34 @@ class ProfileRepository extends GetxController {
|
|||
|
||||
return ProfileModel.fromJson(profileData);
|
||||
} 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) {
|
||||
_logger.e('Exception in getProfileByUserId: $e');
|
||||
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()}';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()}';
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,7 @@
|
|||
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/utils/exceptions/exceptions.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
|
@ -6,17 +9,23 @@ import 'package:supabase_flutter/supabase_flutter.dart';
|
|||
class RolesRepository extends GetxController {
|
||||
static RolesRepository get instance => Get.find();
|
||||
|
||||
final _supabase = Supabase.instance.client;
|
||||
final _supabase = SupabaseService.instance.client;
|
||||
final _logger = Get.find<Logger>();
|
||||
|
||||
// Get all roles
|
||||
Future<List<RoleModel>> getAllRoles() async {
|
||||
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();
|
||||
} 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) {
|
||||
_logger.e('Exception in getAllRoles: $e');
|
||||
throw 'Failed to fetch roles: ${e.toString()}';
|
||||
}
|
||||
}
|
||||
|
@ -25,12 +34,18 @@ class RolesRepository extends GetxController {
|
|||
Future<RoleModel> getRoleById(String roleId) async {
|
||||
try {
|
||||
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);
|
||||
} 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) {
|
||||
_logger.e('Exception in getRoleById: $e');
|
||||
throw 'Failed to fetch role data: ${e.toString()}';
|
||||
}
|
||||
}
|
||||
|
@ -39,13 +54,82 @@ class RolesRepository extends GetxController {
|
|||
Future<RoleModel> getRoleByName(String roleName) async {
|
||||
try {
|
||||
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);
|
||||
} 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) {
|
||||
_logger.e('Exception in getRoleByName: $e');
|
||||
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()}';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import 'package:flutter/services.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/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/format_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();
|
||||
|
||||
final _supabase = SupabaseService.instance.client;
|
||||
final _logger = Get.find<Logger>();
|
||||
|
||||
// Get current user ID
|
||||
String? get currentUserId => SupabaseService.instance.currentUserId;
|
||||
|
||||
// Check if user is authenticated
|
||||
bool get isAuthenticated => currentUserId != null;
|
||||
|
||||
// Check if user is an officer
|
||||
Future<bool> isCurrentUserOfficer() async {
|
||||
try {
|
||||
if (currentUserId == null) return false;
|
||||
if (!isAuthenticated) return false;
|
||||
|
||||
final user = _supabase.auth.currentUser;
|
||||
if (user == null) return false;
|
||||
|
@ -29,29 +35,30 @@ class UserRepository extends GetxController {
|
|||
return metadata['is_officer'] == true;
|
||||
}
|
||||
|
||||
// If no flag in metadata, check if user exists in officers table
|
||||
final officerData =
|
||||
// If no flag in metadata, check role information
|
||||
final userData =
|
||||
await _supabase
|
||||
.from('officers')
|
||||
.select()
|
||||
.from('users')
|
||||
.select('role:roles(name)')
|
||||
.eq('id', currentUserId!)
|
||||
.single();
|
||||
|
||||
return officerData != null;
|
||||
final roleName =
|
||||
(userData['role'] as Map<String, dynamic>)['name'] as String;
|
||||
return roleName.toLowerCase() == 'officer';
|
||||
} on PostgrestException catch (error) {
|
||||
// PGRST116 means no rows returned - not an officer
|
||||
if (error.code == 'PGRST116') return false;
|
||||
throw TExceptions.fromCode(error.code!);
|
||||
_logger.e('PostgrestException in isCurrentUserOfficer: ${error.message}');
|
||||
return false; // Default to not an officer
|
||||
} catch (e) {
|
||||
// Default to not an officer on error
|
||||
return false;
|
||||
_logger.e('Exception in isCurrentUserOfficer: $e');
|
||||
return false; // Default to not an officer
|
||||
}
|
||||
}
|
||||
|
||||
// Get user data for regular users
|
||||
Future<UserModel> getUserData() async {
|
||||
// Get current user data
|
||||
Future<UserModel> getCurrentUserData() async {
|
||||
try {
|
||||
if (currentUserId == null) {
|
||||
if (!isAuthenticated) {
|
||||
throw 'User not authenticated';
|
||||
}
|
||||
|
||||
|
@ -64,70 +71,155 @@ class UserRepository extends GetxController {
|
|||
|
||||
return UserModel.fromJson(userData);
|
||||
} 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 (_) {
|
||||
throw const TFormatException();
|
||||
} on PlatformException catch (e) {
|
||||
throw TPlatformException(e.code).message;
|
||||
} catch (e) {
|
||||
_logger.e('Exception in getCurrentUserData: $e');
|
||||
throw 'Failed to fetch user data: ${e.toString()}';
|
||||
}
|
||||
}
|
||||
|
||||
// Update user profile data
|
||||
Future<void> updateUserProfile({
|
||||
String? firstName,
|
||||
String? lastName,
|
||||
String? bio,
|
||||
String? address,
|
||||
String? birthDate,
|
||||
String? avatar,
|
||||
String? username,
|
||||
String? phone,
|
||||
}) async {
|
||||
// Update user metadata
|
||||
Future<void> updateUserMetadata(Map<String, dynamic> metadata) async {
|
||||
try {
|
||||
if (currentUserId == null) {
|
||||
if (!isAuthenticated) {
|
||||
throw 'User not authenticated';
|
||||
}
|
||||
|
||||
// First update user profile
|
||||
final Map<String, dynamic> profileData = {};
|
||||
await _supabase.auth.updateUser(UserAttributes(data: metadata));
|
||||
|
||||
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!);
|
||||
} on AuthException catch (e) {
|
||||
_logger.e('AuthException in updateUserMetadata: ${e.message}');
|
||||
throw TExceptions(e.message);
|
||||
} catch (e) {
|
||||
_logger.e('Exception in updateUserMetadata: $e');
|
||||
throw 'Failed to update user metadata: ${e.toString()}';
|
||||
}
|
||||
}
|
||||
|
||||
// Then update user table if needed
|
||||
if (phone != null) {
|
||||
// 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({'phone': phone})
|
||||
.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!);
|
||||
|
||||
// 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) {
|
||||
_logger.e('AuthException in updateUserPhone: ${e.message}');
|
||||
throw TExceptions(e.message);
|
||||
} on FormatException catch (_) {
|
||||
throw const TFormatException();
|
||||
} on PlatformException catch (e) {
|
||||
throw TPlatformException(e.code).message;
|
||||
} on PostgrestException catch (error) {
|
||||
_logger.e('PostgrestException in updateUserPhone: ${error.message}');
|
||||
throw TExceptions.fromCode(error.code ?? 'unknown-error');
|
||||
} catch (e) {
|
||||
throw 'Failed to update profile: ${e.toString()}';
|
||||
_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);
|
||||
} 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) {
|
||||
_logger.e('Exception in getUserById: $e');
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue