~vpzom/lotide

a180781e02e14910f367bf2c704b84103e84b8c2 — Colin Reeder a month ago 542c56d
Include score and optionally your_vote in every post list (#109)
M openapi/openapi.json => openapi/openapi.json +26 -7
@@ 102,7 102,7 @@
			},
			"PostListPost": {
				"type": "object",
				"required": ["id", "title", "created", "community"],
				"required": ["id", "title", "created", "community", "score"],
				"properties": {
					"id": {"type": "integer"},
					"title": {"type": "string"},


@@ 129,7 129,9 @@
							"host": {"type": "string"},
							"remote_url": {"type": "string", "nullable": true}
						}
					}
					},
					"score": {"type": "integer"},
					"your_vote": {"$ref": "#/components/schemas/YourVote"}
				}
			},
			"PostCommentInfo": {


@@ 687,6 689,12 @@
						"in": "query",
						"required": false,
						"schema": {"$ref": "#/components/schemas/SortType"}
					},
					{
						"name": "include_your",
						"in": "query",
						"required": false,
						"schema": {"type": "boolean"}
					}
				],
				"responses": {


@@ 1024,6 1032,14 @@
		"/api/unstable/posts": {
			"get": {
				"summary": "List posts",
				"parameters": [
					{
						"name": "include_your",
						"in": "query",
						"required": false,
						"schema": {"type": "boolean"}
					}
				],
				"responses": {
					"200": {
						"description": "",


@@ 1105,15 1121,13 @@
								"schema": {
									"allOf": [{"$ref": "#/components/schemas/PostListPost"}],
									"type": "object",
									"required": ["approved", "score", "replies"],
									"required": ["approved", "replies"],
									"properties": {
										"approved": {"type": "boolean"},
										"score": {"type": "integer"},
										"replies": {
											"type": "array",
											"items": {"$ref": "#/components/schemas/PostCommentInfo"}
										},
										"your_vote": {"$ref": "#/components/schemas/YourVote"}
										}
									}
								}
							}


@@ 1479,7 1493,12 @@
								"schema": {
									"type": "array",
									"items": {
										"$ref": "#/components/schemas/PostListPost"
										"allOf": [{"$ref": "#/components/schemas/PostListPost"}],
										"type": "object",
										"required": ["your_vote"],
										"properties": {
											"your_vote": {"$ref": "#/components/schemas/YourVote"}
										}
									}
								}
							}

M src/routes/api/communities.rs => src/routes/api/communities.rs +27 -2
@@ 536,6 536,8 @@ async fn route_unstable_communities_posts_list(
    struct Query {
        #[serde(default = "default_sort")]
        sort: super::SortType,
        #[serde(default)]
        include_your: bool,
    }

    let query: Query = serde_urlencoded::from_str(req.uri().query().unwrap_or(""))?;


@@ 545,6 547,13 @@ async fn route_unstable_communities_posts_list(
    let lang = crate::get_lang_for_req(&req);
    let db = ctx.db_pool.get().await?;

    let include_your_for = if query.include_your {
        let user = crate::require_login(&req, &db).await?;
        Some(user)
    } else {
        None
    };

    let community_row = db
        .query_opt(
            "SELECT name, local, ap_id FROM community WHERE id=$1",


@@ 581,9 590,15 @@ async fn route_unstable_communities_posts_list(

    let limit: i64 = 30; // TODO make configurable

    let values: &[&(dyn tokio_postgres::types::ToSql + Sync)] = &[&community_id, &limit];
    let mut values: Vec<&(dyn tokio_postgres::types::ToSql + Sync)> = vec![&community_id, &limit];
    let sql: &str = &format!(
        "SELECT post.id, post.author, post.href, post.content_text, post.title, post.created, post.content_html, person.username, person.local, person.ap_id, person.avatar FROM post LEFT OUTER JOIN person ON (person.id = post.author) WHERE post.community = $1 AND post.approved=TRUE AND post.deleted=FALSE ORDER BY {} LIMIT $2",
        "SELECT post.id, post.author, post.href, post.content_text, post.title, post.created, post.content_html, person.username, person.local, person.ap_id, person.avatar, (SELECT COUNT(*) FROM post_like WHERE post_like.post = post.id){} FROM post LEFT OUTER JOIN person ON (person.id = post.author) WHERE post.community = $1 AND post.approved=TRUE AND post.deleted=FALSE ORDER BY {} LIMIT $2",
        if let Some(user) = &include_your_for {
            values.push(user);
            ", EXISTS(SELECT 1 FROM post_like WHERE post=post.id AND person=$3)"
        } else {
            ""
        },
        query.sort.post_sort_sql(),
    );



@@ 631,6 646,16 @@ async fn route_unstable_communities_posts_list(
                author: author.as_ref(),
                created: &created.to_rfc3339(),
                community: &community,
                score: row.get(11),
                your_vote: if include_your_for.is_some() {
                    Some(if row.get(12) {
                        Some(crate::Empty {})
                    } else {
                        None
                    })
                } else {
                    None
                },
            };

            futures::future::ready(serde_json::to_value(&post).map_err(Into::into))

M src/routes/api/mod.rs => src/routes/api/mod.rs +14 -0
@@ 102,6 102,9 @@ struct RespPostListPost<'a> {
    author: Option<&'a RespMinimalAuthorInfo<'a>>,
    created: &'a str,
    community: &'a RespMinimalCommunityInfo<'a>,
    score: i64,
    #[serde(skip_serializing_if = "Option::is_none")]
    your_vote: Option<Option<crate::Empty>>,
}

#[derive(Serialize)]


@@ 727,6 730,7 @@ async fn handle_common_posts_list(
    stream: impl futures::stream::TryStream<Ok = tokio_postgres::Row, Error = tokio_postgres::Error>
        + Send,
    ctx: &crate::RouteContext,
    include_your: bool,
) -> Result<Vec<serde_json::Value>, crate::Error> {
    use futures::stream::TryStreamExt;



@@ 787,6 791,16 @@ async fn handle_common_posts_list(
                author: author.as_ref(),
                created: &created.to_rfc3339(),
                community: &community,
                score: row.get(15),
                your_vote: if include_your {
                    Some(if row.get(16) {
                        Some(crate::Empty {})
                    } else {
                        None
                    })
                } else {
                    None
                },
            };

            futures::future::ready(serde_json::to_value(&post).map_err(Into::into))

M src/routes/api/posts.rs => src/routes/api/posts.rs +26 -11
@@ 99,18 99,36 @@ async fn get_post_comments<'a>(
async fn route_unstable_posts_list(
    _: (),
    ctx: Arc<crate::RouteContext>,
    _req: hyper::Request<hyper::Body>,
    req: hyper::Request<hyper::Body>,
) -> Result<hyper::Response<hyper::Body>, crate::Error> {
    let query: MaybeIncludeYour = serde_urlencoded::from_str(req.uri().query().unwrap_or(""))?;

    let db = ctx.db_pool.get().await?;

    let include_your_for = if query.include_your {
        let user = crate::require_login(&req, &db).await?;
        Some(user)
    } else {
        None
    };

    let limit: i64 = 30;

    let stream = db.query_raw(
        "SELECT post.id, post.author, post.href, post.content_text, post.title, post.created, post.content_html, community.id, community.name, community.local, community.ap_id, person.username, person.local, person.ap_id, person.avatar FROM community, post LEFT OUTER JOIN person ON (person.id = post.author) WHERE post.community = community.id AND deleted=FALSE ORDER BY hot_rank((SELECT COUNT(*) FROM post_like WHERE post = post.id AND person != post.author), post.created) DESC LIMIT $1",
        ([limit]).iter().map(|x| x as _),
    ).await?;
    let mut values: Vec<&(dyn tokio_postgres::types::ToSql + Sync)> = vec![&limit];

    let sql: &str = &format!(
        "SELECT post.id, post.author, post.href, post.content_text, post.title, post.created, post.content_html, community.id, community.name, community.local, community.ap_id, person.username, person.local, person.ap_id, person.avatar, (SELECT COUNT(*) FROM post_like WHERE post_like.post = post.id){} FROM community, post LEFT OUTER JOIN person ON (person.id = post.author) WHERE post.community = community.id AND deleted=FALSE ORDER BY hot_rank((SELECT COUNT(*) FROM post_like WHERE post = post.id AND person != post.author), post.created) DESC LIMIT $1",
        if let Some(user) = &include_your_for {
            values.push(user);
            ", EXISTS(SELECT 1 FROM post_like WHERE post=post.id AND person=$2)"
        } else {
            ""
        },
    );

    let stream = crate::query_stream(&db, sql, &values).await?;

    let posts = super::handle_common_posts_list(stream, &ctx).await?;
    let posts = super::handle_common_posts_list(stream, &ctx, include_your_for.is_some()).await?;

    crate::json_response(&posts)
}


@@ 253,10 271,7 @@ async fn route_unstable_posts_get(
        #[serde(flatten)]
        post: &'a RespPostListPost<'a>,
        approved: bool,
        score: i64,
        replies: Vec<RespPostCommentInfo<'a>>,
        #[serde(skip_serializing_if = "Option::is_none")]
        your_vote: Option<Option<crate::Empty>>,
    }

    let (post_id,) = params;


@@ 340,14 355,14 @@ async fn route_unstable_posts_get(
                author: author.as_ref(),
                created: &created.to_rfc3339(),
                community: &community,
                score: row.get(13),
                your_vote,
            };

            let output = RespPostInfo {
                post: &post,
                replies: comments,
                approved: row.get(14),
                score: row.get(13),
                your_vote,
            };

            crate::json_response(&output)

M src/routes/api/users.rs => src/routes/api/users.rs +2 -2
@@ 264,11 264,11 @@ async fn route_unstable_users_following_posts_list(
    let values: &[&(dyn tokio_postgres::types::ToSql + Sync)] = &[&user, &limit];

    let stream = db.query_raw(
        "SELECT post.id, post.author, post.href, post.content_text, post.title, post.created, post.content_html, community.id, community.name, community.local, community.ap_id, person.username, person.local, person.ap_id, person.avatar FROM community, post LEFT OUTER JOIN person ON (person.id = post.author) WHERE post.community = community.id AND post.approved AND post.deleted=FALSE AND community.id IN (SELECT community FROM community_follow WHERE follower=$1 AND accepted) ORDER BY hot_rank((SELECT COUNT(*) FROM post_like WHERE post = post.id AND person != post.author), post.created) DESC LIMIT $2",
        "SELECT post.id, post.author, post.href, post.content_text, post.title, post.created, post.content_html, community.id, community.name, community.local, community.ap_id, person.username, person.local, person.ap_id, person.avatar, (SELECT COUNT(*) FROM post_like WHERE post_like.post = post.id), EXISTS(SELECT 1 FROM post_like WHERE post = post.id AND person = $1) FROM community, post LEFT OUTER JOIN person ON (person.id = post.author) WHERE post.community = community.id AND post.approved AND post.deleted=FALSE AND community.id IN (SELECT community FROM community_follow WHERE follower=$1 AND accepted) ORDER BY hot_rank((SELECT COUNT(*) FROM post_like WHERE post = post.id AND person != post.author), post.created) DESC LIMIT $2",
        values.iter().map(|s| *s as _)
    ).await?;

    let posts = handle_common_posts_list(stream, &ctx).await?;
    let posts = handle_common_posts_list(stream, &ctx, true).await?;

    crate::json_response(&posts)
}