Skip to content

Commit ba16281

Browse files
committed
Update CI workflow and assorted app fixes
Revamp CI: simplify permissions, pin actions, update Java/Flutter versions, add formatting check, use Codecov token, rename jobs, add conditional Android/iOS builds, upload artifacts with new names, and add CodeQL security scan. App fixes/refactors: initialize OfflineCache in main; switch connectivity provider to a broadcast stream and update isOnline/isOffline providers; add missing toJson() implementations for PetModel and PostModel; small OFFLINE_SUPPORT fix (add breaks in switch); remove memorial gallery UI; simplify medication logic to count unique upcoming days and adjust overdue calculation; clarify auth_repository cleanup with comment/TODO about using an Edge Function to delete users; sanitize supabase .env example keys to placeholders.
1 parent 4786a8f commit ba16281

10 files changed

Lines changed: 133 additions & 197 deletions

File tree

.github/workflows/test-and-build.yml

Lines changed: 86 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -2,182 +2,149 @@ name: Test & Build Validation
22

33
on:
44
push:
5-
branches:
6-
- main
7-
- develop
8-
- 'feature/**'
5+
branches: [ main, develop ]
96
pull_request:
10-
branches:
11-
- main
12-
- develop
7+
branches: [ main, develop ]
138

14-
permissions:
15-
contents: read
16-
actions: read
9+
# Declare default permissions as read-only.
10+
permissions: read-all
1711

1812
jobs:
19-
# Job 1: Analyze code and run tests
20-
test:
13+
test_and_analyze:
2114
name: Test & Analyze
2215
runs-on: ubuntu-latest
23-
16+
permissions:
17+
contents: read
18+
checks: write
19+
pull-requests: write
20+
2421
steps:
25-
- uses: actions/checkout@v4
22+
- name: Checkout Code
23+
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
24+
25+
- name: Setup Java
26+
uses: actions/setup-java@cd89f11832ad0855263829ad3623d1d31a929307 # v3.13.0
2627
with:
27-
fetch-depth: 0
28+
distribution: 'zulu'
29+
java-version: '17'
30+
cache: 'gradle'
2831

2932
- name: Setup Flutter
30-
uses: subosito/flutter-action@v2
33+
uses: subosito/flutter-action@44ae346459a5c099ba318147f2f297c0d74a0db2 # v2.12.0
3134
with:
32-
flutter-version: '3.0.0'
35+
flutter-version: '3.24.3'
3336
channel: 'stable'
34-
35-
- name: Cache Flutter dependencies
36-
uses: actions/cache@v3
37-
with:
38-
path: |
39-
${{ env.FLUTTER_HOME }}/.pub-cache
40-
**/.dart_tool/package_config.json
41-
key: flutter-${{ runner.os }}-${{ hashFiles('**/pubspec.lock') }}
42-
restore-keys: |
43-
flutter-${{ runner.os }}-
37+
cache: true
4438

4539
- name: Get dependencies
4640
run: flutter pub get
4741

42+
- name: Check formatting
43+
run: dart format --set-exit-if-changed .
44+
4845
- name: Analyze code
4946
run: flutter analyze
50-
continue-on-error: false
5147

5248
- name: Run unit tests
5349
run: flutter test --coverage
54-
continue-on-error: false
5550

5651
- name: Upload coverage to Codecov
57-
uses: codecov/codecov-action@v3
52+
uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d # v3.1.4
5853
with:
59-
files: ./coverage/lcov.info
60-
flags: unittests
61-
name: codecov-umbrella
54+
token: ${{ secrets.CODECOV_TOKEN }}
55+
file: ./coverage/lcov.info
6256
fail_ci_if_error: false
63-
verbose: true
6457

6558
- name: Archive coverage reports
66-
if: always()
67-
uses: actions/upload-artifact@v4
59+
uses: actions/upload-artifact@c7d193f32ed247398f1ef51e04acc37d996a730c # v4.1.0
6860
with:
69-
name: coverage-report
61+
name: code-coverage
7062
path: coverage/
7163

72-
# Job 2: Build APK for Android
73-
build-android:
64+
build_android:
7465
name: Build Android APK
66+
needs: test_and_analyze
7567
runs-on: ubuntu-latest
76-
needs: test
77-
68+
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop')
69+
permissions:
70+
contents: read
71+
7872
steps:
79-
- uses: actions/checkout@v4
73+
- name: Checkout Code
74+
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
8075

81-
- name: Setup Flutter
82-
uses: subosito/flutter-action@v2
76+
- name: Setup Java
77+
uses: actions/setup-java@cd89f11832ad0855263829ad3623d1d31a929307 # v3.13.0
8378
with:
84-
flutter-version: '3.0.0'
85-
channel: 'stable'
79+
distribution: 'zulu'
80+
java-version: '17'
81+
cache: 'gradle'
8682

