~vpzom/lotide

3419324fd722e1edaae572a396816fd63d6bcf82 — Colin Reeder 10 months ago 3856bd4
Include more fields in community list (#128)
3 files changed, 81 insertions(+), 37 deletions(-)

M openapi/openapi.json
M src/main.rs
M src/routes/api/communities.rs
M openapi/openapi.json => openapi/openapi.json +33 -20
@@ 100,6 100,26 @@
					}
				}
			},
			"CommunityInfo": {
				"allOf": [{"$ref": "#/components/schemas/MinimalCommunityInfo"}],
				"type": "object",
				"required": ["description"],
				"properties": {
					"description": {"type": "string"},
					"you_are_moderator": {"type": "boolean"},
					"your_follow": {
						"type": "object",
						"nullable": true,
						"required": ["accepted"],
						"properties": {
							"accepted": {
								"type": "boolean",
								"description": "Whether your follow request has been accepted by the community."
							}
						}
					}
				}
			},
			"SomePostInfo": {
				"type": "object",
				"required": ["id", "title", "created", "community", "score"],


@@ 445,6 465,15 @@
		"/api/unstable/communities": {
			"get": {
				"summary": "List communities",
				"parameters": [
					{
						"name": "include_your",
						"in": "query",
						"required": false,
						"schema": {"type": "boolean"},
						"description": "If true, will include `you_are_moderator` and `your_follow` in list. Requires login."
					}
				],
				"responses": {
					"200": {
						"description": "",


@@ 453,13 482,14 @@
								"schema": {
									"type": "array",
									"items": {
										"$ref": "#/components/schemas/MinimalCommunityInfo"
										"$ref": "#/components/schemas/CommunityInfo"
									}
								}
							}
						}
					}
				}
				},
				"security": [{"bearer": []}]
			},
			"post": {
				"summary": "Create a community",


@@ 525,24 555,7 @@
						"content": {
							"application/json": {
								"schema": {
									"allOf": [{"$ref": "#/components/schemas/MinimalCommunityInfo"}],
									"type": "object",
									"required": ["description"],
									"properties": {
										"description": {"type": "string"},
										"you_are_moderator": {"type": "boolean"},
										"your_follow": {
											"type": "object",
											"nullable": true,
											"required": ["accepted"],
											"properties": {
												"accepted": {
													"type": "boolean",
													"description": "Whether your follow request has been accepted by the community."
												}
											}
										}
									}
									"$ref": "#/components/schemas/CommunityInfo"
								}
							}
						}

M src/main.rs => src/main.rs +3 -1
@@ 87,6 87,8 @@ impl Into<activitystreams::primitives::OneOrMany<activitystreams::base::AnyBase>
    }
}

pub type ParamSlice<'a> = &'a [&'a (dyn tokio_postgres::types::ToSql + Sync)];

#[derive(Serialize, Default)]
pub struct Empty {}



@@ 450,7 452,7 @@ pub fn get_path_and_query(url: &url::Url) -> Result<String, url::ParseError> {
pub async fn query_stream(
    db: &tokio_postgres::Client,
    statement: &(impl tokio_postgres::ToStatement + ?Sized),
    params: &[&(dyn tokio_postgres::types::ToSql + Sync)],
    params: ParamSlice<'_>,
) -> Result<tokio_postgres::RowStream, tokio_postgres::Error> {
    let params = params.iter().map(|s| *s as _);


M src/routes/api/communities.rs => src/routes/api/communities.rs +45 -16
@@ 10,7 10,7 @@ use std::sync::Arc;
#[derive(Serialize)]
struct RespCommunityInfo<'a> {
    #[serde(flatten)]
    base: &'a RespMinimalCommunityInfo<'a>,
    base: RespMinimalCommunityInfo<'a>,

    description: &'a str,



@@ 28,30 28,59 @@ struct RespYourFollowInfo {
async fn route_unstable_communities_list(
    _: (),
    ctx: Arc<crate::RouteContext>,
    _req: hyper::Request<hyper::Body>,
    req: hyper::Request<hyper::Body>,
) -> Result<hyper::Response<hyper::Body>, crate::Error> {
    let db = ctx.db_pool.get().await?;
    let query: MaybeIncludeYour = serde_urlencoded::from_str(req.uri().query().unwrap_or(""))?;

    let rows = db
        .query("SELECT id, local, ap_id, name FROM community", &[])
        .await?;
    let db = ctx.db_pool.get().await?;
    let rows = if query.include_your {
        let user = crate::require_login(&req, &db).await?;
        db.query(
            "SELECT id, name, local, ap_id, description, (SELECT accepted FROM community_follow WHERE community=community.id AND follower=$1), EXISTS(SELECT 1 FROM community_moderator WHERE community=community.id AND person=$1) FROM community",
            &[&user.raw()],
        ).await?
    } else {
        db.query(
            "SELECT id, name, local, ap_id, description FROM community",
            &[],
        )
        .await?
    };

    let output: Vec<_> = rows
        .iter()
        .map(|row| {
            let id = CommunityLocalID(row.get(0));
            let local = row.get(1);
            let ap_id = row.get(2);
            let name = row.get(3);
            let name = row.get(1);
            let local = row.get(2);
            let ap_id = row.get(3);
            let description = row.get(4);

            let host = crate::get_actor_host_or_unknown(local, ap_id, &ctx.local_hostname);

            RespMinimalCommunityInfo {
                id,
                name,
                local,
                host,
                remote_url: ap_id,
            RespCommunityInfo {
                base: RespMinimalCommunityInfo {
                    id,
                    name,
                    local,
                    host,
                    remote_url: ap_id,
                },

                description,
                you_are_moderator: if query.include_your {
                    Some(row.get(6))
                } else {
                    None
                },
                your_follow: if query.include_your {
                    Some(match row.get(5) {
                        Some(accepted) => Some(RespYourFollowInfo { accepted }),
                        None => None,
                    })
                } else {
                    None
                },
            }
        })
        .collect();


@@ 173,7 202,7 @@ async fn route_unstable_communities_get(
    let community_ap_id: Option<&str> = row.get(2);

    let info = RespCommunityInfo {
        base: &RespMinimalCommunityInfo {
        base: RespMinimalCommunityInfo {
            id: community_id,
            name: row.get(0),
            local: community_local,