cyberhybridhub/server/lib/spelling/user_spelling_progress_db.dart
2026-06-05 15:10:03 +00:00

128 lines
3.3 KiB
Dart

import 'package:postgres/postgres.dart';
import 'spelling_progress_policy.dart';
/// Per-user familiarity with shared [spelling_items].
class UserSpellingProgressDb {
UserSpellingProgressDb(this._connection);
final Connection _connection;
/// Ensures a per-user link to [spellingItemId] with scores starting at zero.
Future<void> ensureLink({
required String firebaseUid,
required String spellingItemId,
}) async {
await _connection.execute(
Sql.named(
'''
INSERT INTO user_spelling_progress (
firebase_uid,
spelling_item_id,
familiarity_score,
attempt_count,
correct_count
) VALUES (
@uid,
@item_id::uuid,
0,
0,
0
)
ON CONFLICT (firebase_uid, spelling_item_id) DO NOTHING
''',
),
parameters: <String, dynamic>{
'uid': firebaseUid,
'item_id': spellingItemId,
},
);
}
Future<void> recordQuizOutcome({
required String firebaseUid,
required String spellingItemId,
required bool correct,
}) async {
final DateTime now = DateTime.now().toUtc();
final String outcome = correct ? 'correct' : 'incorrect';
final int delta = correct
? SpellingProgressPolicy.correctDelta
: -SpellingProgressPolicy.incorrectDelta;
await _connection.execute(
Sql.named(
'''
INSERT INTO user_spelling_progress (
firebase_uid,
spelling_item_id,
familiarity_score,
attempt_count,
correct_count,
last_attempted_at,
last_outcome
) VALUES (
@uid,
@item_id::uuid,
@delta,
1,
@correct_inc,
@now,
@outcome
)
ON CONFLICT (firebase_uid, spelling_item_id) DO UPDATE SET
familiarity_score = user_spelling_progress.familiarity_score + @delta,
attempt_count = user_spelling_progress.attempt_count + 1,
correct_count = user_spelling_progress.correct_count + @correct_inc,
last_attempted_at = @now,
last_outcome = @outcome
''',
),
parameters: <String, dynamic>{
'uid': firebaseUid,
'item_id': spellingItemId,
'delta': delta,
'correct_inc': correct ? 1 : 0,
'now': now,
'outcome': outcome,
},
);
}
/// Removes all per-user spelling familiarity rows (initial progress state).
Future<void> deleteAllForUser(String firebaseUid) async {
await _connection.execute(
Sql.named(
'''
DELETE FROM user_spelling_progress
WHERE firebase_uid = @uid
''',
),
parameters: <String, dynamic>{'uid': firebaseUid},
);
}
Future<int?> familiarityScore({
required String firebaseUid,
required String spellingItemId,
}) async {
final Result result = await _connection.execute(
Sql.named(
'''
SELECT familiarity_score
FROM user_spelling_progress
WHERE firebase_uid = @uid AND spelling_item_id = @item_id::uuid
''',
),
parameters: <String, dynamic>{
'uid': firebaseUid,
'item_id': spellingItemId,
},
);
if (result.isEmpty) {
return null;
}
return (result.first[0]! as num).toInt();
}
}