import '../../config/database.dart'; /// Data access layer for streaks and streak entries. class StreakRepository { // ── Create ────────────────────────────────────────────────────────── Future> create(Map data) async { final result = await Database.query( ''' INSERT INTO streaks ( id, user_id, name, description, frequency, grace_days, current_count, longest_count, frozen_until, created_at, updated_at ) VALUES ( @id, @user_id, @name, @description, @frequency, @grace_days, 0, 0, NULL, NOW(), NOW() ) RETURNING * ''', parameters: data, ); return _rowToMap(result.first); } // ── Read ──────────────────────────────────────────────────────────── Future?> findById(String id, String userId) async { final result = await Database.query( ''' SELECT * FROM streaks WHERE id = @id AND user_id = @user_id ''', parameters: {'id': id, 'user_id': userId}, ); if (result.isEmpty) return null; return _rowToMap(result.first); } Future>> findAll(String userId) async { final result = await Database.query( ''' SELECT * FROM streaks WHERE user_id = @user_id ORDER BY created_at DESC ''', parameters: {'user_id': userId}, ); return result.map(_rowToMap).toList(); } // ── Update ────────────────────────────────────────────────────────── Future?> update( String id, String userId, Map data, ) async { final setClauses = []; final params = {'id': id, 'user_id': userId}; data.forEach((key, value) { setClauses.add('$key = @$key'); params[key] = value; }); setClauses.add('updated_at = NOW()'); final result = await Database.query( ''' UPDATE streaks SET ${setClauses.join(', ')} WHERE id = @id AND user_id = @user_id RETURNING * ''', parameters: params, ); if (result.isEmpty) return null; return _rowToMap(result.first); } // ── Entries ───────────────────────────────────────────────────────── Future addEntry(Map data) async { await Database.query( ''' INSERT INTO streak_entries (id, streak_id, entry_date, entry_type, note, created_at) VALUES (@id, @streak_id, @entry_date, @entry_type, @note, NOW()) ON CONFLICT (streak_id, entry_date) DO NOTHING ''', parameters: data, ); } Future>> getHistory( String streakId, String userId, ) async { final result = await Database.query( ''' SELECT se.* FROM streak_entries se JOIN streaks s ON se.streak_id = s.id WHERE se.streak_id = @streak_id AND s.user_id = @user_id ORDER BY se.entry_date DESC LIMIT 90 ''', parameters: {'streak_id': streakId, 'user_id': userId}, ); return result .map((row) => { 'id': row[0], 'streak_id': row[1], 'entry_date': row[2] is DateTime ? (row[2] as DateTime).toIso8601String() : row[2], 'entry_type': row[3], 'note': row[4], 'created_at': row[5] is DateTime ? (row[5] as DateTime).toIso8601String() : row[5], }) .toList(); } Future lastCompletionDate(String streakId) async { final result = await Database.query( ''' SELECT MAX(entry_date) FROM streak_entries WHERE streak_id = @streak_id AND entry_type = 'completion' ''', parameters: {'streak_id': streakId}, ); if (result.isEmpty || result.first[0] == null) return null; final val = result.first[0]; if (val is DateTime) return val; return DateTime.tryParse(val.toString()); } // ── Row mapper ────────────────────────────────────────────────────── Map _rowToMap(dynamic row) { final columns = [ 'id', 'user_id', 'name', 'description', 'frequency', 'grace_days', 'current_count', 'longest_count', 'frozen_until', 'created_at', 'updated_at', ]; final map = {}; for (var i = 0; i < columns.length; i++) { final value = row[i]; map[columns[i]] = value is DateTime ? value.toIso8601String() : value; } return map; } }