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:
2026-04-19 22:18:25 +05:00
parent 52aea9ac87
commit f1e0f6b5ef
56 changed files with 8892 additions and 397 deletions
+9 -4
View File
@@ -35,8 +35,12 @@ class AlarmModel {
factory AlarmModel.fromFirestore(DocumentSnapshot doc) {
final data = doc.data() as Map<String, dynamic>;
return AlarmModel.fromMap(doc.id, data);
}
factory AlarmModel.fromMap(String id, Map<String, dynamic> data) {
return AlarmModel(
id: doc.id,
id: id,
title: data['title'] ?? '',
taskId: data['taskId'],
eventId: data['eventId'],
@@ -107,10 +111,10 @@ class AlarmModel {
DateTime get nextOccurrence {
if (!isRepeating) return dateTime;
final now = DateTime.now();
if (dateTime.isAfter(now)) return dateTime;
switch (repeatPattern) {
case 'daily':
return dateTime.add(const Duration(days: 1));
@@ -124,7 +128,8 @@ class AlarmModel {
while (!repeatDays.contains(next.weekday)) {
next = next.add(const Duration(days: 1));
}
return DateTime(next.year, next.month, next.day, dateTime.hour, dateTime.minute);
return DateTime(
next.year, next.month, next.day, dateTime.hour, dateTime.minute);
default:
return dateTime;
}
+5 -1
View File
@@ -43,8 +43,12 @@ class CalendarEventModel {
factory CalendarEventModel.fromFirestore(DocumentSnapshot doc) {
final data = doc.data() as Map<String, dynamic>;
return CalendarEventModel.fromMap(doc.id, data);
}
factory CalendarEventModel.fromMap(String id, Map<String, dynamic> data) {
return CalendarEventModel(
id: doc.id,
id: id,
title: data['title'] ?? '',
description: data['description'],
location: data['location'] ?? '',
+5 -1
View File
@@ -35,8 +35,12 @@ class MindMapNode {
factory MindMapNode.fromFirestore(DocumentSnapshot doc) {
final data = doc.data() as Map<String, dynamic>;
return MindMapNode.fromMap(doc.id, data);
}
factory MindMapNode.fromMap(String id, Map<String, dynamic> data) {
return MindMapNode(
id: doc.id,
id: id,
projectId: data['projectId'] ?? '',
parentId: data['parentId'],
level: data['level'] ?? 0,
+88
View File
@@ -0,0 +1,88 @@
import 'package:cloud_firestore/cloud_firestore.dart';
class MindMapModel {
final String id;
final String title;
final String description;
final String ownerEmail;
final bool isMain; // Основные карты (админские) vs Личные
final List<Map<String, dynamic>> nodes;
final Map<String, dynamic> settings;
final DateTime createdAt;
final DateTime updatedAt;
MindMapModel({
required this.id,
required this.title,
this.description = '',
required this.ownerEmail,
this.isMain = false,
this.nodes = const [],
this.settings = const {},
required this.createdAt,
required this.updatedAt,
});
factory MindMapModel.fromFirestore(DocumentSnapshot doc) {
final data = doc.data() as Map<String, dynamic>;
return MindMapModel.fromMap(doc.id, data);
}
factory MindMapModel.fromMap(String id, Map<String, dynamic> data) {
return MindMapModel(
id: id,
title: data['title'] ?? '',
description: data['description'] ?? '',
ownerEmail: data['ownerEmail'] ?? '',
isMain: data['isMain'] ?? false,
nodes: List<Map<String, dynamic>>.from(data['nodes'] ?? []),
settings: data['settings'] ?? {},
createdAt: (data['createdAt'] as Timestamp).toDate(),
updatedAt: (data['updatedAt'] as Timestamp).toDate(),
);
}
Map<String, dynamic> toFirestore() {
return {
'title': title,
'description': description,
'ownerEmail': ownerEmail,
'isMain': isMain,
'nodes': nodes,
'settings': settings,
'createdAt': Timestamp.fromDate(createdAt),
'updatedAt': Timestamp.fromDate(updatedAt),
};
}
MindMapModel copyWith({
String? id,
String? title,
String? description,
String? ownerEmail,
bool? isMain,
List<Map<String, dynamic>>? nodes,
Map<String, dynamic>? settings,
DateTime? createdAt,
DateTime? updatedAt,
}) {
return MindMapModel(
id: id ?? this.id,
title: title ?? this.title,
description: description ?? this.description,
ownerEmail: ownerEmail ?? this.ownerEmail,
isMain: isMain ?? this.isMain,
nodes: nodes ?? this.nodes,
settings: settings ?? this.settings,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
);
}
int get nodeCount => nodes.length;
int get maxLevel {
if (nodes.isEmpty) return 0;
return nodes.fold(
0, (max, node) => max > (node['level'] ?? 0) ? max : (node['level'] ?? 0));
}
}
+24 -15
View File
@@ -34,24 +34,33 @@ class ProjectModel {
});
factory ProjectModel.fromFirestore(DocumentSnapshot doc) {
final data = doc.data() as Map<String, dynamic>;
final data = doc.data();
if (data == null) {
throw Exception('Document data is null');
}
final mapData = data is Map<String, dynamic>
? data
: (data as Map<dynamic, dynamic>)
.map((k, v) => MapEntry(k.toString(), v));
return ProjectModel(
id: doc.id,
name: data['name'] ?? '',
description: data['description'] ?? '',
teamId: data['teamId'] ?? '',
ownerId: data['ownerId'] ?? '',
memberIds: List<String>.from(data['memberIds'] ?? []),
status: data['status'] ?? 'active',
startDate: (data['startDate'] as Timestamp).toDate(),
endDate: data['endDate'] != null
? (data['endDate'] as Timestamp).toDate()
name: mapData['name'] ?? '',
description: mapData['description'] ?? '',
teamId: mapData['teamId'] ?? '',
ownerId: mapData['ownerId'] ?? '',
memberIds: List<String>.from(mapData['memberIds'] ?? []),
status: mapData['status'] ?? 'active',
startDate: (mapData['startDate'] as Timestamp).toDate(),
endDate: mapData['endDate'] != null
? (mapData['endDate'] as Timestamp).toDate()
: null,
color: data['color'] ?? '#1976D2',
icon: data['icon'] ?? 'project',
settings: data['settings'] ?? {},
createdAt: (data['createdAt'] as Timestamp).toDate(),
updatedAt: (data['updatedAt'] as Timestamp).toDate(),
color: mapData['color'] ?? '#1976D2',
icon: mapData['icon'] ?? 'project',
settings: mapData['settings'] ?? {},
createdAt: (mapData['createdAt'] as Timestamp).toDate(),
updatedAt: (mapData['updatedAt'] as Timestamp).toDate(),
);
}