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 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'); }); // Handle CORS Preflight (OPTIONS) for all routes app.add('OPTIONS', r'/', (Request request) { return Response.ok('', headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', 'Access-Control-Allow-Headers': 'Origin, Content-Type', }); }); // Sync Push Endpoint // Expects JSON: { "userId": "...", "entries": [], "teachingPlans": [], "prayerRequests": [] } app.post('/sync/push', (Request request) async { try { final payload = await request.readAsString(); final data = jsonDecode(payload); final userId = data['userId']; // 0. Update User Record // We expect 'userDetails' in payload now, but handle legacy if (data.containsKey('userDetails')) { db.upsertUser(userId, data['userDetails']); } else { // Fallback: create basic record if missing? // Or just let it fail if we need linking. // We'll trust frontend sends it now. } // 1. Cycle Entries final entries = data['entries'] as List? ?? []; for (var entry in entries) { db.upsertCycleEntry(userId, entry); } // 2. Teaching Plans final teachingPlans = data['teachingPlans'] as List? ?? []; for (var plan in teachingPlans) { db.upsertTeachingPlan(userId, plan); } // 3. Prayer Requests final prayerRequests = data['prayerRequests'] as List? ?? []; for (var req in prayerRequests) { db.upsertPrayerRequest(userId, req); } print( 'Synced for $userId: ${entries.length} entries, ${teachingPlans.length} plans, ${prayerRequests.length} prayers'); return Response.ok(jsonEncode({'status': 'success'})); } catch (e) { print('Sync Error: $e'); return Response.internalServerError(body: 'Sync Failed: $e'); } }); // Preview Link Endpoint // POST /sync/preview // Body: { "targetId": "..." } // Returns: { "name": "...", "role": "..." } app.post('/sync/preview', (Request request) async { try { final payload = await request.readAsString(); final data = jsonDecode(payload); final targetId = data['targetId']; if (targetId == null) { return Response.badRequest(body: 'Missing targetId'); } final targetUser = db.getUser(targetId); if (targetUser == null) { return Response.notFound(jsonEncode({'error': 'Partner ID not found'})); } return Response.ok(jsonEncode({ 'status': 'success', 'partnerName': targetUser['name'], 'partnerRole': targetUser['role'], })); } catch (e) { print('Preview Error: $e'); return Response.internalServerError(body: 'Preview Failed: $e'); } }); // Link Endpoint // POST /sync/link // Body: { "userId": "...", "targetId": "..." } app.post('/sync/link', (Request request) async { try { final payload = await request.readAsString(); final data = jsonDecode(payload); final userId = data['userId']; final targetId = data['targetId']; if (userId == null || targetId == null) { return Response.badRequest(body: 'Missing userId or targetId'); } // Verify target exists final targetUser = db.getUser(targetId); if (targetUser == null) { return Response.notFound(jsonEncode({'error': 'Partner ID not found'})); } // Perform Link db.linkPartners(userId, targetId); // Return partner name/info return Response.ok(jsonEncode({ 'status': 'success', 'partnerName': targetUser['name'], 'partnerEmail': targetUser['email'] })); } catch (e) { print('Link Error: $e'); return Response.internalServerError(body: 'Link Failed: $e'); } }); // Pull Endpoint // GET /sync/pull?userId=...&partnerId=... app.get('/sync/pull', (Request request) { final userId = request.url.queryParameters['userId']; final partnerId = request.url.queryParameters['partnerId']; if (userId == null) return Response.badRequest(body: 'Missing userId'); // 1. Get My Data final myEntries = db.getCycleEntries(userId); final myPlans = db.getTeachingPlans(userId); // Plans I created final myPrayers = db.getPrayerRequests(userId); // Prayers I created // 2. Get Partner Data (if linked) List> partnerEntries = []; List> partnerPlans = []; List> partnerPrayers = []; if (partnerId != null && partnerId.isNotEmpty) { // Fetch partner's cycle entries partnerEntries = db.getCycleEntries(partnerId); // Fetch plans created by partner (e.g. Husband created plans for Wife to see) partnerPlans = db.getTeachingPlans(partnerId); // Fetch partner's prayer requests partnerPrayers = db.getPrayerRequests(partnerId); } // 3. Get User Profile (to sync back partner changes) final userProfile = db.getUser(userId); // Combine Data final responseData = { 'entries': [...myEntries, ...partnerEntries], // Teaching Plans: I want to see my own AND my partner's 'teachingPlans': [...myPlans, ...partnerPlans], // Prayer Requests: I want to see my own AND my partner's 'prayerRequests': [...myPrayers, ...partnerPrayers], if (userProfile != null) 'userProfile': userProfile, }; return Response.ok(jsonEncode(responseData)); }); // 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}'); }