From 400d9c9c01668944943580c6cd3269057a559fb4 Mon Sep 17 00:00:00 2001 From: Alec Murphy Date: Fri, 14 Mar 2025 18:24:10 -0400 Subject: [PATCH] Slon/Api/V1/Bookmarks: Implement Bookmarks Fixes #10 --- Slon/Api/V1/Bookmarks.HC | 33 +++++++++++++++++++-- Slon/Api/V1/Statuses.HC | 16 ++++++++++ Slon/Modules/Api.HC | 64 ++++++++++++++++++++++++++++++++++++++++ Slon/Modules/Db.HC | 34 +++++++++++++++++++++ 4 files changed, 144 insertions(+), 3 deletions(-) diff --git a/Slon/Api/V1/Bookmarks.HC b/Slon/Api/V1/Bookmarks.HC index a9c1d48..bd84077 100644 --- a/Slon/Api/V1/Bookmarks.HC +++ b/Slon/Api/V1/Bookmarks.HC @@ -3,9 +3,36 @@ U0 @slon_api_v1_bookmarks_get(SlonHttpSession* session) // SLON_SCRATCH_BUFFER_AND_REQUEST_JSON if (@slon_api_authorized(session)) { - // SLON_AUTH_ACCOUNT_ID - // FIXME: Implement this - session->send(SLON_EMPTY_JSON_ARRAY); + SLON_AUTH_ACCOUNT_ID + + JsonArray* bookmarks_array = db->o("bookmarks")->a(account_id); + JsonArray* bookmarks = NULL; + JsonObject* status = NULL; + U8* status_acct_id = NULL; + U8* status_id = NULL; + + I64 i; + if (bookmarks_array) { + bookmarks = Json.CreateArray(session->mem_task); + for (i = 0; i < bookmarks_array->length; i++) { + status_acct_id = bookmarks_array->o(i)->@("account_id"); + status_id = bookmarks_array->o(i)->@("status_id"); + if (status_id && status_acct_id) { + status = @slon_api_find_status_by_id(status_id, status_acct_id); + if (status) { + status = Json.Clone(status, session->mem_task); + // FIXME: We should have a unified way to apply these for an auth user during a query: + // favourited, reblogged, muted, bookmarked, pinned, filtered + status->set("bookmarked", TRUE, JSON_BOOLEAN); + bookmarks->append(status); + } + } + } + session->send(bookmarks); + } else { + session->send(SLON_EMPTY_JSON_ARRAY); + } + } else { session->status(401); } diff --git a/Slon/Api/V1/Statuses.HC b/Slon/Api/V1/Statuses.HC index 7843ff7..3b4aac3 100644 --- a/Slon/Api/V1/Statuses.HC +++ b/Slon/Api/V1/Statuses.HC @@ -234,6 +234,22 @@ U0 @slon_api_v1_statuses_post(SlonHttpSession* session) return; } + if (!StrICmp("bookmark", verb)) { + status = Json.Clone(status, session->mem_task); + @slon_api_bookmark_status(session, status, account_id); + status->set("bookmarked", TRUE, JSON_BOOLEAN); + session->send(status); + return; + } + + if (!StrICmp("unbookmark", verb)) { + status = Json.Clone(status, session->mem_task); + @slon_api_unbookmark_status(session, status, account_id); + status->set("bookmarked", FALSE, JSON_BOOLEAN); + session->send(status); + return; + } + if (!StrICmp("favourite", verb)) { status = Json.Clone(status, session->mem_task); @slon_api_favourite_status(session, status, account_id); diff --git a/Slon/Modules/Api.HC b/Slon/Modules/Api.HC index fd2d42c..7af7baa 100644 --- a/Slon/Modules/Api.HC +++ b/Slon/Modules/Api.HC @@ -147,6 +147,70 @@ JsonObject* @slon_api_announcement_by_id(U8* id) return NULL; } +Bool @slon_api_status_is_bookmarked(SlonHttpSession* session, JsonObject* status, U8* account_id) +{ + no_warn session; + JsonArray* bookmarks = db->o("bookmarks")->a(account_id); + JsonObject* bookmark = NULL; + if (!bookmarks) { + return FALSE; + } + I64 i; + for (i = 0; i < bookmarks->length; i++) { + bookmark = bookmarks->o(i); + if (!StrICmp(bookmark->@("status_id"), status->@("id")) && !StrICmp(bookmark->@("account_id"), status->o("account")->@("id"))) { + return TRUE; + } + } + return FALSE; +} + +U0 @slon_api_bookmark_status(SlonHttpSession* session, JsonObject* status, U8* account_id) +{ + Bool is_already_bookmarked = FALSE; + JsonArray* bookmarks = db->o("bookmarks")->a(account_id); + JsonObject* bookmark = NULL; + if (!bookmarks) { + bookmarks = Json.CreateArray(slon_db_mem_task); + db->o("bookmarks")->set(account_id, bookmarks, JSON_ARRAY); + } + I64 i; + for (i = 0; i < bookmarks->length; i++) { + bookmark = bookmarks->o(i); + if (!StrICmp(bookmark->@("status_id"), status->@("id")) && !StrICmp(bookmark->@("account_id"), status->o("account")->@("id"))) { + is_already_bookmarked = TRUE; + break; + } + } + if (!is_already_bookmarked) { + bookmark = Json.CreateObject(session->mem_task); + bookmark->set("status_id", status->@("id"), JSON_STRING); + bookmark->set("account_id", status->o("account")->@("id"), JSON_STRING); + bookmarks->append(bookmark); + @slon_db_save_bookmarks_to_disk; + } +} + +U0 @slon_api_unbookmark_status(SlonHttpSession* session, JsonObject* status, U8* account_id) +{ + no_warn session; + JsonArray* bookmarks = db->o("bookmarks")->a(account_id); + JsonObject* bookmark = NULL; + if (!bookmarks) { + bookmarks = Json.CreateArray(slon_db_mem_task); + db->o("bookmarks")->set(account_id, bookmarks, JSON_ARRAY); + } + I64 i; + for (i = 0; i < bookmarks->length; i++) { + bookmark = bookmarks->o(i); + if (!StrICmp(bookmark->@("status_id"), status->@("id")) && !StrICmp(bookmark->@("account_id"), status->o("account")->@("id"))) { + bookmarks->remove(i); + @slon_db_save_bookmarks_to_disk; + break; + } + } +} + Bool @slon_api_status_is_favourited(SlonHttpSession* session, JsonObject* status, U8* account_id) { no_warn session; diff --git a/Slon/Modules/Db.HC b/Slon/Modules/Db.HC index 2e5bb50..7207505 100644 --- a/Slon/Modules/Db.HC +++ b/Slon/Modules/Db.HC @@ -86,6 +86,26 @@ U0 @slon_db_load_private_keys_from_disk() db->set("private_keys", Json.ParseFile(scratch_buffer, slon_db_mem_task), JSON_OBJECT); } +U0 @slon_db_load_bookmarks_from_disk() +{ + JsonObject* bookmarks = Json.CreateObject(slon_db_mem_task); + U8 scratch_buffer[256]; + StrPrint(scratch_buffer, "%s/bookmarks/*.json", SLON_DB_PATH); + CDirEntry* files = FilesFind(scratch_buffer); + CDirEntry* de = files; + JsonArray* bookmark_array = NULL; + while (de) { + bookmark_array = Json.ParseFile(de->full_name, slon_db_mem_task); + if (bookmark_array) { + StrFind(".json", de->name)[0] = NULL; + bookmarks->set(de->name, bookmark_array, JSON_ARRAY); + } + de = de->next; + } + DirTreeDel(files); + db->set("bookmarks", bookmarks, JSON_OBJECT); +} + U0 @slon_db_load_favourites_from_disk() { JsonObject* favourites = Json.CreateObject(slon_db_mem_task); @@ -299,6 +319,17 @@ U0 @slon_db_save_private_keys_to_disk() Json.DumpToFile(scratch_buffer, db->o("private_keys"), slon_db_mem_task); } +U0 @slon_db_save_bookmarks_to_disk() +{ + U8 scratch_buffer[256]; + JsonKey* key = db->o("bookmarks")->keys; + while (key) { + StrPrint(scratch_buffer, "%s/bookmarks/%s.json", SLON_DB_PATH, key->name); + Json.DumpToFile(scratch_buffer, key->value, slon_db_mem_task); + key = key->next; + } +} + U0 @slon_db_save_favourites_to_disk() { U8 scratch_buffer[256]; @@ -389,6 +420,7 @@ U0 @slon_db_save_to_disk() @slon_db_save_actors_to_disk(); @slon_db_save_announcements_to_disk(); @slon_db_save_apps_to_disk(); + @slon_db_save_bookmarks_to_disk(); @slon_db_save_custom_emojis_to_disk(); @slon_db_save_favourites_to_disk(); @slon_db_save_followers_to_disk(); @@ -408,6 +440,7 @@ U0 @slon_db_load_from_defaults() db->set("actors", Json.CreateObject(slon_db_mem_task), JSON_OBJECT); db->set("announcements", Json.CreateArray(slon_db_mem_task), JSON_ARRAY); db->set("apps", Json.CreateObject(slon_db_mem_task), JSON_OBJECT); + db->set("bookmarks", Json.CreateObject(slon_db_mem_task), JSON_OBJECT); db->set("custom_emojis", Json.CreateArray(slon_db_mem_task), JSON_ARRAY); db->set("idempotency_keys", Json.CreateObject(slon_db_mem_task), JSON_OBJECT); db->set("private_keys", Json.CreateObject(slon_db_mem_task), JSON_OBJECT); @@ -439,6 +472,7 @@ U0 @slon_db_load_from_disk() @slon_db_load_actors_from_disk(); @slon_db_load_announcements_from_disk(); @slon_db_load_apps_from_disk(); + @slon_db_load_bookmarks_from_disk(); @slon_db_load_custom_emojis_from_disk(); db->set("idempotency_keys", Json.CreateObject(slon_db_mem_task), JSON_OBJECT); @slon_db_load_private_keys_from_disk();