f1e0f6b5ef
- Add complete server infrastructure with Gitea, API, PostgreSQL - Implement Obsidian-compatible markdown mindmap converter - Add Flutter service for server synchronization - Configure GitHub Actions for automated build and deploy - Add production docker-compose with health checks - Include VPS setup and deployment scripts - Add comprehensive deployment documentation
179 lines
5.2 KiB
Dart
179 lines
5.2 KiB
Dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:cloud_firestore/cloud_firestore.dart';
|
|
import 'package:firebase_auth/firebase_auth.dart';
|
|
import 'package:uuid/uuid.dart';
|
|
import '../models/mindmap_model.dart';
|
|
import '../core/admin_access_service.dart';
|
|
import '../core/deepseek_service.dart';
|
|
|
|
// Provider для получения текущего email пользователя
|
|
final currentUserEmailProvider = Provider<String?>((ref) {
|
|
return FirebaseAuth.instance.currentUser?.email;
|
|
});
|
|
|
|
// Provider проверки является ли пользователь админом
|
|
final isAdminProvider = Provider<bool>((ref) {
|
|
final email = ref.watch(currentUserEmailProvider);
|
|
if (email == null) return false;
|
|
return AdminAccessService.adminEmails.contains(email.toLowerCase());
|
|
});
|
|
|
|
// Provider списка mind maps
|
|
final mindMapsProvider = StreamProvider<List<MindMapModel>>((ref) {
|
|
return FirebaseFirestore.instance
|
|
.collection('mind_maps')
|
|
.orderBy('createdAt', descending: true)
|
|
.snapshots()
|
|
.map((snapshot) {
|
|
return snapshot.docs.map((doc) {
|
|
return MindMapModel.fromFirestore(doc);
|
|
}).toList();
|
|
});
|
|
});
|
|
|
|
// Notifier для управления mind maps
|
|
class MindMapsNotifier extends StateNotifier<AsyncValue<List<MindMapModel>>> {
|
|
final Ref ref;
|
|
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
|
|
final FirebaseAuth _auth = FirebaseAuth.instance;
|
|
|
|
MindMapsNotifier(this.ref) : super(const AsyncValue.loading()) {
|
|
_listenToMindMaps();
|
|
}
|
|
|
|
void _listenToMindMaps() {
|
|
_firestore
|
|
.collection('mind_maps')
|
|
.orderBy('createdAt', descending: true)
|
|
.snapshots()
|
|
.listen(
|
|
(snapshot) {
|
|
final mindMaps = snapshot.docs.map((doc) {
|
|
return MindMapModel.fromFirestore(doc);
|
|
}).toList();
|
|
state = AsyncValue.data(mindMaps);
|
|
},
|
|
onError: (error, st) {
|
|
state = AsyncValue.error(error, st);
|
|
},
|
|
);
|
|
}
|
|
|
|
// Создание новой mind map
|
|
Future<String> createMindMap({
|
|
required String title,
|
|
String description = '',
|
|
required List<Map<String, dynamic>> nodes,
|
|
bool isMain = false,
|
|
}) async {
|
|
try {
|
|
final userEmail = _auth.currentUser?.email ?? 'unknown';
|
|
final now = DateTime.now();
|
|
final id = const Uuid().v4();
|
|
|
|
final mindMap = MindMapModel(
|
|
id: id,
|
|
title: title,
|
|
description: description,
|
|
ownerEmail: userEmail,
|
|
isMain: isMain,
|
|
nodes: nodes,
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
);
|
|
|
|
await _firestore.collection('mind_maps').doc(id).set(mindMap.toFirestore());
|
|
|
|
return id;
|
|
} catch (e, st) {
|
|
state = AsyncValue.error(e, st);
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
// Удаление mind map (админы могут удалять любые, обычные - только свои)
|
|
Future<void> deleteMindMap(String id, String ownerEmail) async {
|
|
try {
|
|
final isAdmin = ref.read(isAdminProvider);
|
|
final currentUserEmail = _auth.currentUser?.email ?? '';
|
|
|
|
// Проверка прав
|
|
if (!isAdmin && ownerEmail != currentUserEmail) {
|
|
throw Exception('Нет прав для удаления этой mind map');
|
|
}
|
|
|
|
await _firestore.collection('mind_maps').doc(id).delete();
|
|
} catch (e, st) {
|
|
state = AsyncValue.error(e, st);
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
// Обновление mind map
|
|
Future<void> updateMindMap(String id, MindMapModel mindMap) async {
|
|
try {
|
|
final isAdmin = ref.read(isAdminProvider);
|
|
final currentUserEmail = _auth.currentUser?.email ?? '';
|
|
|
|
// Проверка прав
|
|
if (!isAdmin && mindMap.ownerEmail != currentUserEmail) {
|
|
throw Exception('Нет прав для редактирования этой mind map');
|
|
}
|
|
|
|
await _firestore
|
|
.collection('mind_maps')
|
|
.doc(id)
|
|
.update(mindMap.copyWith(updatedAt: DateTime.now()).toFirestore());
|
|
} catch (e, st) {
|
|
state = AsyncValue.error(e, st);
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
// AI генерация mind map
|
|
Future<String> generateWithAI({
|
|
required String title,
|
|
required String description,
|
|
}) async {
|
|
try {
|
|
final nodesData = await deepSeekService.generateMindMap(
|
|
projectName: title,
|
|
description: description,
|
|
);
|
|
|
|
final nodes = <Map<String, dynamic>>[];
|
|
for (var i = 0; i < nodesData.length; i++) {
|
|
final nodeData = nodesData[i];
|
|
nodes.add({
|
|
'id': 'node_$i',
|
|
'level': nodeData['level'] ?? 0,
|
|
'parentId': nodeData['parentId'],
|
|
'title': nodeData['title'] ?? '',
|
|
'color': _getColorForLevel(nodeData['level'] ?? 0),
|
|
});
|
|
}
|
|
|
|
return await createMindMap(
|
|
title: title,
|
|
description: description,
|
|
nodes: nodes,
|
|
);
|
|
} catch (e, st) {
|
|
state = AsyncValue.error(e, st);
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
String _getColorForLevel(int level) {
|
|
final colors = [
|
|
'#2196F3', '#4CAF50', '#FF9800', '#E91E63', '#9C27B0', '#00BCD4'
|
|
];
|
|
return colors[level % colors.length];
|
|
}
|
|
}
|
|
|
|
final mindMapsNotifierProvider =
|
|
StateNotifierProvider<MindMapsNotifier, AsyncValue<List<MindMapModel>>>(
|
|
(ref) => MindMapsNotifier(ref),
|
|
);
|