cyberhybridhub/server/lib/handlers/spelling_words_handler.dart
nathanAndrinoid 4459852f83
Some checks failed
Admin portal tests / test (push) Has been cancelled
spelling
2026-06-05 03:22:35 +00:00

138 lines
4.6 KiB
Dart

import 'dart:convert';
import 'dart:io';
import 'package:shelf/shelf.dart';
import 'package:shelf_router/shelf_router.dart';
import '../cors_headers.dart';
import '../firebase_auth.dart';
import '../spelling/spelling_items_db.dart';
const String spellingWordsBasePath = '/v1/me/spelling-words';
Handler spellingWordsHandler({
required FirebaseAuthVerifier auth,
required SpellingItemsDb spellingItemsDb,
}) {
final Router router = Router();
router.get(spellingWordsBasePath, (Request request) async {
final String? firebaseUid = await _verify(auth, request);
if (firebaseUid == null) {
return _jsonResponse(401, <String, dynamic>{'error': 'Unauthorized'});
}
try {
final List<String> words = await spellingItemsDb.listCanonicalWords();
return _jsonResponse(200, <String, dynamic>{'words': words});
} catch (e, st) {
stderr.writeln('List spelling words error: $e\n$st');
return _jsonResponse(500, <String, dynamic>{'error': 'Internal error'});
}
});
router.post('$spellingWordsBasePath/import', (Request request) async {
final String? firebaseUid = await _verify(auth, request);
if (firebaseUid == null) {
return _jsonResponse(401, <String, dynamic>{'error': 'Unauthorized'});
}
try {
final String body = await request.readAsString();
final List<String> lines = body.split(RegExp(r'\r?\n'));
final ({int added, int skipped}) result =
await spellingItemsDb.importCanonicalWords(lines);
return _jsonResponse(200, <String, dynamic>{
'added': result.added,
'skipped': result.skipped,
});
} catch (e, st) {
stderr.writeln('Import spelling words error: $e\n$st');
return _jsonResponse(500, <String, dynamic>{'error': 'Internal error'});
}
});
router.get('$spellingWordsBasePath/export', (Request request) async {
final String? firebaseUid = await _verify(auth, request);
if (firebaseUid == null) {
return Response(401, body: 'Unauthorized', headers: _textCorsHeaders());
}
try {
final List<String> words = await spellingItemsDb.listCanonicalWords();
return Response.ok(
words.join('\n'),
headers: <String, String>{
..._textCorsHeaders(),
'Content-Type': 'text/plain; charset=utf-8',
},
);
} catch (e, st) {
stderr.writeln('Export spelling words error: $e\n$st');
return Response(500, body: 'Internal error', headers: _textCorsHeaders());
}
});
router.post(spellingWordsBasePath, (Request request) async {
final String? firebaseUid = await _verify(auth, request);
if (firebaseUid == null) {
return _jsonResponse(401, <String, dynamic>{'error': 'Unauthorized'});
}
try {
final String body = await request.readAsString();
final Map<String, dynamic> json = body.isEmpty
? <String, dynamic>{}
: jsonDecode(body) as Map<String, dynamic>;
final String? word = json['word'] as String?;
if (word == null || word.trim().isEmpty) {
return _jsonResponse(400, <String, dynamic>{'error': 'word is required'});
}
final bool added = await spellingItemsDb.addCanonicalWord(word);
if (!added) {
return _jsonResponse(409, <String, dynamic>{'error': 'Word already exists'});
}
final List<String> words = await spellingItemsDb.listCanonicalWords();
return _jsonResponse(201, <String, dynamic>{
'word': word.trim().toLowerCase(),
'words': words,
});
} catch (e, st) {
stderr.writeln('Add spelling word error: $e\n$st');
return _jsonResponse(500, <String, dynamic>{'error': 'Internal error'});
}
});
return (Request request) async {
if (request.method == 'OPTIONS' &&
request.requestedUri.path.startsWith(spellingWordsBasePath)) {
return Response.ok('', headers: _jsonCorsHeaders());
}
final Response response = await router.call(request);
if (response.statusCode != 404) {
return response;
}
return _jsonResponse(404, <String, dynamic>{'error': 'Not found'});
};
}
Future<String?> _verify(FirebaseAuthVerifier auth, Request request) {
return auth.verifyBearerToken(
request.headers['Authorization'] ?? request.headers['authorization'],
);
}
Map<String, String> _jsonCorsHeaders() =>
apiCorsHeaders(methods: 'GET, POST, OPTIONS');
Map<String, String> _textCorsHeaders() =>
apiCorsHeaders(methods: 'GET, OPTIONS');
Response _jsonResponse(int status, Map<String, dynamic> body) {
return Response(
status,
body: jsonEncode(body),
headers: <String, String>{
..._jsonCorsHeaders(),
'Content-Type': 'application/json',
},
);
}