87-
- name: Setup Java
88-
uses: actions/setup-java@v3
83+
- name: Setup Flutter
84+
uses: subosito/flutter-action@44ae346459a5c099ba318147f2f297c0d74a0db2 # v2.12.0
8985
with:
90-
distribution: 'temurin'
91-
java-version: '11'
92-
cache: gradle
86+
flutter-version: '3.24.3'
87+
channel: 'stable'
88+
cache: true
9389

9490
- name: Get dependencies
9591
run: flutter pub get
9692

97-
- name: Build debug APK
98-
run: flutter build apk --debug
99-
continue-on-error: false
93+
- name: Build APK
94+
run: flutter build apk --debug --dart-define=SUPABASE_URL=${{ secrets.SUPABASE_URL }} --dart-define=SUPABASE_ANON_KEY=${{ secrets.SUPABASE_ANON_KEY }}
10095

101-
- name: Archive APK
102-
uses: actions/upload-artifact@v4
96+
- name: Upload APK
97+
uses: actions/upload-artifact@c7d193f32ed247398f1ef51e04acc37d996a730c # v4.1.0
10398
with:
104-
name: android-apk
105-
path: build/app/outputs/apk/debug/app-debug.apk
99+
name: release-apk
100+
path: build/app/outputs/flutter-apk/app-debug.apk
106101

107-
# Job 3: Build for iOS
108-
build-ios:
102+
build_ios:
109103
name: Build iOS App
104+
needs: test_and_analyze
110105
runs-on: macos-latest
111-
needs: test
112-
106+
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
107+
permissions:
108+
contents: read
109+
113110
steps:
114-
- uses: actions/checkout@v4
111+
- name: Checkout Code
112+
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
115113

116114
- name: Setup Flutter
117-
uses: subosito/flutter-action@v2
115+
uses: subosito/flutter-action@44ae346459a5c099ba318147f2f297c0d74a0db2 # v2.12.0
118116
with:
119-
flutter-version: '3.0.0'
117+
flutter-version: '3.24.3'
120118
channel: 'stable'
119+
cache: true
121120

122121
- name: Get dependencies
123122
run: flutter pub get
124123

125-
- name: Build iOS app (simulator)
126-
run: flutter build ios --debug --simulator
127-
continue-on-error: true # May fail in CI if iOS SDK is unavailable
124+
- name: Build iOS
125+
run: flutter build ios --no-codesign --debug --dart-define=SUPABASE_URL=${{ secrets.SUPABASE_URL }} --dart-define=SUPABASE_ANON_KEY=${{ secrets.SUPABASE_ANON_KEY }}
128126

129-
- name: Build iOS app
130-
run: flutter build ios --debug
131-
continue-on-error: true # May fail in CI if iOS SDK is unavailable
127+
- name: Upload Runner
128+
uses: actions/upload-artifact@c7d193f32ed247398f1ef51e04acc37d996a730c # v4.1.0
129+
with:
130+
name: ios-build
131+
path: build/ios/iphoneos/Runner.app
132132

133-
# Job 4: Schema migration validation
134-
migrations:
135-
name: Validate Schema Migrations
133+
security_scan:
134+
name: Security Scan
136135
runs-on: ubuntu-latest
137-
136+
permissions:
137+
contents: read
138+
security-events: write
139+
138140
steps:
139-
- uses: actions/checkout@v4
140-
141-
- name: List migrations
142-
run: |
143-
echo "=== Database Migrations ==="
144-
ls -la supabase/migrations/
145-
echo "Total migrations: $(ls -1 supabase/migrations/*.sql 2>/dev/null | wc -l)"
146-
147-
- name: Validate migration naming
148-
run: |
149-
cd supabase/migrations
150-
for file in *.sql; do
151-
if [[ ! $file =~ ^[0-9]{14}_ ]]; then
152-
echo "❌ Invalid migration name: $file (must start with YYYYMMDDHHMMSS_)"
153-
exit 1
154-
fi
155-
done
156-
echo "✓ All migrations have valid naming"
157-
158-
- name: Check for duplicate timestamps
159-
run: |
160-
cd supabase/migrations
161-
timestamps=$(ls *.sql | cut -d_ -f1 | sort | uniq -d)
162-
if [ ! -z "$timestamps" ]; then
163-
echo "❌ Duplicate migration timestamps found: $timestamps"
164-
exit 1
165-
fi
166-
echo "✓ No duplicate migration timestamps"
167-
168-
# Job 5: Summary Report
169-
report:
170-
name: Test Results Summary
171-
runs-on: ubuntu-latest
172-
needs: [test, build-android, build-ios, migrations]
173-
if: always()
141+
- name: Checkout Code
142+
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
174143

175-
steps:
176-
- name: Check test results
177-
run: |
178-
echo "✓ Code analysis: passed"
179-
echo "✓ Unit tests: passed"
180-
echo "✓ Android build: passed"
181-
echo "✓ Schema migrations: validated"
182-
echo ""
183-
echo "Build status: SUCCESS"
144+
- name: Initialize CodeQL
145+
uses: github/codeql-action/init@ccf91b7d19760778f69e6b54a32057635678b871 # v3.25.3
146+
with:
147+
languages: javascript, python
148+
149+
- name: Perform CodeQL Analysis
150+
uses: github/codeql-action/analyze@ccf91b7d19760778f69e6b54a32057635678b871 # v3.25.3

