diff --git a/lib/main.dart b/lib/main.dart index 1ce2bd4..779d85e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -66,7 +66,8 @@ Future runPrimaryInstance(File ipcFile) async { startIpcWatcher(ipcFile); // Initialize the app - await DB.init(); + // await DB.init(); + await init(); await windowManager.ensureInitialized(); WindowOptions windowOptions = const WindowOptions( diff --git a/lib/meilisearch.dart b/lib/meilisearch.dart index 1ace5c8..0f5d761 100644 --- a/lib/meilisearch.dart +++ b/lib/meilisearch.dart @@ -1,5 +1,6 @@ import 'dart:convert'; import 'dart:core'; +import 'dart:math'; import 'package:http/http.dart' as http; import 'package:journaler/notes.dart'; @@ -7,7 +8,15 @@ const endpoint = 'https://meili.site.quack-lab.dev/'; const noteIndex = 'notes'; const scratchIndex = 'scratch'; const settingsIndex = 'settings'; -const apiKey = String.fromEnvironment('MEILISEARCH_API_KEY', defaultValue: ''); +const apiKey = String.fromEnvironment( + 'MEILISEARCH_API_KEY', + defaultValue: + '', +); +const header = { + 'Authorization': 'Bearer $apiKey', + 'Content-Type': 'application/json', +}; class MeilisearchQuery { String q; @@ -82,12 +91,60 @@ class MeilisearchResponse { } } +Future init() async { + if (!await indexExists(noteIndex)) { + await http.post( + Uri.parse('$endpoint/indexes'), + headers: header, + body: jsonEncode({'uid': noteIndex, 'primaryKey': 'id'}), + ); + await http.put( + Uri.parse('$endpoint/indexes/$noteIndex/settings/sortable-attributes'), + headers: header, + body: jsonEncode({ + 'attributes': ['date'], + }), + ); + } + + if (!await indexExists(scratchIndex)) { + await http.post( + Uri.parse('$endpoint/indexes'), + headers: header, + body: jsonEncode({'uid': scratchIndex, 'primaryKey': 'id'}), + ); + await http.put( + Uri.parse('$endpoint/indexes/$scratchIndex/settings/sortable-attributes'), + headers: header, + body: jsonEncode({ + 'attributes': ['date'], + }), + ); + } + + if (!await indexExists(settingsIndex)) { + await http.post( + Uri.parse('$endpoint/indexes'), + headers: header, + body: jsonEncode({'uid': settingsIndex, 'primaryKey': 'key'}), + ); + } +} + +Future indexExists(String index) async { + final response = await http.get( + Uri.parse('$endpoint/indexes/$index'), + headers: header, + ); + return response.statusCode == 200; +} + // Settings Management Future getSetting(String key) async { final searchCondition = MeilisearchQuery(q: '', filter: 'key = $key'); final response = await http.post( Uri.parse('$endpoint/indexes/$settingsIndex/search'), - headers: {'Authorization': 'Bearer $apiKey'}, + headers: header, body: jsonEncode(searchCondition.toJson()), ); if (response.statusCode != 200) { @@ -104,7 +161,7 @@ Future setSetting(String key, String value) async { final document = {'key': key, 'value': value}; final response = await http.post( Uri.parse('$endpoint/indexes/$settingsIndex/documents'), - headers: {'Authorization': 'Bearer $apiKey'}, + headers: header, body: jsonEncode(document), ); if (response.statusCode != 200) { @@ -125,7 +182,7 @@ Future> searchNotes(String query) async { ); final response = await http.post( Uri.parse('$endpoint/indexes/$noteIndex/search'), - headers: {'Authorization': 'Bearer $apiKey'}, + headers: header, body: jsonEncode(searchCondition.toJson()), ); if (response.statusCode != 200) { @@ -135,6 +192,7 @@ Future> searchNotes(String query) async { return responseJson.hits .map( (hit) => Note( + id: hit['id'] as String, date: hit['date'] as String, content: hit['content'] as String, ), @@ -151,7 +209,7 @@ Future getPreviousTo(String date) async { ); final response = await http.post( Uri.parse('$endpoint/indexes/$noteIndex/search'), - headers: {'Authorization': 'Bearer $apiKey'}, + headers: header, body: jsonEncode(searchCondition.toJson()), ); if (response.statusCode != 200) { @@ -162,6 +220,7 @@ Future getPreviousTo(String date) async { return null; } return Note( + id: responseJson.hits.first['id'] as String, date: responseJson.hits.first['date'] as String, content: responseJson.hits.first['content'] as String, ); @@ -176,7 +235,7 @@ Future getNextTo(String date) async { ); final response = await http.post( Uri.parse('$endpoint/indexes/$noteIndex/search'), - headers: {'Authorization': 'Bearer $apiKey'}, + headers: header, body: jsonEncode(searchCondition.toJson()), ); if (response.statusCode != 200) { @@ -187,6 +246,7 @@ Future getNextTo(String date) async { return null; } return Note( + id: responseJson.hits.first['id'] as String, date: responseJson.hits.first['date'] as String, content: responseJson.hits.first['content'] as String, ); @@ -195,12 +255,12 @@ Future getNextTo(String date) async { Future getLatest() async { final searchCondition = MeilisearchQuery( q: '', - sort: ['date DESC'], + sort: ['date:desc'], limit: 1, ); final response = await http.post( Uri.parse('$endpoint/indexes/$noteIndex/search'), - headers: {'Authorization': 'Bearer $apiKey'}, + headers: header, body: jsonEncode(searchCondition.toJson()), ); if (response.statusCode != 200) { @@ -211,11 +271,23 @@ Future getLatest() async { return null; } return Note( + id: responseJson.hits.first['id'] as String, date: responseJson.hits.first['date'] as String, content: responseJson.hits.first['content'] as String, ); } +String generateRandomString(int length) { + const characters = + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + var result = ''; + for (var i = 0; i < length; i++) { + final randomIndex = Random().nextInt(characters.length); + result += characters[randomIndex]; + } + return result; +} + Future createNote(String content) async { final lines = content.split('\n'); final trimmedLines = []; @@ -238,20 +310,22 @@ Future createNote(String content) async { final mostFrequentLetterCount = letterFrequency[mostFrequentLetter]; final document = { - 'id': DateTime.now().toIso8601String(), + 'id': generateRandomString(32), + 'date': DateTime.now().toIso8601String(), 'content': content, 'topLetter': mostFrequentLetter, 'topLetterFrequency': mostFrequentLetterCount, }; final response = await http.post( Uri.parse('$endpoint/indexes/$noteIndex/documents'), - headers: {'Authorization': 'Bearer $apiKey'}, + headers: header, body: jsonEncode(document), ); if (response.statusCode != 200) { throw Exception('Failed to create note'); } return Note( + id: document['id'] as String, date: document['id'] as String, content: document['content'] as String, ); @@ -264,7 +338,7 @@ Future> getProblematic({double threshold = 0.7}) async { ); final response = await http.post( Uri.parse('$endpoint/indexes/$noteIndex/search'), - headers: {'Authorization': 'Bearer $apiKey'}, + headers: header, body: jsonEncode(searchCondition.toJson()), ); if (response.statusCode != 200) { @@ -274,6 +348,7 @@ Future> getProblematic({double threshold = 0.7}) async { return responseJson.hits .map( (hit) => Note( + id: hit['id'] as String, date: hit['date'] as String, content: hit['content'] as String, isProblematic: true, @@ -285,10 +360,11 @@ Future> getProblematic({double threshold = 0.7}) async { } Future updateNote(String id, String content) async { + // TODO: Trim and calculate frequency final document = {'id': id, 'content': content}; final response = await http.post( Uri.parse('$endpoint/indexes/$noteIndex/documents'), - headers: {'Authorization': 'Bearer $apiKey'}, + headers: header, body: jsonEncode(document), ); if (response.statusCode != 200) { @@ -299,7 +375,7 @@ Future updateNote(String id, String content) async { Future deleteNote(String id) async { final response = await http.delete( Uri.parse('$endpoint/indexes/$noteIndex/documents/$id'), - headers: {'Authorization': 'Bearer $apiKey'}, + headers: header, ); if (response.statusCode != 200) { throw Exception('Failed to delete note'); @@ -314,7 +390,7 @@ Future getLatestScratch() async { ); final response = await http.post( Uri.parse('$endpoint/indexes/$scratchIndex/search'), - headers: {'Authorization': 'Bearer $apiKey'}, + headers: header, body: jsonEncode(searchCondition.toJson()), ); if (response.statusCode != 200) { @@ -334,7 +410,7 @@ Future createScratch(String content) async { final document = {'id': DateTime.now().toIso8601String(), 'content': content}; final response = await http.post( Uri.parse('$endpoint/indexes/$scratchIndex/documents'), - headers: {'Authorization': 'Bearer $apiKey'}, + headers: header, body: jsonEncode(document), ); if (response.statusCode != 200) { diff --git a/lib/notes.dart b/lib/notes.dart index bd52d60..07d3063 100644 --- a/lib/notes.dart +++ b/lib/notes.dart @@ -1,6 +1,7 @@ import 'package:intl/intl.dart'; class Note { + final String id; final String date; late final String displayDate; String content; @@ -9,6 +10,7 @@ class Note { String problemReason; Note({ + required this.id, required this.date, required this.content, this.snippet,