Files
gemeindetrommler/lib/services/firestore_service.dart
T
Nizz05andClaude Opus 5 df34d66d38
Build Android APK / Build Release APKs (push) Failing after 1m7s
build: upgrade to Flutter 3.47.4 (AGP 9.1 / Gradle 9.3, iOS 15 + UIScene), refresh deps and CI
Flutter 3.24.5 -> 3.47.4 with the matching toolchain changes:

- pubspec: Dart ^3.11, Firebase core 4 / auth 6 / firestore 6 / storage 13,
  drop unused image_cropper, logging, change_app_package_name,
  pdf_widget_wrapper; pin path_provider_android <2.3 (needs NDK/CMake, not
  feasible on the ARM64 Gitea runner)
- Dart: CardTheme -> CardThemeData, replace deprecated withOpacity,
  Matrix4.scale, DropdownButtonFormField.value
- iOS: deployment target 15.0, UIScene lifecycle (AppDelegate/Info.plist),
  untrack stale Podfile.lock, add LaunchImage asset set
- Android: Gradle 9.3.1, AGP 9.1.0, Kotlin 2.4.0, google-services 4.5.0,
  layout.buildDirectory instead of removed Project.buildDir, remove manifest
  package attribute and duplicate MainActivity, kotlin.incremental=false
- CI: pin FLUTTER_VERSION in both workflows; iOS on macos-26 with SwiftPM
  disabled (CocoaPods only); Gitea job keeps cirruslabs 3.44.0 as base image
  (images discontinued) and switches the bundled Flutter SDK by git tag

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 22:45:30 +02:00

201 lines
6.0 KiB
Dart

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/foundation.dart';
import '../models/post_item.dart';
class FirestoreService {
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
FirestoreService() {
_initSettings();
}
void _initSettings() {
try {
_firestore.settings = const Settings(
persistenceEnabled: true,
cacheSizeBytes: Settings.CACHE_SIZE_UNLIMITED,
);
} catch (e) {
if (kDebugMode) {
debugPrint('Firestore settings initialization error: $e');
}
}
}
/// Stream of posts ordered by creation date descending
Stream<List<PostItem>> getPostsStream(String collectionName) {
return _firestore
.collection(collectionName)
.orderBy('createdDate', descending: true)
.snapshots(includeMetadataChanges: true)
.map((snapshot) =>
snapshot.docs.map((doc) => PostItem.fromFirestore(doc)).toList());
}
/// Add a new post / issue / idea
Future<DocumentReference> addPost(
String collectionName, PostItem item) async {
return await _firestore.collection(collectionName).add(item.toFirestore());
}
/// Delete a post by ID
Future<void> deletePost(String collectionName, String documentId) async {
await _firestore.collection(collectionName).doc(documentId).delete();
}
/// Send a contact form submission to Firestore
Future<void> submitContactMessage({
required String firstName,
required String lastName,
required String email,
String? phone,
required String message,
String? authorUid,
}) async {
await _firestore.collection('ContactMessages').add({
'firstName': firstName,
'lastName': lastName,
'email': email,
'phone': phone ?? '',
'message': message,
'author_uid': authorUid ?? '',
'createdDate': Timestamp.now(),
'status': 'Neu',
});
}
/// Stream of all contact messages (for admins)
Stream<QuerySnapshot<Map<String, dynamic>>> getContactMessagesStream() {
return _firestore
.collection('ContactMessages')
.orderBy('createdDate', descending: true)
.snapshots();
}
/// Update the status of a contact message
Future<void> updateContactMessageStatus(String docId, String status) async {
await _firestore.collection('ContactMessages').doc(docId).update({
'status': status,
'statusUpdatedAt': FieldValue.serverTimestamp(),
});
}
/// Delete a contact message
Future<void> deleteContactMessage(String docId) async {
await _firestore.collection('ContactMessages').doc(docId).delete();
}
/// Stream of all registered users
Stream<QuerySnapshot<Map<String, dynamic>>> getUsersStream() {
return _firestore.collection('Users').snapshots();
}
/// Stream of all admin documents
Stream<QuerySnapshot<Map<String, dynamic>>> getAdminsStream() {
return _firestore.collection('Admins').snapshots();
}
/// Upsert a registered user profile into Firestore
Future<void> syncUserProfile({
required String uid,
required String email,
String? displayName,
String? firstName,
String? lastName,
bool? emailVerified,
bool? isAdmin,
}) async {
try {
final docRef = _firestore.collection('Users').doc(uid);
final doc = await docRef.get();
final data = <String, dynamic>{
'uid': uid,
'email': email.trim().toLowerCase(),
'lastLogin': FieldValue.serverTimestamp(),
if (displayName != null && displayName.isNotEmpty)
'displayName': displayName,
if (firstName != null && firstName.isNotEmpty)
'firstName': firstName,
if (lastName != null && lastName.isNotEmpty)
'lastName': lastName,
'emailVerified': ?emailVerified,
'isAdmin': ?isAdmin,
};
if (!doc.exists) {
data['createdAt'] = FieldValue.serverTimestamp();
}
await docRef.set(data, SetOptions(merge: true));
} catch (e) {
if (kDebugMode) {
debugPrint('Error syncing user profile to Firestore: $e');
}
}
}
/// Promote or demote a user to Admin in Firestore
Future<void> setAdminRole({
required String uid,
required String email,
required bool makeAdmin,
String? grantedBy,
}) async {
final cleanEmail = email.trim().toLowerCase();
final adminRef = _firestore.collection('Admins').doc(uid);
final userRef = _firestore.collection('Users').doc(uid);
if (makeAdmin) {
// 1. Add UID to Admins collection
await adminRef.set({
'uid': uid,
'email': cleanEmail,
'grantedAt': FieldValue.serverTimestamp(),
'grantedBy': grantedBy ?? '',
}, SetOptions(merge: true));
// 2. Also ensure email doc exists if email is known
if (cleanEmail.isNotEmpty) {
await _firestore.collection('Admins').doc(cleanEmail).set({
'uid': uid,
'email': cleanEmail,
'grantedAt': FieldValue.serverTimestamp(),
'grantedBy': grantedBy ?? '',
}, SetOptions(merge: true));
}
// 3. Mark user document as admin
await userRef.set({'isAdmin': true}, SetOptions(merge: true));
} else {
// 1. Remove UID from Admins collection
await adminRef.delete();
// 2. Remove email from Admins collection if present
if (cleanEmail.isNotEmpty) {
try {
await _firestore.collection('Admins').doc(cleanEmail).delete();
} catch (_) {}
}
// 3. Update user document
await userRef.set({'isAdmin': false}, SetOptions(merge: true));
}
}
/// Add admin directly by UID or email (e.g. manual entry)
Future<void> addAdminDirectly({
required String identifier,
required String email,
String? grantedBy,
}) async {
final cleanId = identifier.trim();
final cleanEmail = email.trim().toLowerCase();
await _firestore.collection('Admins').doc(cleanId).set({
'uid': cleanId,
'email': cleanEmail,
'grantedAt': FieldValue.serverTimestamp(),
'grantedBy': grantedBy ?? '',
}, SetOptions(merge: true));
}
}