fix(review): address auth subscription, safe image replace, logging and widget tests

This commit is contained in:
Lukas Müllner 2026-03-06 07:43:40 +01:00
parent e8501f7d72
commit 38de6d8b86
4 changed files with 89 additions and 17 deletions

View file

@ -209,6 +209,7 @@ class _AuthGateState extends State<AuthGate> {
bool _isInPasswordRecoveryFlow = false; bool _isInPasswordRecoveryFlow = false;
Session? _session; Session? _session;
String? _lastEnsuredUserId; String? _lastEnsuredUserId;
StreamSubscription<AuthState>? _authStateSubscription;
@override @override
void initState() { void initState() {
@ -217,7 +218,7 @@ class _AuthGateState extends State<AuthGate> {
_session = supabase.auth.currentSession; _session = supabase.auth.currentSession;
_ensureDefaultCollectionIfNeeded(); _ensureDefaultCollectionIfNeeded();
supabase.auth.onAuthStateChange.listen( _authStateSubscription = supabase.auth.onAuthStateChange.listen(
(AuthState authState) { (AuthState authState) {
if (!mounted) return; if (!mounted) return;
@ -247,6 +248,12 @@ class _AuthGateState extends State<AuthGate> {
setState(() => _isLoading = false); setState(() => _isLoading = false);
} }
@override
void dispose() {
_authStateSubscription?.cancel();
super.dispose();
}
Future<void> _ensureDefaultCollectionIfNeeded() async { Future<void> _ensureDefaultCollectionIfNeeded() async {
final userId = _session?.user.id; final userId = _session?.user.id;
if (userId == null || userId == _lastEnsuredUserId) return; if (userId == null || userId == _lastEnsuredUserId) return;

View file

@ -1,4 +1,5 @@
//import 'package:supabase_flutter/supabase_flutter.dart'; //import 'package:supabase_flutter/supabase_flutter.dart';
import 'package:flutter/foundation.dart';
import '../main.dart'; import '../main.dart';
/// Data model for a collection. /// Data model for a collection.
@ -120,7 +121,8 @@ class CollectionService {
'p_collection_id': collectionId, 'p_collection_id': collectionId,
}); });
memberCounts[collectionId] = (rows as List).length; memberCounts[collectionId] = (rows as List).length;
} catch (_) { } catch (e) {
debugPrint('CollectionService.getMyCollections member RPC error: $e');
} }
})); }));
@ -182,7 +184,9 @@ class CollectionService {
if (collectionId == null) continue; if (collectionId == null) continue;
itemCounts[collectionId] = (row['total_count'] as num?)?.toInt() ?? 0; itemCounts[collectionId] = (row['total_count'] as num?)?.toInt() ?? 0;
} }
} catch (_) {} } catch (e) {
debugPrint('CollectionService.getCollectionItemCounts RPC error: $e');
}
final unresolvedIds = collectionIds final unresolvedIds = collectionIds
.where((collectionId) => !itemCounts.containsKey(collectionId)) .where((collectionId) => !itemCounts.containsKey(collectionId))
@ -211,7 +215,9 @@ class CollectionService {
recent: (first['recent_count'] as num?)?.toInt() ?? 0, recent: (first['recent_count'] as num?)?.toInt() ?? 0,
); );
} }
} catch (_) {} } catch (e) {
debugPrint('CollectionService.getCollectionStats RPC error: $e');
}
final weekAgoIso = DateTime.now() final weekAgoIso = DateTime.now()
.subtract(const Duration(days: 7)) .subtract(const Duration(days: 7))
@ -269,11 +275,21 @@ class CollectionService {
.single(); .single();
// Add owner as a member. // Add owner as a member.
try {
await supabase.from('collection_members').insert({ await supabase.from('collection_members').insert({
'collection_id': row['id'], 'collection_id': row['id'],
'user_id': userId, 'user_id': userId,
'role': 'owner', 'role': 'owner',
}); });
} catch (e) {
debugPrint('CollectionService.create member insert failed: $e');
try {
await supabase.from('collections').delete().eq('id', row['id']);
} catch (cleanupError) {
debugPrint('CollectionService.create rollback failed: $cleanupError');
}
rethrow;
}
return Collection( return Collection(
id: row['id'] as String, id: row['id'] as String,

View file

@ -27,10 +27,6 @@ class StorageService {
required int entryId, required int entryId,
String? oldPath, String? oldPath,
}) async { }) async {
if (oldPath != null && oldPath.isNotEmpty) {
await deleteCarImage(oldPath);
}
final user = supabase.auth.currentUser; final user = supabase.auth.currentUser;
if (user == null) { if (user == null) {
throw Exception('You must be signed in to upload images.'); throw Exception('You must be signed in to upload images.');
@ -50,6 +46,14 @@ class StorageService {
_signedUrlCache.remove(path); _signedUrlCache.remove(path);
if (oldPath != null && oldPath.isNotEmpty && oldPath != path) {
try {
await deleteCarImage(oldPath);
} catch (e) {
debugPrint('StorageService.uploadCarImage cleanup error: $e');
}
}
return path; return path;
} }

View file

@ -1,9 +1,54 @@
// Basic smoke test placeholder will be updated when UI is finalized. import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:hwhub/widgets/car_card.dart';
void main() { void main() {
test('placeholder test', () { testWidgets('CarCard renders model id and metadata', (tester) async {
expect(1 + 1, 2); await tester.pumpWidget(
const MaterialApp(
home: Scaffold(
body: Center(
child: SizedBox(
width: 220,
height: 360,
child: CarCard(
hwId: 'ABC12',
name: 'Die-Cast Model',
series: 'Collector Series',
year: 2024,
isVerified: true,
),
),
),
),
),
);
expect(find.text('ABC12'), findsOneWidget);
expect(find.text('Die-Cast Model'), findsOneWidget);
expect(find.text('Collector Series'), findsOneWidget);
expect(find.text('2024'), findsOneWidget);
expect(find.text('Verified'), findsOneWidget);
});
testWidgets('CarCard shows selected state label', (tester) async {
await tester.pumpWidget(
const MaterialApp(
home: Scaffold(
body: Center(
child: SizedBox(
width: 220,
height: 360,
child: CarCard(
hwId: 'XYZ99',
isSelected: true,
),
),
),
),
),
);
expect(find.text('Selected'), findsOneWidget);
}); });
} }