OFFLINE_SUPPORT.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,10 +215,13 @@ class FeedNotifier extends Notifier<FeedState> {
215215
switch (op['operation']) {
216216
case 'create':
217217
await _syncCreate(op);
218+
break;
218219
case 'update':
219220
await _syncUpdate(op);
221+
break;
220222
case 'delete':
221223
await _syncDelete(op);
224+
break;
222225
}
223226
await OfflineCache().removeSyncOperation(i);
224227
} catch (e) {

lib/controllers/connectivity_controller.dart

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,15 @@ final connectivityServiceProvider = Provider<ConnectivityService>((ref) {
1010
return ConnectivityService();
1111
});
1212

13-
/// Stream of connectivity status changes
14-
final connectivityStreamProvider = StreamProvider<ConnectivityStatus>((ref) {
15-
final service = ref.watch(connectivityServiceProvider);
16-
return service.statusStream;
17-
});
18-
19-
/// Current connectivity status (watch this to rebuild on connectivity changes)
13+
/// Stream of connectivity status changes (Broadcast stream for multiple listeners)
2014
final connectivityStatusProvider = StreamProvider<ConnectivityStatus>((ref) {
2115
final service = ref.watch(connectivityServiceProvider);
2216
return service.statusStream.asBroadcastStream();
2317
});
2418

2519
/// Convenience: whether device is currently online
2620
final isOnlineProvider = Provider<bool>((ref) {
27-
final stream = ref.watch(connectivityStreamProvider);
21+
final stream = ref.watch(connectivityStatusProvider);
2822
return stream.whenData((status) => status == ConnectivityStatus.online)
2923
.maybeWhen(
3024
data: (isOnline) => isOnline,
@@ -34,7 +28,7 @@ final isOnlineProvider = Provider<bool>((ref) {
3428

3529
/// Convenience: whether device is currently offline
3630
final isOfflineProvider = Provider<bool>((ref) {
37-
final stream = ref.watch(connectivityStreamProvider);
31+
final stream = ref.watch(connectivityStatusProvider);
3832
return stream.whenData((status) => status == ConnectivityStatus.offline)
3933
.maybeWhen(
4034
data: (isOffline) => isOffline,

lib/main.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import 'services/push_notification_service.dart';
1515
import 'utils/routes.dart';
1616
import 'utils/supabase_config.dart';
1717
import 'utils/theme_bootstrap.dart';
18+
import 'utils/offline_cache.dart';
1819
import 'theme/app_theme.dart';
1920

2021
/// Set via `flutter test integration_test/... --dart-define=INTEGRATION_TEST=true`
@@ -75,6 +76,11 @@ Future<void> main() async {
7576
}
7677

7778
final prefs = await SharedPreferences.getInstance();
79+
80+
// Initialize OfflineCache for persistence
81+
final offlineCache = OfflineCache();
82+
await offlineCache.initialize();
83+
7884
pendingBootstrapThemeMode =
7985
prefs.getString('theme_mode') == 'dark' ? ThemeMode.dark : ThemeMode.light;
8086

lib/models/pet_model.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ class PetModel {
111111
}
112112

113113
Map<String, dynamic> toJson() => {
114+
'id': id,
114115
'user_id': userId,
115116
'name': name,
116117
'breed': breed,

lib/models/post_model.dart

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,4 +120,17 @@ class PostModel {
120120
createdAt: DateTime.parse(json['created_at'] as String),
121121
);
122122
}
123+
124+
Map<String, dynamic> toJson() => {
125+
'id': id,
126+
'pets': pet.toJson(),
127+
'media_url': mediaUrl,
128+
'caption': caption,
129+
'location': location,
130+
'tagged_pet_ids': taggedPetIds,
131+
'tagged_pet_names': taggedPetNames,
132+
'post_likes': likedByPetIds.map((id) => {'pet_id': id}).toList(),
133+
'comments': comments.map((c) => c.toJson()).toList(),
134+
'created_at': createdAt.toIso8601String(),
135+
};
123136
}

lib/repositories/auth_repository.dart

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,11 @@ class AuthRepository {
4141
'name': name,
4242
});
4343
} catch (e) {
44-
// Clean up by signing out the user since profile creation failed
44+
// Clean up by signing out the user since profile creation failed.
45+
// NOTE: Ideally, we would delete the auth user here to allow re-signup with the same email.
46+
// However, deleting a user requires admin privileges (Service Role key), which should
47+
// NOT be embedded in the client app.
48+
// TODO: Implement a Supabase Edge Function 'delete-self' or similar to handle this rollback.
4549
await supabase.auth.signOut();
4650
throw Exception(
4751
'Failed to create your profile. Please try signing up again. If the problem persists, contact support.');

0 commit comments

Comments
 (0)