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, is_completed INTEGER DEFAULT 0, updated_at TEXT DEFAULT CURRENT_TIMESTAMP ) '''); // Prayer Requests table _db.execute(''' CREATE TABLE IF NOT EXISTS prayer_requests ( id TEXT PRIMARY KEY, user_id TEXT NOT NULL, request TEXT, is_answered INTEGER DEFAULT 0, created_at TEXT DEFAULT CURRENT_TIMESTAMP, updated_at TEXT DEFAULT CURRENT_TIMESTAMP ) '''); print('Tables created.'); } // Basic CRUD placeholders for sync void upsertCycleEntry(String userId, Map 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> 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(); } // Teaching Plan operations void upsertTeachingPlan(String userId, Map plan) { final stmt = _db.prepare(''' INSERT OR REPLACE INTO teaching_plans ( id, user_id, date, topic, scripture_reference, notes, is_completed, updated_at ) VALUES (?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) '''); stmt.execute([ plan['id'], userId, plan['date'], plan['topic'], plan['scriptureReference'], plan['notes'], plan['isCompleted'] == true ? 1 : 0, ]); stmt.dispose(); } List> getTeachingPlans(String userId) { final result = _db.select('SELECT * FROM teaching_plans WHERE user_id = ?', [userId]); return result .map((row) => { 'id': row['id'], 'date': row['date'], 'topic': row['topic'], 'scriptureReference': row['scripture_reference'], 'notes': row['notes'], 'isCompleted': row['is_completed'] == 1, 'updatedAt': row['updated_at'] }) .toList(); } // Prayer Request operations void upsertPrayerRequest(String userId, Map request) { final stmt = _db.prepare(''' INSERT OR REPLACE INTO prayer_requests ( id, user_id, request, is_answered, created_at, updated_at ) VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP) '''); stmt.execute([ request['id'], userId, request['request'], request['isAnswered'] == true ? 1 : 0, request['createdAt'], ]); stmt.dispose(); } List> getPrayerRequests(String userId) { final result = _db.select('SELECT * FROM prayer_requests WHERE user_id = ?', [userId]); return result .map((row) => { 'id': row['id'], 'request': row['request'], 'isAnswered': row['is_answered'] == 1, 'createdAt': row['created_at'], 'updatedAt': row['updated_at'] }) .toList(); } // User operations void upsertUser(String userId, Map userData) { // Check if user exists first final existing = _db.select('SELECT * FROM users WHERE id = ?', [userId]); if (existing.isEmpty) { // Insert new final stmt = _db.prepare(''' INSERT INTO users ( id, role, name, email, partner_id, created_at, updated_at ) VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) '''); stmt.execute([ userId, userData['role'] ?? 'wife', userData['name'], userData['email'], userData['partnerId'], userData['createdAt'] ?? DateTime.now().toIso8601String(), ]); stmt.dispose(); } else { // Update existing // CRITICAL: Do NOT overwrite partner_id with NULL if it is already set in DB // unless the client specifically might intend it (which is hard to know). // For Safe Onboarding: If DB has a partner_id, and incoming is NULL, keep DB value. final row = existing.first; final dbPartnerId = row['partner_id']; final incomingPartnerId = userData['partnerId']; String? finalPartnerId = incomingPartnerId; if (incomingPartnerId == null && dbPartnerId != null) { finalPartnerId = dbPartnerId as String?; } final stmt = _db.prepare(''' UPDATE users SET role = ?, name = ?, email = ?, partner_id = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? '''); stmt.execute([ userData['role'] ?? row['role'], userData['name'] ?? row['name'], userData['email'] ?? row['email'], finalPartnerId, userId, ]); stmt.dispose(); } } bool linkPartners(String userId, String targetPartnerId) { // 1. Verify target exists (optional, or just blindly update) // For MVP, just update both. // Update User -> Partner _db.execute('UPDATE users SET partner_id = ? WHERE id = ?', [targetPartnerId, userId]); // Update Partner -> User _db.execute('UPDATE users SET partner_id = ? WHERE id = ?', [userId, targetPartnerId]); // Check if both have partner_id set now? // Just return true. return true; } Map? getUser(String userId) { final result = _db.select('SELECT * FROM users WHERE id = ?', [userId]); if (result.isEmpty) return null; final row = result.first; return { 'id': row['id'], 'role': row['role'], 'name': row['name'], 'email': row['email'], 'partnerId': row['partner_id'], }; } }