Implement data sync and cleanup
This commit is contained in:
67
backend/bin/server.dart
Normal file
67
backend/bin/server.dart
Normal file
@@ -0,0 +1,67 @@
|
||||
import 'dart:io';
|
||||
import 'dart:convert';
|
||||
import 'package:shelf/shelf.dart';
|
||||
import 'package:shelf/shelf_io.dart' as io;
|
||||
import 'package:shelf_router/shelf_router.dart';
|
||||
import 'package:tracker_backend/database.dart';
|
||||
|
||||
void main(List<String> args) async {
|
||||
// Use port 8090 as requested
|
||||
final port = 8090;
|
||||
final db = TrackerDatabase();
|
||||
|
||||
final app = Router();
|
||||
|
||||
app.get('/', (Request request) {
|
||||
return Response.ok('Tracker Sync Server Running');
|
||||
});
|
||||
|
||||
// Simple Sync Endpoint (Push)
|
||||
// Expects JSON: { "userId": "...", "entries": [ ... ] }
|
||||
app.post('/sync/push', (Request request) async {
|
||||
try {
|
||||
final payload = await request.readAsString();
|
||||
final data = jsonDecode(payload);
|
||||
final userId = data['userId'];
|
||||
final entries = data['entries'] as List;
|
||||
|
||||
print('Received sync push for $userId with ${entries.length} entries');
|
||||
|
||||
for (var entry in entries) {
|
||||
// Basic upsert handling
|
||||
db.upsertCycleEntry(userId, entry);
|
||||
}
|
||||
|
||||
return Response.ok(
|
||||
jsonEncode({'status': 'success', 'synced': entries.length}));
|
||||
} catch (e) {
|
||||
print('Sync Error: $e');
|
||||
return Response.internalServerError(body: 'Sync Failed: $e');
|
||||
}
|
||||
});
|
||||
|
||||
// Pull Endpoint
|
||||
// GET /sync/pull?userId=...
|
||||
app.get('/sync/pull', (Request request) {
|
||||
final userId = request.url.queryParameters['userId'];
|
||||
if (userId == null) return Response.badRequest(body: 'Missing userId');
|
||||
|
||||
final entries = db.getCycleEntries(userId);
|
||||
return Response.ok(jsonEncode({'entries': entries}));
|
||||
});
|
||||
|
||||
// Enable CORS
|
||||
final handler = Pipeline().addMiddleware((innerHandler) {
|
||||
return (request) async {
|
||||
final response = await innerHandler(request);
|
||||
return response.change(headers: {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Origin, Content-Type',
|
||||
});
|
||||
};
|
||||
}).addHandler(app);
|
||||
|
||||
final server = await io.serve(handler, InternetAddress.anyIPv4, port);
|
||||
print('Server running on localhost:${server.port}');
|
||||
}
|
||||
BIN
backend/data/tracker.db
Normal file
BIN
backend/data/tracker.db
Normal file
Binary file not shown.
117
backend/lib/database.dart
Normal file
117
backend/lib/database.dart
Normal file
@@ -0,0 +1,117 @@
|
||||
import 'dart:io';
|
||||
import 'package:sqlite3/sqlite3.dart' as sql;
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
class TrackerDatabase {
|
||||
late final sql.Database _db;
|
||||
|
||||
TrackerDatabase() {
|
||||
_init();
|
||||
}
|
||||
|
||||
void _init() {
|
||||
// Ensure data directory exists
|
||||
final dataDir = Directory('data');
|
||||
if (!dataDir.existsSync()) {
|
||||
dataDir.createSync();
|
||||
}
|
||||
|
||||
final dbPath = p.join('data', 'tracker.db');
|
||||
print('Opening database at $dbPath');
|
||||
_db = sql.sqlite3.open(dbPath);
|
||||
|
||||
_createTables();
|
||||
}
|
||||
|
||||
void _createTables() {
|
||||
print('Creating tables if not exist...');
|
||||
|
||||
// Users table
|
||||
_db.execute('''
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id TEXT PRIMARY KEY,
|
||||
role TEXT NOT NULL,
|
||||
name TEXT,
|
||||
email TEXT,
|
||||
partner_id TEXT,
|
||||
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TEXT DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
''');
|
||||
|
||||
// Cycle Entries table
|
||||
_db.execute('''
|
||||
CREATE TABLE IF NOT EXISTS cycle_entries (
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id TEXT NOT NULL,
|
||||
date TEXT NOT NULL,
|
||||
flow_intensity TEXT,
|
||||
is_period_day INTEGER DEFAULT 0,
|
||||
symptoms TEXT, -- JSON string
|
||||
moods TEXT, -- JSON string
|
||||
notes TEXT,
|
||||
updated_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(user_id, date)
|
||||
)
|
||||
''');
|
||||
|
||||
// Teaching Plans table
|
||||
_db.execute('''
|
||||
CREATE TABLE IF NOT EXISTS teaching_plans (
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id TEXT NOT NULL,
|
||||
date TEXT NOT NULL,
|
||||
topic TEXT,
|
||||
scripture_reference TEXT,
|
||||
notes TEXT,
|
||||
application_question TEXT,
|
||||
prayer_points TEXT, -- JSON string
|
||||
updated_at TEXT DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
''');
|
||||
|
||||
print('Tables created.');
|
||||
}
|
||||
|
||||
// Basic CRUD placeholders for sync
|
||||
|
||||
void upsertCycleEntry(String userId, Map<String, dynamic> entry) {
|
||||
// Assuming entry contains fields matching DB
|
||||
final stmt = _db.prepare('''
|
||||
INSERT OR REPLACE INTO cycle_entries (
|
||||
id, user_id, date, flow_intensity, is_period_day, symptoms, moods, notes, updated_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
||||
''');
|
||||
|
||||
stmt.execute([
|
||||
entry['id'],
|
||||
userId,
|
||||
entry['date'],
|
||||
entry['flowIntensity'],
|
||||
entry['isPeriodDay'] == true ? 1 : 0,
|
||||
entry['symptoms'], // JSON
|
||||
entry['moods'], // JSON
|
||||
entry['notes'],
|
||||
]);
|
||||
stmt.dispose();
|
||||
}
|
||||
|
||||
List<Map<String, dynamic>> getCycleEntries(String userId, {String? since}) {
|
||||
// If since is provided, filter by updated_at
|
||||
// For MVP sync, we might just return all or a simple diff
|
||||
final result =
|
||||
_db.select('SELECT * FROM cycle_entries WHERE user_id = ?', [userId]);
|
||||
return result
|
||||
.map((row) => {
|
||||
'id': row['id'],
|
||||
'date': row['date'],
|
||||
'flowIntensity': row['flow_intensity'],
|
||||
'isPeriodDay': row['is_period_day'] == 1,
|
||||
'symptoms': row['symptoms'],
|
||||
'moods': row['moods'],
|
||||
'notes': row['notes'],
|
||||
'updatedAt': row['updated_at']
|
||||
})
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
173
backend/pubspec.lock
Normal file
173
backend/pubspec.lock
Normal file
@@ -0,0 +1,173 @@
|
||||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: async
|
||||
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.13.0"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.19.1"
|
||||
crypto:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: crypto
|
||||
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.7"
|
||||
ffi:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: ffi
|
||||
sha256: d07d37192dbf97461359c1518788f203b0c9102cfd2c35a716b823741219542c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.5"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fixnum
|
||||
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
http_methods:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_methods
|
||||
sha256: "6bccce8f1ec7b5d701e7921dca35e202d425b57e317ba1a37f2638590e29e566"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_parser
|
||||
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.2"
|
||||
lints:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: lints
|
||||
sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.17.0"
|
||||
path:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: path
|
||||
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
shelf:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: shelf
|
||||
sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.2"
|
||||
shelf_router:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: shelf_router
|
||||
sha256: f5e5d492440a7fb165fe1e2e1a623f31f734d3370900070b2b1e0d0428d59864
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.4"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_span
|
||||
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.10.1"
|
||||
sqlite3:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: sqlite3
|
||||
sha256: "3145bd74dcdb4fd6f5c6dda4d4e4490a8087d7f286a14dee5d37087290f0f8a2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.9.4"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stack_trace
|
||||
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.12.1"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_channel
|
||||
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: string_scanner
|
||||
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: term_glyph
|
||||
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.2"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: typed_data
|
||||
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
uuid:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: uuid
|
||||
sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.5.2"
|
||||
web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web
|
||||
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
sdks:
|
||||
dart: ">=3.7.0 <4.0.0"
|
||||
18
backend/pubspec.yaml
Normal file
18
backend/pubspec.yaml
Normal file
@@ -0,0 +1,18 @@
|
||||
name: tracker_backend
|
||||
description: A simple backend for Tracker sync.
|
||||
version: 1.0.0
|
||||
publish_to: "none"
|
||||
|
||||
environment:
|
||||
sdk: ^3.5.0
|
||||
|
||||
dependencies:
|
||||
shelf: ^1.4.1
|
||||
shelf_router: ^1.1.4
|
||||
sqlite3: ^2.4.0
|
||||
path: ^1.8.3
|
||||
uuid: ^4.0.0
|
||||
crypto: ^3.0.3
|
||||
|
||||
dev_dependencies:
|
||||
lints: ^2.1.1
|
||||
Reference in New Issue
Block a user