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:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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'] ?? '',
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user