caracal 0.4.3

Nostr client for Gemini
use super::route_prelude::*;
use crate::nostru;
use regex::Regex;

#[derive(Template)]
#[template(path = "profile_index.gmi", escape = "txt")]
pub struct MyProfileTemplate {
    url: Url,
    npub: String,
    metadata: Option<Metadata>,
}

#[derive(Template)]
#[template(path = "profile_start.gmi", escape = "txt")]
pub struct ProfileStartTemplate {}

pub async fn profile_init_reset(
    _: RouteContext,
    user: &'static mut CaracalUser,
) -> Response {
    let default_meta = Metadata::new();

    match user.client.set_metadata(&default_meta).await {
        Ok(_event_id) => Response::temporary_redirect("/profile"),
        Err(_error) => Response::temporary_failure("Error"),
    }
}

pub async fn profile_index(
    ctx: RouteContext,
    user: &'static mut CaracalUser,
) -> Response {
    let Ok(pubk) = user.public_key().await else {
        return resp_signer_error();
    };

    let npub = pubk.to_bech32().unwrap();

    match nostru::fetch_user_metadata(&user.client, pubk).await {
        Some(metadata) => {
            Response::success(WindTemplate::render(MyProfileTemplate {
                url: ctx.url,
                npub,
                metadata: Some(metadata),
            }))
        }
        None => {
            Response::success(WindTemplate::render(ProfileStartTemplate {}))
        }
    }
}

pub async fn profile_edit(
    ctx: RouteContext,
    user: &'static mut CaracalUser,
) -> Response {
    let Some(attr) = ctx.parameters.get("attr") else {
        return resp_invalid_params();
    };

    let Some(value) = ctx.url.query() else {
        let input_message = match attr {
            "about" => t!("type_in_your_biography"),
            "name" => t!("type_in_your_name"),
            "website" => t!("type_in_your_website"),
            "misfin" => t!("type_in_your_misfin_address"),
            _ => format!("Type in the value for: {}", attr).into(),
        };

        return Response::input(input_message);
    };

    let value = decode(value).unwrap();

    let Some(mut metadata) = nostru::fetch_user_metadata(
        &user.client,
        user.signer.get_public_key().await.unwrap(),
    )
    .await
    else {
        return resp_invalid_params();
    };

    match attr {
        "about" => {
            metadata.about = Some(value.into());
        }
        "name" => {
            metadata.name = Some(value.into());
        }
        "nip05" => {
            metadata.nip05 = Some(value.into());
        }
        "lud06" => {
            metadata.lud06 = Some(value.into());
        }
        "lud16" => {
            metadata.lud16 = Some(value.into());
        }
        "display_name" => {
            metadata.display_name = Some(value.into());
        }
        "website" => {
            if let Ok(url) = Url::parse(&value) {
                metadata.website = Some(url.to_string());
            }
        }
        "picture" => {
            if let Ok(url) = Url::parse(&value) {
                metadata.picture = Some(url.to_string());
            }
        }
        "banner" => {
            if let Ok(url) = Url::parse(&value) {
                metadata.banner = Some(url.to_string());
            }
        }
        "misfin" => {
            // Change the misfin address
            let pattern = r"^[a-zA-Z0-9.%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$";
            let regex = Regex::new(pattern).unwrap();

            if regex.is_match(&value) {
                // Valid misfin address
                metadata.custom.insert("misfin".to_string(), value.into());
            } else {
                // Invalid misfin address
                return Response::temporary_failure(t!(
                    "invalid_misfin_address"
                ));
            }
        }
        &_ => todo!(),
    }

    user.client.connect().await;

    match user.client.set_metadata(&metadata).await {
        Ok(_event_id) => Response::permanent_redirect("/profile"),
        Err(_error) => Response::permanent_failure("Error updating metadata"),
    }
}

/// Edit some metadata fields via a Titan request
pub async fn profile_titan_edit(
    ctx: RouteContext,
    user: &'static mut CaracalUser,
) -> Response {
    let Some(attr) = ctx.parameters.get("attr") else {
        return resp_invalid_params();
    };

    let Some(mut metadata) = nostru::fetch_user_metadata(
        &user.client,
        user.public_key().await.unwrap(),
    )
    .await
    else {
        return resp_invalid_params();
    };

    match ctx.titan_rsc {
        Some(titan) => {
            match attr {
                "about" => {
                    if let Ok(text) = String::from_utf8(titan.content) {
                        metadata.about = Some(text);
                    }
                }
                &_ => (),
            }

            match user.client.set_metadata(&metadata).await {
                Ok(_) => Response::temporary_redirect(format!(
                    "gemini://{}/profile",
                    ctx.url.authority()
                )),
                Err(_error) => {
                    Response::permanent_failure("Error updating metadata")
                }
            }
        }
        None => Response::temporary_failure("No Titan data"),
    }
}

pub async fn profile_upload(
    ctx: RouteContext,
    user: &'static mut CaracalUser,
) -> Response {
    let Some(obj) = ctx.parameters.get("object") else {
        return resp_invalid_params();
    };

    let Some(rsc) = ctx.titan_rsc else {
        return Response::temporary_failure("No file");
    };

    if let Ok(descriptor) = titan_to_blossom(rsc, user.signer.clone()).await {
        let Some(metadata) = nostru::fetch_user_metadata(
            &user.client,
            user.signer.get_public_key().await.unwrap(),
        )
        .await
        else {
            return resp_invalid_params();
        };

        let metadata = if obj == "picture" {
            metadata.picture(descriptor.url)
        } else if obj == "banner" {
            metadata.banner(descriptor.url)
        } else {
            metadata
        };

        match user.client.set_metadata(&metadata).await {
            Ok(_event_id) => Response::temporary_redirect(format!(
                "gemini://{}/profile",
                ctx.url.authority()
            )),
            Err(_error) => {
                Response::permanent_failure("Error updating metadata")
            }
        }
    } else {
        Response::temporary_failure(t!("blossom_error"))
    }
}