Skip to content

Commit

Permalink
feat: add skip pagination over post tags (#293)
Browse files Browse the repository at this point in the history
  • Loading branch information
tipogi authored Jan 20, 2025
1 parent 97d10da commit 9a53bc3
Show file tree
Hide file tree
Showing 22 changed files with 407 additions and 93 deletions.
5 changes: 3 additions & 2 deletions benches/tag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ fn bench_get_user_tags(c: &mut Criterion) {
&user_id,
|b, &id| {
b.to_async(&rt).iter(|| async {
let tag_details_list = TagUser::get_by_id(id, None, None, None, None, None)
let tag_details_list = TagUser::get_by_id(id, None, None, None, None, None, None)
.await
.unwrap();
criterion::black_box(tag_details_list);
Expand Down Expand Up @@ -56,6 +56,7 @@ fn bench_get_wot_user_tags(c: &mut Criterion) {
None,
None,
None,
None,
Some("bbkdkhm97pytrb785rdpornkjpcxi331hpq446ckn6rhb4abiguy"),
Some(3),
)
Expand Down Expand Up @@ -143,7 +144,7 @@ fn bench_get_post_tags(c: &mut Criterion) {
|b, &params| {
b.to_async(&rt).iter(|| async {
let tag_details_list =
TagPost::get_by_id(params[0], Some(params[1]), None, None, None, None)
TagPost::get_by_id(params[0], Some(params[1]), None, None, None, None, None)
.await
.unwrap();
criterion::black_box(tag_details_list);
Expand Down
1 change: 1 addition & 0 deletions src/models/post/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ impl PostView {
TagPost::get_by_id(
author_id,
Some(post_id),
None,
limit_tags,
limit_taggers,
viewer_id,
Expand Down
31 changes: 27 additions & 4 deletions src/models/tag/traits/collection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ where
/// # Parameters
/// - `user_id` - A string slice representing the ID of the user for whom the tags are being retrieved
/// - `extra_param` - An optional string slice used as an additional filter or context in tag retrieval. If it is Some(), the value is post_id
/// - skip_tags - The number of tags to skip before retrieving results
/// - `limit_tags` - An optional limit on the number of tags to retrieve.
/// - `limit_taggers` - An optional limit on the number of taggers (users who have tagged) to retrieve.
/// - `viewer_id` - An optional string slice representing the ID of the viewer or requester.
Expand All @@ -47,6 +48,7 @@ where
async fn get_by_id(
user_id: &str,
extra_param: Option<&str>,
skip_tags: Option<usize>,
limit_tags: Option<usize>,
limit_taggers: Option<usize>,
viewer_id: Option<&str>,
Expand All @@ -55,7 +57,16 @@ where
// Query for the tags that are in its WoT
// Actually we just apply that search to User node
if viewer_id.is_some() && matches!(depth, Some(1..=3)) {
match Self::get_from_index(user_id, viewer_id, limit_tags, limit_taggers, true).await? {
match Self::get_from_index(
user_id,
viewer_id,
skip_tags,
limit_tags,
limit_taggers,
true,
)
.await?
{
Some(tag_details) => return Ok(Some(tag_details)),
None => {
let depth = depth.unwrap_or(1);
Expand All @@ -69,8 +80,17 @@ where
}
}
}
// Get global tags for that user
match Self::get_from_index(user_id, extra_param, limit_tags, limit_taggers, false).await? {
// Get global tags for that user/post
match Self::get_from_index(
user_id,
extra_param,
skip_tags,
limit_tags,
limit_taggers,
false,
)
.await?
{
Some(tag_details) => Ok(Some(tag_details)),
None => {
let graph_response = Self::get_from_graph(user_id, extra_param, None).await?;
Expand All @@ -87,6 +107,7 @@ where
/// # Arguments
/// * user_id - The key of the user for whom to retrieve tags.
/// * extra_param - An optional parameter for specifying additional constraints: post_id, viewer_id (for WoT search)
/// * skip_tags - The number of tags to skip before retrieving results
/// * limit_tags - A limit on the number of tags to retrieve.
/// * limit_taggers - A limit on the number of taggers to retrieve.
/// * is_cache - A boolean indicating whether to retrieve tags from the cache or the primary index.
Expand All @@ -97,11 +118,13 @@ where
async fn get_from_index(
user_id: &str,
extra_param: Option<&str>,
skip_tags: Option<usize>,
limit_tags: Option<usize>,
limit_taggers: Option<usize>,
is_cache: bool,
) -> Result<Option<Vec<TagDetails>>, DynError> {
let limit_tags = limit_tags.unwrap_or(5);
let skip_tags = skip_tags.unwrap_or(0);
let limit_taggers = limit_taggers.unwrap_or(5);
let key_parts = Self::create_sorted_set_key_parts(user_id, extra_param, is_cache);
// Prepare the extra prefix for cache search
Expand All @@ -117,7 +140,7 @@ where
&key_parts,
None,
None,
None,
Some(skip_tags),
Some(limit_tags),
SortOrder::Descending,
cache_prefix.0,
Expand Down
2 changes: 1 addition & 1 deletion src/models/user/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ impl UserView {
UserDetails::get_by_id(user_id),
UserCounts::get_by_id(user_id),
Relationship::get_by_id(user_id, viewer_id),
TagUser::get_by_id(user_id, None, None, None, viewer_id, depth)
TagUser::get_by_id(user_id, None, None, None, None, viewer_id, depth)
)?;

let details = match details {
Expand Down
2 changes: 1 addition & 1 deletion src/routes/v0/endpoints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pub const USER_FRIENDS_ROUTE: &str = concatcp!(USER_ROUTE, "/friends");
pub const USER_MUTED_ROUTE: &str = concatcp!(USER_ROUTE, "/muted");

// -- POST endpoints --
const POST_PREFIX: &str = concatcp!(VERSION_ROUTE, "/post");
pub const POST_PREFIX: &str = concatcp!(VERSION_ROUTE, "/post");
pub const POST_ROUTE: &str = concatcp!(POST_PREFIX, "/{author_id}/{post_id}");
pub const POST_RELATIONSHIPS_ROUTE: &str = concatcp!(POST_ROUTE, "/relationships");
pub const POST_BOOKMARK_ROUTE: &str = concatcp!(POST_ROUTE, "/bookmark");
Expand Down
20 changes: 11 additions & 9 deletions src/routes/v0/post/tags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,13 @@ use utoipa::OpenApi;
params(
("author_id" = String, Path, description = "Author Pubky ID"),
("post_id" = String, Path, description = "Post ID"),
("limit_tags" = Option<usize>, Query, description = "Upper limit on the number of tags for the posts"),
("limit_taggers" = Option<usize>, Query, description = "Upper limit on the number of taggers per tag"),
("skip_tags" = Option<usize>, Query, description = "Skip N tags. Defaults to `0`"),
("limit_tags" = Option<usize>, Query, description = "Upper limit on the number of tags for the posts. Defaults to `5`"),
("limit_taggers" = Option<usize>, Query, description = "Upper limit on the number of taggers per tag. Defaults to `5`"),
),
responses(
(status = 200, description = "Post tags", body = TagPost),
(status = 404, description = "Post not found"),
(status = 200, description = "Post tags", body = Vec<TagDetails>),
(status = 500, description = "Internal server error")
)
)]
Expand All @@ -33,15 +34,16 @@ pub async fn post_tags_handler(
Query(query): Query<TagsQuery>,
) -> Result<Json<Vec<TagDetails>>> {
info!(
"GET {POST_TAGS_ROUTE} author_id:{}, post_id: {}, limit_tags:{:?}, limit_taggers:{:?}",
author_id, post_id, query.limit_tags, query.limit_taggers
"GET {POST_TAGS_ROUTE} author_id:{}, post_id: {}, skip_tags:{:?}, limit_tags:{:?}, limit_taggers:{:?}",
author_id, post_id, query.limit_tags, query.skip_tags, query.limit_taggers
);
match TagPost::get_by_id(
&author_id,
Some(&post_id),
query.skip_tags,
query.limit_tags,
query.limit_taggers,
None,
None, // Avoid by default WoT tags in a Post
None, // Avoid by default WoT tags in a Post
)
.await
Expand All @@ -61,11 +63,11 @@ pub async fn post_tags_handler(
("author_id" = String, Path, description = "Author Pubky ID"),
("label" = String, Path, description = "Tag name"),
("post_id" = String, Path, description = "Post ID"),
("skip" = Option<usize>, Query, description = "Number of taggers to skip for pagination"),
("limit" = Option<usize>, Query, description = "Number of taggers to return for pagination")
("skip" = Option<usize>, Query, description = "Number of taggers to skip for pagination. Defaults to `0`"),
("limit" = Option<usize>, Query, description = "Number of taggers to return for pagination. Defaults to `40`")
),
responses(
(status = 200, description = "Post tags", body = TagPost),
(status = 200, description = "Post tags", body = Taggers),
(status = 404, description = "Post not found"),
(status = 500, description = "Internal server error")
)
Expand Down
4 changes: 2 additions & 2 deletions src/routes/v0/post/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ use utoipa::OpenApi;
("author_id" = String, Path, description = "Author Pubky ID"),
("post_id" = String, Path, description = "Post Crockford32 ID"),
("viewer_id" = Option<String>, Query, description = "Viewer Pubky ID"),
("max_tags" = Option<usize>, Query, description = "Upper limit on the number of tags for the post"),
("max_taggers" = Option<usize>, Query, description = "Upper limit on the number of taggers per tag")
("limit_tags" = Option<usize>, Query, description = "Upper limit on the number of tags for the post"),
("limit_taggers" = Option<usize>, Query, description = "Upper limit on the number of taggers per tag")
),
responses(
(status = 200, description = "Post", body = PostView),
Expand Down
1 change: 1 addition & 0 deletions src/routes/v0/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use utoipa::ToSchema;
#[derive(Default, Deserialize, Debug, ToSchema)]
pub struct TagsQuery {
pub limit_tags: Option<usize>,
pub skip_tags: Option<usize>,
pub limit_taggers: Option<usize>,
pub viewer_id: Option<String>,
#[serde(default, deserialize_with = "parse_string_to_u8")]
Expand Down
10 changes: 6 additions & 4 deletions src/routes/v0/user/tags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ use utoipa::OpenApi;
tag = "User",
params(
("user_id" = String, Path, description = "User Pubky ID"),
("limit_tags" = Option<usize>, Query, description = "Upper limit on the number of tags for the user"),
("limit_taggers" = Option<usize>, Query, description = "Upper limit on the number of taggers per tag"),
("skip_tags" = Option<usize>, Query, description = "Skip N tags. **Default** value 0"),
("limit_tags" = Option<usize>, Query, description = "Upper limit on the number of tags for the user. **Default** value 5"),
("limit_taggers" = Option<usize>, Query, description = "Upper limit on the number of taggers per tag. **Default** value 5"),
("viewer_id" = Option<String>, Query, description = "Viewer Pubky ID"),
("depth" = Option<usize>, Query, description = "User trusted network depth, user following users distance. Numbers bigger than 4, will be ignored")
),
Expand All @@ -36,13 +37,14 @@ pub async fn user_tags_handler(
Query(query): Query<TagsQuery>,
) -> Result<Json<Vec<TagDetails>>> {
info!(
"GET {USER_TAGS_ROUTE} user_id:{}, limit_tags:{:?}, limit_taggers:{:?}, viewer_id:{:?}, depth:{:?}",
user_id, query.limit_tags, query.limit_taggers, query.viewer_id, query.depth
"GET {USER_TAGS_ROUTE} user_id:{}, skip_tags:{:?}, limit_tags:{:?}, limit_taggers:{:?}, viewer_id:{:?}, depth:{:?}",
user_id, query.skip_tags, query.limit_tags, query.limit_taggers, query.viewer_id, query.depth
);

match TagUser::get_by_id(
&user_id,
None,
query.skip_tags,
query.limit_tags,
query.limit_taggers,
query.viewer_id.as_deref(),
Expand Down
51 changes: 0 additions & 51 deletions tests/service/all.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ use std::{
};

use anyhow::Result;
use pubky_nexus::models::tag::TagDetails;
use serde_json::json;

const HOST_URL: &str = "http://localhost:8080";
Expand Down Expand Up @@ -104,53 +103,3 @@ async fn test_files_by_ids() -> Result<()> {

Ok(())
}

#[tokio::test]
async fn test_get_post() -> Result<()> {
let client = httpc_test::new_client(HOST_URL)?;

let author_id = "y4euc58gnmxun9wo87gwmanu6kztt9pgw1zz1yp1azp7trrsjamy";
let post_id = "2ZCW1TGR5BKG0";

let res = client
.do_get(&format!(
"/v0/post/{}/{}?viewer_id={}",
author_id, post_id, author_id
))
.await?;
assert_eq!(res.status(), 200);

let body = res.json_body()?;

assert_eq!(body["details"]["content"], "I am told we can reply now!");
assert_eq!(body["details"]["indexed_at"].as_u64(), Some(1718616844478));
assert_eq!(body["details"]["id"], post_id);
assert_eq!(body["details"]["author"], author_id);
assert_eq!(body["details"]["attachments"].as_array().unwrap().len(), 1);
assert_eq!(
(body["details"]["attachments"].as_array().unwrap())[0],
"pubky://y4euc58gnmxun9wo87gwmanu6kztt9pgw1zz1yp1azp7trrsjamy/pub/pubky.app/files/2ZKH7K7M9G3G0".to_string()
);
assert_eq!(
body["details"]["uri"],
"pubky://y4euc58gnmxun9wo87gwmanu6kztt9pgw1zz1yp1azp7trrsjamy/pub/pubky.app/posts/2ZCW1TGR5BKG0"
);
assert_eq!(body["counts"]["tags"].as_u64(), Some(5));
assert_eq!(body["counts"]["replies"].as_u64(), Some(2));
assert_eq!(body["counts"]["reposts"].as_u64(), Some(1));
assert_eq!(body["bookmark"]["indexed_at"].as_u64(), Some(1721764200000));
assert_eq!(body["bookmark"]["id"], "2Z9PFGC3WWWW0");

// Panic if tags vector is bigger that 1
let post_tag_object = body["tags"][0].clone();
let post_tag: TagDetails = serde_json::from_value(post_tag_object.clone())?;
assert_eq!(post_tag.label, "pubky");

// Test non-existing post
let res = client
.do_get(&format!("/v0/post/{}/{}", author_id, "no_post"))
.await?;
assert_eq!(res.status(), 404);

Ok(())
}
1 change: 1 addition & 0 deletions tests/service/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod all;
pub mod endpoints;
pub mod post;
pub mod stream;
pub mod tags;
pub mod user;
Expand Down
7 changes: 7 additions & 0 deletions tests/service/post/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use pubky_nexus::routes::v0::endpoints;

pub mod view;

pub const ROOT_PATH: &str = endpoints::POST_PREFIX;
pub const CAIRO_USER: &str = "f5tcy5gtgzshipr6pag6cn9uski3s8tjare7wd3n7enmyokgjk1o";
pub const ENCRYPTION_TAG: &str = "encryption";
Loading

0 comments on commit 9a53bc3

Please sign in to comment.