cyberhybridhub/lib/data/remote/spelling_words_remote_store.dart
2026-06-07 03:10:21 +00:00

142 lines
4.4 KiB
Dart

import 'dart:convert';
import 'package:http/http.dart' as http;
import '../../config/api_config.dart';
import '../../services/auth_service.dart';
class SpellingWordsApiException implements Exception {
SpellingWordsApiException(this.statusCode, this.body);
final int statusCode;
final String body;
@override
String toString() => 'SpellingWordsApiException($statusCode): $body';
}
/// HTTP client for the shared spelling word catalog.
class SpellingWordsRemoteStore {
SpellingWordsRemoteStore({http.Client? client})
: _client = client ?? http.Client();
final http.Client _client;
static const String _basePath = '/v1/me/spelling-words';
Future<List<String>> fetchWords() async {
final String? token = await AuthService.instance.getIdToken();
if (token == null) {
throw StateError('Not signed in');
}
final http.Response response = await _client.get(
Uri.parse('$apiBaseUrl$_basePath'),
headers: _authHeaders(token),
);
if (response.statusCode != 200) {
throw SpellingWordsApiException(response.statusCode, response.body);
}
final Map<String, dynamic> body =
jsonDecode(response.body) as Map<String, dynamic>;
final List<dynamic> words = body['words'] as List<dynamic>? ?? <dynamic>[];
return words.map((dynamic w) => w as String).toList(growable: false);
}
Future<List<String>> addWord(String word) async {
final String? token = await AuthService.instance.getIdToken();
if (token == null) {
throw StateError('Not signed in');
}
final http.Response response = await _client.post(
Uri.parse('$apiBaseUrl$_basePath'),
headers: _authHeaders(token),
body: jsonEncode(<String, dynamic>{'word': word}),
);
if (response.statusCode != 201) {
throw SpellingWordsApiException(response.statusCode, response.body);
}
final Map<String, dynamic> body =
jsonDecode(response.body) as Map<String, dynamic>;
final List<dynamic> words = body['words'] as List<dynamic>? ?? <dynamic>[];
return words.map((dynamic w) => w as String).toList(growable: false);
}
Future<({int added, int skipped})> importText(String text) async {
final String? token = await AuthService.instance.getIdToken();
if (token == null) {
throw StateError('Not signed in');
}
final http.Response response = await _client.post(
Uri.parse('$apiBaseUrl$_basePath/import'),
headers: <String, String>{
..._authHeaders(token),
'Content-Type': 'text/plain; charset=utf-8',
},
body: text,
);
if (response.statusCode != 200) {
throw SpellingWordsApiException(response.statusCode, response.body);
}
final Map<String, dynamic> body =
jsonDecode(response.body) as Map<String, dynamic>;
return (
added: (body['added'] as num?)?.toInt() ?? 0,
skipped: (body['skipped'] as num?)?.toInt() ?? 0,
);
}
Future<String> exportText() async {
final String? token = await AuthService.instance.getIdToken();
if (token == null) {
throw StateError('Not signed in');
}
final http.Response response = await _client.get(
Uri.parse('$apiBaseUrl$_basePath/export'),
headers: <String, String>{
'Authorization': 'Bearer $token',
'Accept': 'text/plain',
},
);
if (response.statusCode != 200) {
throw SpellingWordsApiException(response.statusCode, response.body);
}
return response.body;
}
Future<List<String>> deleteWord(String word) async {
final String? token = await AuthService.instance.getIdToken();
if (token == null) {
throw StateError('Not signed in');
}
final String encodedWord = Uri.encodeComponent(word);
final http.Response response = await _client.delete(
Uri.parse('$apiBaseUrl$_basePath/$encodedWord'),
headers: _authHeaders(token),
);
if (response.statusCode != 200) {
throw SpellingWordsApiException(response.statusCode, response.body);
}
final Map<String, dynamic> body =
jsonDecode(response.body) as Map<String, dynamic>;
final List<dynamic> words = body['words'] as List<dynamic>? ?? <dynamic>[];
return words.map((dynamic w) => w as String).toList(growable: false);
}
Map<String, String> _authHeaders(String token) {
return <String, String>{
'Authorization': 'Bearer $token',
'Content-Type': 'application/json',
'Accept': 'application/json',
};
}
}