~vpzom/hitide

c27383b441e51c336df10543b5a9ab4c8618075c — Colin Reeder 1 year, 2 months ago 31a5aad
Initial work on administration edit form
5 files changed, 193 insertions(+), 12 deletions(-)

M res/lang/en.ftl
M src/components/mod.rs
M src/main.rs
M src/routes/administration.rs
M src/routes/communities.rs
M res/lang/en.ftl => res/lang/en.ftl +3 -0
@@ 9,7 9,9 @@ action_flag = flag
add = Add
add_by_remote_id = Add by ID:
administration = Administration
administration_edit = Edit Instance Details
administration_signup_allowed = Signups are currently
administration_edit_signup_allowed = Public Signups:
all = All
all_title = The Whole Known Network
allowed_false = not allowed


@@ 101,6 103,7 @@ new_post_poll = Add Poll
no_cancel = No, cancel
no_such_invitation = No such invitation
no_such_local_user = No such local user
not_site_admin = You are not an instance admin
nothing = Looks like there's nothing here.
nothing_yet = Looks like there's nothing here (yet!).
notifications = Notifications

M src/components/mod.rs => src/components/mod.rs +16 -2
@@ 680,13 680,27 @@ pub fn MaybeFillCheckbox<'a, M: GetIndex<&'a str, serde_json::Value>>(
#[render::component]
pub fn MaybeFillOption<'a, M: GetIndex<&'a str, serde_json::Value>, Children: render::Render>(
    values: &'a Option<&'a M>,
    default_value: Option<&'a str>,
    name: &'a str,
    value: &'a str,
    children: Children,
) {
    let selected_value = maybe_fill_value(values, name, None);
    let selected_value = maybe_fill_value(values, name, default_value);

    SelectOption {
        value,
        selected: selected_value == value,
        children,
    }
}

    if selected_value == value {
#[render::component]
pub fn SelectOption<'a, Children: render::Render>(
    value: &'a str,
    selected: bool,
    children: Children,
) {
    if selected {
        render::rsx! {
            <option value={value} selected={""}>{children}</option>
        }

M src/main.rs => src/main.rs +8 -0
@@ 160,6 160,14 @@ pub fn get_lang_for_req(req: &hyper::Request<hyper::Body>) -> Translator {
    get_lang_for_headers(req.headers())
}

pub fn bool_as_str(src: bool) -> &'static str {
    if src {
        "true"
    } else {
        "false"
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    env_logger::init();

M src/routes/administration.rs => src/routes/administration.rs +163 -7
@@ 1,13 1,12 @@
use super::{
    fetch_base_data, for_client, get_cookie_map_for_headers, get_cookie_map_for_req, html_response,
    res_to_error,
    res_to_error, CookieMap,
};
use crate::components::HTPage;
use crate::components::{HTPage, MaybeFillOption};
use crate::lang;
use crate::resp_types::RespInstanceInfo;
use serde_derive::Deserialize;
use std::collections::HashMap;
use std::convert::TryInto;
use std::ops::Deref;
use std::sync::Arc;

async fn page_administration(


@@ 21,6 20,19 @@ async fn page_administration(
    let base_data =
        fetch_base_data(&ctx.backend_host, &ctx.http_client, req.headers(), &cookies).await?;

    let title = lang.tr(&lang::ADMINISTRATION);

    if !base_data.is_site_admin() {
        return Ok(html_response(render::html! {
            <HTPage base_data={&base_data} lang={&lang} title={&title}>
                <h1>{title.as_ref()}</h1>
                <div class={"errorBox"}>
                    {lang.tr(&lang::not_site_admin())}
                </div>
            </HTPage>
        }));
    }

    let api_res = res_to_error(
        ctx.http_client
            .get(


@@ 34,11 46,10 @@ async fn page_administration(
    let api_res = hyper::body::to_bytes(api_res.into_body()).await?;
    let api_res: RespInstanceInfo = serde_json::from_slice(&api_res)?;

    let title = lang.tr(&lang::ADMINISTRATION);

    Ok(html_response(render::html! {
        <HTPage base_data={&base_data} lang={&lang} title={&title}>
            <h1>{title.as_ref()}</h1>
            <a href={"/administration/edit"}>{lang.tr(&lang::administration_edit())}</a>
            <ul>
                <li>
                    {lang.tr(&lang::ADMINISTRATION_SIGNUP_ALLOWED)}{" "}


@@ 53,6 64,151 @@ async fn page_administration(
    }))
}

async fn page_administration_edit(
    _params: (),
    ctx: Arc<crate::RouteContext>,
    req: hyper::Request<hyper::Body>,
) -> Result<hyper::Response<hyper::Body>, crate::Error> {
    let cookies = get_cookie_map_for_req(&req)?;

    page_administration_edit_inner(req.headers(), &cookies, ctx, None, None).await
}

async fn page_administration_edit_inner(
    headers: &hyper::header::HeaderMap,
    cookies: &CookieMap<'_>,
    ctx: Arc<crate::RouteContext>,
    display_error: Option<String>,
    prev_values: Option<&HashMap<&str, serde_json::Value>>,
) -> Result<hyper::Response<hyper::Body>, crate::Error> {
    let lang = crate::get_lang_for_headers(headers);

    let base_data = fetch_base_data(&ctx.backend_host, &ctx.http_client, headers, cookies).await?;

    let title = lang.tr(&lang::ADMINISTRATION_EDIT);

    if !base_data.is_site_admin() {
        return Ok(html_response(render::html! {
            <HTPage base_data={&base_data} lang={&lang} title={&title}>
                <h1>{title.as_ref()}</h1>
                <div class={"errorBox"}>
                    {lang.tr(&lang::not_site_admin())}
                </div>
            </HTPage>
        }));
    }

    let api_res = res_to_error(
        ctx.http_client
            .get(
                format!("{}/api/unstable/instance", ctx.backend_host)
                    .try_into()
                    .unwrap(),
            )
            .await?,
    )
    .await?;
    let api_res = hyper::body::to_bytes(api_res.into_body()).await?;
    let api_res: RespInstanceInfo = serde_json::from_slice(&api_res)?;

    let signup_allowed_value = Some(crate::bool_as_str(api_res.signup_allowed));

    Ok(html_response(render::html! {
        <HTPage base_data={&base_data} lang={&lang} title={&title}>
            <h1>{title.as_ref()}</h1>
            {
                display_error.map(|msg| {
                    render::rsx! {
                        <div class={"errorBox"}>{msg}</div>
                    }
                })
            }
            <form method={"POST"} action={"/administration/edit/submit"}>
                <label>
                    {lang.tr(&lang::administration_edit_signup_allowed())}<br />
                    <select name={"signup_allowed"}>
                        <MaybeFillOption value={"true"} values={&prev_values} default_value={signup_allowed_value} name={"signup_allowed"}>
                            {lang.tr(&lang::allowed_true())}
                        </MaybeFillOption>
                        <MaybeFillOption value={"false"} values={&prev_values} default_value={signup_allowed_value} name={"signup_allowed"}>
                            {lang.tr(&lang::allowed_false())}
                        </MaybeFillOption>
                    </select>
                </label>
                <br />
                <br />
                <button type={"submit"}>{"Save"}</button>
            </form>
        </HTPage>
    }))
}

async fn handler_administration_edit_submit(
    _params: (),
    ctx: Arc<crate::RouteContext>,
    req: hyper::Request<hyper::Body>,
) -> Result<hyper::Response<hyper::Body>, crate::Error> {
    let (req_parts, body) = req.into_parts();

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

    let body = hyper::body::to_bytes(body).await?;
    let mut body: HashMap<&str, serde_json::Value> = serde_urlencoded::from_bytes(&body)?;

    body.insert(
        "signup_allowed",
        body.get("signup_allowed")
            .and_then(|x| x.as_str())
            .ok_or(crate::Error::InternalStrStatic(
                "Failed to extract signup_allowed in administration edit",
            ))?
            .parse()?,
    );

    let api_res = res_to_error(
        ctx.http_client
            .request(for_client(
                hyper::Request::patch(format!("{}/api/unstable/instance", ctx.backend_host,))
                    .body(serde_json::to_vec(&body)?.into())?,
                &req_parts.headers,
                &cookies,
            )?)
            .await?,
    )
    .await;

    match api_res {
        Err(crate::Error::RemoteError((_, message))) => {
            page_administration_edit_inner(
                &req_parts.headers,
                &cookies,
                ctx,
                Some(message),
                Some(&body),
            )
            .await
        }
        Err(other) => Err(other),
        Ok(_) => Ok(hyper::Response::builder()
            .status(hyper::StatusCode::SEE_OTHER)
            .header(hyper::header::LOCATION, "/administration")
            .body("Successfully edited.".into())?),
    }
}

pub fn route_administration() -> crate::RouteNode<()> {
    crate::RouteNode::new().with_handler_async(hyper::Method::GET, page_administration)
    crate::RouteNode::new()
        .with_handler_async(hyper::Method::GET, page_administration)
        .with_child(
            "edit",
            crate::RouteNode::new()
                .with_handler_async(hyper::Method::GET, page_administration_edit)
                .with_child(
                    "submit",
                    crate::RouteNode::new().with_handler_async(
                        hyper::Method::POST,
                        handler_administration_edit_submit,
                    ),
                ),
        )
}

M src/routes/communities.rs => src/routes/communities.rs +3 -3
@@ 1371,9 1371,9 @@ async fn page_community_new_post_inner(
                        {" "}
                        <input type={"number"} name={"poll_duration_value"} required={""} value={maybe_fill_value(&prev_values, "poll_duration_value", Some("10"))} />
                        <select name={"poll_duration_unit"}>
                            <MaybeFillOption values={&prev_values} name={"poll_duration_unit"} value={"m"}>{lang.tr(&lang::time_input_minutes())}</MaybeFillOption>
                            <MaybeFillOption values={&prev_values} name={"poll_duration_unit"} value={"h"}>{lang.tr(&lang::time_input_hours())}</MaybeFillOption>
                            <MaybeFillOption values={&prev_values} name={"poll_duration_unit"} value={"d"}>{lang.tr(&lang::time_input_days())}</MaybeFillOption>
                            <MaybeFillOption default_value={None} values={&prev_values} name={"poll_duration_unit"} value={"m"}>{lang.tr(&lang::time_input_minutes())}</MaybeFillOption>
                            <MaybeFillOption default_value={None} values={&prev_values} name={"poll_duration_unit"} value={"h"}>{lang.tr(&lang::time_input_hours())}</MaybeFillOption>
                            <MaybeFillOption default_value={None} values={&prev_values} name={"poll_duration_unit"} value={"d"}>{lang.tr(&lang::time_input_days())}</MaybeFillOption>
                        </select>
                    </div>
                </div>