feat: Add Nanaxe server with Docker, GitHub Actions CI/CD, and auto-deployment
- 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
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
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),
|
||||
);
|
||||
Reference in New Issue
Block a user