From d798320c244b0a703e9d00060c2e2075b4559872 Mon Sep 17 00:00:00 2001 From: Doug Hoyte Date: Thu, 19 Dec 2024 00:44:16 -0500 Subject: [PATCH] paginate followers --- src/apps/web/TODO | 1 - src/apps/web/WebData.h | 8 ++++-- src/apps/web/WebReader.cpp | 35 +++++++++++++++++++++++--- src/apps/web/static/oddbean.css | 8 ++++++ src/apps/web/tmpls/feed/list.tmpl | 2 +- src/apps/web/tmpls/user/followers.tmpl | 8 ++++++ src/apps/web/tmpls/user/following.tmpl | 2 ++ 7 files changed, 57 insertions(+), 7 deletions(-) diff --git a/src/apps/web/TODO b/src/apps/web/TODO index e8a9f5f..6d099a3 100644 --- a/src/apps/web/TODO +++ b/src/apps/web/TODO @@ -1,5 +1,4 @@ read - ! paginate followers * nostr: links not replaced in feed titles * support nprofile/nevent/etc links * non-500 error pages when bech32 fails to parse, for example diff --git a/src/apps/web/WebData.h b/src/apps/web/WebData.h index ce9c131..54d6268 100644 --- a/src/apps/web/WebData.h +++ b/src/apps/web/WebData.h @@ -128,13 +128,15 @@ struct User { kind3Event = loadKindEvent(txn, decomp, 3); } - std::vector getFollowers(lmdb::txn &txn, Decompressor &decomp, const std::string &pubkey) { + std::vector getFollowers(lmdb::txn &txn, Decompressor &decomp, const std::string &pubkey, uint64_t offset = 0, uint64_t limit = MAX_U64, uint64_t *countOut = nullptr) { std::vector output; flat_hash_set alreadySeen; std::string prefix = "p"; prefix += pubkey; + uint64_t curr = 0; + env.generic_foreachFull(txn, env.dbi_Event__tag, prefix, "", [&](std::string_view k, std::string_view v){ ParsedKey_StringUint64 parsedKey(k); if (parsedKey.s != prefix) return false; @@ -148,13 +150,15 @@ struct User { if (!alreadySeen.contains(pubkey)) { alreadySeen.insert(pubkey); - output.emplace_back(std::move(pubkey)); + curr++; + if (curr >= offset && curr - offset < limit) output.emplace_back(std::move(pubkey)); } } return true; }); + if (countOut) *countOut = curr; return output; } }; diff --git a/src/apps/web/WebReader.cpp b/src/apps/web/WebReader.cpp index 2eafa24..7720388 100644 --- a/src/apps/web/WebReader.cpp +++ b/src/apps/web/WebReader.cpp @@ -186,8 +186,11 @@ HTTPResponse WebServer::generateReadResponse(lmdb::txn &txn, Decompressor &decom auto handleFeed = [&](std::string_view feedId){ uint64_t resultsPerPage = 30; uint64_t page = 0; - auto pageStr = u.lookupQuery("p"); - if (pageStr) page = std::stoull(std::string(*pageStr)); + + try { + auto pageStr = u.lookupQuery("p"); + if (pageStr) page = std::stoull(std::string(*pageStr)); + } catch(...) {} feedReader.emplace(txn, decomp, feedId); @@ -267,28 +270,54 @@ HTTPResponse WebServer::generateReadResponse(lmdb::txn &txn, Decompressor &decom title = std::string("following: ") + user.username; user.populateContactList(txn, decomp); + uint64_t numFollowing = 0; + if (user.kind3Event) { + for (const auto &tagJson : user.kind3Event->at("tags").get_array()) { + const auto &tag = tagJson.get_array(); + if (tag.size() >= 2 && tag.at(0).get_string() == "p") numFollowing++; + } + } + struct { User &user; std::function getUser; + uint64_t numFollowing; } ctx = { user, [&](const std::string &pubkey){ return userCache.getUser(txn, decomp, pubkey); }, + numFollowing, }; body = tmpl::user::following(ctx); } else if (u.path[2] == "followers") { + uint64_t resultsPerPage = 500; + uint64_t page = 0; + + try { + auto pageStr = u.lookupQuery("p"); + if (pageStr) page = std::stoull(std::string(*pageStr)); + } catch(...) {} + User user(txn, decomp, userPubkey); title = std::string("followers: ") + user.username; - auto followers = user.getFollowers(txn, decomp, user.pubkey); + uint64_t numFollowers = 0; + auto followers = user.getFollowers(txn, decomp, user.pubkey, page * resultsPerPage, resultsPerPage, &numFollowers); + uint64_t numPages = (numFollowers + resultsPerPage + 1) / resultsPerPage; struct { const User &user; const std::vector &followers; + uint64_t numFollowers; std::function getUser; + uint64_t page; + uint64_t numPages; } ctx = { user, followers, + numFollowers, [&](const std::string &pubkey){ return userCache.getUser(txn, decomp, pubkey); }, + page, + numPages, }; body = tmpl::user::followers(ctx); diff --git a/src/apps/web/static/oddbean.css b/src/apps/web/static/oddbean.css index 9415fc0..bbfbc54 100644 --- a/src/apps/web/static/oddbean.css +++ b/src/apps/web/static/oddbean.css @@ -292,6 +292,14 @@ table.vert { margin-left: 40px; } +.pagination-links { + margin-top: 40px; + text-align: center; + > * { + margin: 10px; + } +} + .feed-info { .feed-id { background-color: #dfdfdf; diff --git a/src/apps/web/tmpls/feed/list.tmpl b/src/apps/web/tmpls/feed/list.tmpl index ee02af9..d596f1e 100644 --- a/src/apps/web/tmpls/feed/list.tmpl +++ b/src/apps/web/tmpls/feed/list.tmpl @@ -3,6 +3,6 @@ diff --git a/src/apps/web/tmpls/user/followers.tmpl b/src/apps/web/tmpls/user/followers.tmpl index b9b3974..3fa4784 100644 --- a/src/apps/web/tmpls/user/followers.tmpl +++ b/src/apps/web/tmpls/user/followers.tmpl @@ -1,4 +1,6 @@
+

$(ctx.user.username) has $(ctx.numFollowers) known followers.

+ @@ -16,4 +18,10 @@
user
+ +
diff --git a/src/apps/web/tmpls/user/following.tmpl b/src/apps/web/tmpls/user/following.tmpl index a5c1967..2bdb577 100644 --- a/src/apps/web/tmpls/user/following.tmpl +++ b/src/apps/web/tmpls/user/following.tmpl @@ -1,4 +1,6 @@
+

$(ctx.user.username) is following $(ctx.numFollowing) users.

?(ctx.user.kind3Event) +
?(!ctx.user.kind3Event) No kind 3 contact list found for $(ctx.user.npubId)