fix(stability): remove auth/context force unwraps and add guarded null-safe paths
This commit is contained in:
parent
f41708c662
commit
947992e8b9
5 changed files with 60 additions and 15 deletions
|
|
@ -68,8 +68,12 @@ void showGlobalSnackBar(String message, {bool isError = false}) {
|
|||
|
||||
/// Show a dialog safely through the global navigator key.
|
||||
Future<T?> showGlobalDialog<T>({required WidgetBuilder builder}) {
|
||||
final context = navigatorKey.currentContext;
|
||||
if (context == null) {
|
||||
return Future<T?>.value(null);
|
||||
}
|
||||
return showDialog<T>(
|
||||
context: navigatorKey.currentContext!,
|
||||
context: context,
|
||||
builder: builder,
|
||||
);
|
||||
}
|
||||
|
|
@ -201,8 +205,14 @@ class _AuthGateState extends State<AuthGate> {
|
|||
}
|
||||
|
||||
Future<void> _showResetPasswordDialog() async {
|
||||
final context = navigatorKey.currentContext;
|
||||
if (context == null) {
|
||||
_isInPasswordRecoveryFlow = false;
|
||||
return;
|
||||
}
|
||||
|
||||
await showDialog<void>(
|
||||
context: navigatorKey.currentContext!,
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (_) => const _ResetPasswordDialog(),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -483,6 +483,11 @@ class GarageScreenState extends State<GarageScreen> {
|
|||
.update({'collection_id': targetId})
|
||||
.inFilter('id', _selectedIds.toList());
|
||||
} else {
|
||||
final userId = supabase.auth.currentUser?.id;
|
||||
if (userId == null) {
|
||||
throw Exception('You must be signed in to copy cars.');
|
||||
}
|
||||
|
||||
final sourceCars = _cars
|
||||
.where((car) => _selectedIds.contains(car['id'] as int))
|
||||
.toList(growable: false);
|
||||
|
|
@ -492,7 +497,7 @@ class GarageScreenState extends State<GarageScreen> {
|
|||
final imagePath = car['user_image_url'] as String?;
|
||||
return <String, dynamic>{
|
||||
'hw_id': car['hw_id'] as String,
|
||||
'user_id': supabase.auth.currentUser!.id,
|
||||
'user_id': userId,
|
||||
'collection_id': targetId,
|
||||
if (notes != null && notes.trim().isNotEmpty) 'notes': notes,
|
||||
if (imagePath != null && imagePath.isNotEmpty)
|
||||
|
|
@ -1028,11 +1033,16 @@ class GarageScreenState extends State<GarageScreen> {
|
|||
.update({'collection_id': targetId})
|
||||
.eq('id', car['id']);
|
||||
} else {
|
||||
final userId = supabase.auth.currentUser?.id;
|
||||
if (userId == null) {
|
||||
throw Exception('You must be signed in to copy cars.');
|
||||
}
|
||||
|
||||
final notes = car['notes'] as String?;
|
||||
final imagePath = car['user_image_url'] as String?;
|
||||
await supabase.from('hotwheels').insert({
|
||||
'hw_id': car['hw_id'] as String,
|
||||
'user_id': supabase.auth.currentUser!.id,
|
||||
'user_id': userId,
|
||||
'collection_id': targetId,
|
||||
if (notes != null && notes.trim().isNotEmpty) 'notes': notes,
|
||||
if (imagePath != null && imagePath.isNotEmpty)
|
||||
|
|
|
|||
|
|
@ -419,9 +419,14 @@ class ScanTabState extends State<ScanTab> {
|
|||
String hwId, {
|
||||
String? notes,
|
||||
}) async {
|
||||
final userId = supabase.auth.currentUser?.id;
|
||||
if (userId == null) {
|
||||
throw Exception('You must be signed in to add cars.');
|
||||
}
|
||||
|
||||
await supabase.from('hotwheels').insert({
|
||||
'hw_id': hwId,
|
||||
'user_id': supabase.auth.currentUser!.id,
|
||||
'user_id': userId,
|
||||
'collection_id': collectionId,
|
||||
if (notes != null && notes.trim().isNotEmpty) 'notes': notes.trim(),
|
||||
});
|
||||
|
|
@ -433,6 +438,11 @@ class ScanTabState extends State<ScanTab> {
|
|||
String? series,
|
||||
int? year,
|
||||
}) async {
|
||||
final userId = supabase.auth.currentUser?.id;
|
||||
if (userId == null) {
|
||||
throw Exception('You must be signed in to create catalog entries.');
|
||||
}
|
||||
|
||||
final cleanedSeries = series?.trim();
|
||||
final payload = <String, dynamic>{
|
||||
'hw_id': hwId,
|
||||
|
|
@ -445,12 +455,15 @@ class ScanTabState extends State<ScanTab> {
|
|||
|
||||
await supabase.from('car_votes').insert({
|
||||
'hw_id': hwId,
|
||||
'user_id': supabase.auth.currentUser!.id,
|
||||
'user_id': userId,
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _ensureValidationVote(String hwId) async {
|
||||
final userId = supabase.auth.currentUser!.id;
|
||||
final userId = supabase.auth.currentUser?.id;
|
||||
if (userId == null) {
|
||||
throw Exception('You must be signed in to validate entries.');
|
||||
}
|
||||
final existingVote = await supabase
|
||||
.from('car_votes')
|
||||
.select('id')
|
||||
|
|
|
|||
|
|
@ -53,6 +53,14 @@ class CollectionMember {
|
|||
class CollectionService {
|
||||
CollectionService._();
|
||||
|
||||
static String _requireUserId() {
|
||||
final userId = supabase.auth.currentUser?.id;
|
||||
if (userId == null) {
|
||||
throw Exception('You must be signed in to perform this action.');
|
||||
}
|
||||
return userId;
|
||||
}
|
||||
|
||||
/// Ensures the current user has at least one collection membership.
|
||||
/// Creates a default collection on first login.
|
||||
static Future<String?> ensureDefaultCollection() async {
|
||||
|
|
@ -80,7 +88,7 @@ class CollectionService {
|
|||
/// Fetch all collections the current user is a member of,
|
||||
/// including item count and member count.
|
||||
static Future<List<Collection>> getMyCollections() async {
|
||||
final userId = supabase.auth.currentUser!.id;
|
||||
final userId = _requireUserId();
|
||||
|
||||
// Get memberships with collection data.
|
||||
final memberships = await supabase
|
||||
|
|
@ -249,7 +257,7 @@ class CollectionService {
|
|||
required String name,
|
||||
String? description,
|
||||
}) async {
|
||||
final userId = supabase.auth.currentUser!.id;
|
||||
final userId = _requireUserId();
|
||||
|
||||
final row = await supabase
|
||||
.from('collections')
|
||||
|
|
@ -287,7 +295,7 @@ class CollectionService {
|
|||
required String name,
|
||||
String? description,
|
||||
}) async {
|
||||
final userId = supabase.auth.currentUser!.id;
|
||||
final userId = _requireUserId();
|
||||
|
||||
final collection = await supabase
|
||||
.from('collections')
|
||||
|
|
@ -311,7 +319,7 @@ class CollectionService {
|
|||
|
||||
/// Delete a collection. Owner only. Cascade deletes members & items.
|
||||
static Future<void> delete(String collectionId) async {
|
||||
final userId = supabase.auth.currentUser!.id;
|
||||
final userId = _requireUserId();
|
||||
|
||||
final collection = await supabase
|
||||
.from('collections')
|
||||
|
|
@ -362,7 +370,7 @@ class CollectionService {
|
|||
throw Exception('Unsupported role "$role".');
|
||||
}
|
||||
|
||||
final currentUserId = supabase.auth.currentUser!.id;
|
||||
final currentUserId = _requireUserId();
|
||||
|
||||
final collection = await supabase
|
||||
.from('collections')
|
||||
|
|
@ -428,7 +436,7 @@ class CollectionService {
|
|||
required String collectionId,
|
||||
required String memberUserId,
|
||||
}) async {
|
||||
final currentUserId = supabase.auth.currentUser!.id;
|
||||
final currentUserId = _requireUserId();
|
||||
|
||||
final collection = await supabase
|
||||
.from('collections')
|
||||
|
|
@ -501,7 +509,7 @@ class CollectionService {
|
|||
|
||||
/// Leave a collection (for non-owners).
|
||||
static Future<void> leave(String collectionId) async {
|
||||
final userId = supabase.auth.currentUser!.id;
|
||||
final userId = _requireUserId();
|
||||
|
||||
final membership = await supabase
|
||||
.from('collection_members')
|
||||
|
|
|
|||
|
|
@ -31,7 +31,11 @@ class StorageService {
|
|||
await deleteCarImage(oldPath);
|
||||
}
|
||||
|
||||
final userId = supabase.auth.currentUser!.id;
|
||||
final user = supabase.auth.currentUser;
|
||||
if (user == null) {
|
||||
throw Exception('You must be signed in to upload images.');
|
||||
}
|
||||
final userId = user.id;
|
||||
final path = '$userId/$entryId.jpg';
|
||||
final compressed = await _compressImage(file);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue