~vpzom/hitide

5a0cd2b9310417536603ad8f051d9fa8b3e65e69 — Colin Reeder a month ago 464ab3c
Add UI for adding community moderators
2 files changed, 151 insertions(+), 2 deletions(-)

M res/lang/en.ftl
M src/routes/communities.rs
M res/lang/en.ftl => res/lang/en.ftl +4 -0
@@ 5,6 5,7 @@ about_text1 = lotide is an attempt to build a federated forum. Users can create 
about_text2 = For more information or to view the source code, check out the
about_sourcehut = SourceHut page
about_versions = This instance is running hitide { $hitide_version } on { $backend_name } { $backend_version }.
add = Add
add_by_remote_id = Add by ID:
all = All
all_title = The Whole Known Network


@@ 15,6 16,7 @@ comment_delete_title = Delete Comment
comment_delete_question = Delete this comment?
comment_submit = Post Comment
communities = Communities
community_add_moderator = Add Moderator
community_create = Create Community
community_create_submit = Create
community_edit = Customize Community


@@ 37,6 39,7 @@ login = Login
login_signup_link = create a new account
lookup_nothing = Nothing found.
lookup_title = Lookup
moderators = Moderators
name_prompt = Name:
no_cancel = No, cancel
nothing = Looks like there's nothing here.


@@ 116,6 119,7 @@ user_edit_description_prompt = Profile Description:
user_edit_not_you = You can only edit your own profile.
user_edit_submit = Save
user_edit_title = Edit Profile
user_id_prompt = User ID:
user_remote_note = This is a remote user, information on this page may be incomplete.
username_prompt = Username:
view_at_source = View at Source

M src/routes/communities.rs => src/routes/communities.rs +147 -2
@@ 1,7 1,7 @@
use crate::components::{CommunityLink, HTPage, MaybeFillInput, MaybeFillTextArea, PostItem};
use crate::resp_types::{
    JustContentHTML, RespCommunityInfoMaybeYour, RespMinimalCommunityInfo, RespPostListPost,
    RespYourFollow,
    JustContentHTML, RespCommunityInfoMaybeYour, RespMinimalAuthorInfo, RespMinimalCommunityInfo,
    RespPostListPost, RespYourFollow,
};
use crate::routes::{
    fetch_base_data, for_client, get_cookie_map_for_headers, get_cookie_map_for_req, html_response,


@@ 229,6 229,7 @@ async fn page_community(
                    }
                }
                <p>{community_info.description.as_ref()}</p>
                <p><a href={format!("/communities/{}/moderators", community_id)}>{lang.tr("moderators", None)}</a></p>
            </div>
            <div class={"sortOptions"}>
                <span>{lang.tr("sort", None)}</span>


@@ 415,6 416,140 @@ async fn handler_community_follow(
        .body("Successfully followed".into())?)
}

async fn page_community_moderators(
    params: (i64,),
    ctx: Arc<crate::RouteContext>,
    req: hyper::Request<hyper::Body>,
) -> Result<hyper::Response<hyper::Body>, crate::Error> {
    let (community_id,) = params;

    let lang = crate::get_lang_for_req(&req);
    let cookies = get_cookie_map_for_req(&req)?;
    let base_data =
        fetch_base_data(&ctx.backend_host, &ctx.http_client, req.headers(), &cookies).await?;

    let community_info_api_res = res_to_error(
        ctx.http_client
            .request(for_client(
                hyper::Request::get(format!(
                    "{}/api/unstable/communities/{}{}",
                    ctx.backend_host,
                    community_id,
                    if base_data.login.is_some() {
                        "?include_your=true"
                    } else {
                        ""
                    },
                ))
                .body(Default::default())?,
                req.headers(),
                &cookies,
            )?)
            .await?,
    )
    .await?;
    let community_info_api_res = hyper::body::to_bytes(community_info_api_res.into_body()).await?;
    let community_info: RespCommunityInfoMaybeYour =
        { serde_json::from_slice(&community_info_api_res)? };

    let api_res = res_to_error(
        ctx.http_client
            .request(for_client(
                hyper::Request::get(format!(
                    "{}/api/unstable/communities/{}/moderators",
                    ctx.backend_host, community_id,
                ))
                .body(Default::default())?,
                req.headers(),
                &cookies,
            )?)
            .await?,
    )
    .await?;
    let api_res = hyper::body::to_bytes(api_res.into_body()).await?;
    let api_res: Vec<RespMinimalAuthorInfo> = serde_json::from_slice(&api_res)?;

    let title = lang.tr("moderators", None);

    Ok(html_response(render::html! {
        <HTPage base_data={&base_data} lang={&lang} title={&title}>
            <h1>{title.as_ref()}</h1>
            <ul>
                {
                    api_res.iter().map(|user| {
                        render::rsx! {
                            <li><a href={format!("/users/{}", user.id)}>{user.username.as_ref()}</a></li>
                        }
                    })
                    .collect::<Vec<_>>()
                }
            </ul>
            {
                if community_info.you_are_moderator == Some(true) {
                    Some(render::rsx! {
                        <div>
                            <h2>{lang.tr("community_add_moderator", None)}</h2>
                            <form method={"POST"} action={format!("/communities/{}/moderators/add", community_id)}>
                                <label>
                                    {lang.tr("user_id_prompt", None)}{" "}
                                    <input type={"number"} name={"user"} />
                                </label>
                                {" "}
                                <button type={"submit"}>{lang.tr("add", None)}</button>
                            </form>
                        </div>
                    })
                } else {
                    None
                }
            }
        </HTPage>
    }))
}

async fn handler_community_moderators_add(
    params: (i64,),
    ctx: Arc<crate::RouteContext>,
    req: hyper::Request<hyper::Body>,
) -> Result<hyper::Response<hyper::Body>, crate::Error> {
    let (community_id,) = params;

    let (req_parts, body) = req.into_parts();

    let cookies = get_cookie_map_for_headers(&req_parts.headers)?;

    #[derive(Deserialize)]
    struct ModeratorsAddParams {
        user: i64,
    }

    let body = hyper::body::to_bytes(body).await?;
    let body: ModeratorsAddParams = serde_urlencoded::from_bytes(&body)?;

    res_to_error(
        ctx.http_client
            .request(for_client(
                hyper::Request::put(format!(
                    "{}/api/unstable/communities/{}/moderators/{}",
                    ctx.backend_host, community_id, body.user,
                ))
                .body(Default::default())?,
                &req_parts.headers,
                &cookies,
            )?)
            .await?,
    )
    .await?;

    Ok(hyper::Response::builder()
        .status(hyper::StatusCode::SEE_OTHER)
        .header(
            hyper::header::LOCATION,
            format!("/communities/{}/moderators", community_id),
        )
        .body("Successfully added.".into())?)
}

async fn handler_community_post_approve(
    params: (i64, i64),
    ctx: Arc<crate::RouteContext>,


@@ 726,6 861,16 @@ pub fn route_communities() -> crate::RouteNode<()> {
                    crate::RouteNode::new().with_handler_async("POST", handler_community_follow),
                )
                .with_child(
                    "moderators",
                    crate::RouteNode::new()
                        .with_handler_async("GET", page_community_moderators)
                        .with_child(
                            "add",
                            crate::RouteNode::new()
                                .with_handler_async("POST", handler_community_moderators_add),
                        ),
                )
                .with_child(
                    "posts",
                    crate::RouteNode::new().with_child_parse::<i64, _>(
                        crate::RouteNode::new()