caracal 0.4.3

Nostr client for Gemini
use crate::gemtext2md::gemtext2md;
use crate::routes::route_prelude::*;
use crate::storage::LongFormNoteDraft;

#[derive(Template)]
#[template(path = "notes/longform/draft_edit.gmi", escape = "txt")]
pub struct LfNoteDraftEditTemplate<'c> {
    url: Url,
    draft: LongFormNoteDraft<'c>,
    draft_id: String,
    draft_body: String,
}

pub async fn draft_longform_new(
    ctx: RouteContext,
    _user: &'static mut CaracalUser,
) -> Response {
    match decode_query(&ctx) {
        Some(id) => {
            if !id.is_empty() {
                Response::temporary_redirect(format!(
                    "/draft/longform/new/{}",
                    id
                ))
            } else {
                Response::input(t!("longform_note_input_id"))
            }
        }
        None => Response::input(t!("longform_note_input_id")),
    }
}

pub async fn draft_longform_new_with_id(
    ctx: RouteContext,
    user: &'static mut CaracalUser,
) -> Response {
    let Some(id) = ctx.parameters.get("id") else {
        return Response::input(t!("longform_note_input_id"));
    };

    let Ok(_) = user.storage.lf_draft_new(id) else {
        return resp_error_generic();
    };

    Response::temporary_redirect(format!("/draft/longform/{}/edit", id))
}

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

    let Ok(mut rtxn) = user.storage.get_read_txn() else {
        return resp_error_generic();
    };

    let Ok(draft) = user.storage.lf_draft_get(&draft_id, &mut rtxn) else {
        return resp_error_generic();
    };

    let Ok(draft_body) = user.storage.lf_draft_get_content(&draft_id) else {
        return resp_error_generic();
    };

    let template = LfNoteDraftEditTemplate {
        url: ctx.url,
        draft,
        draft_id: draft_id.to_string(),
        draft_body,
    };

    Response::success(WindTemplate::render(template))
}

pub async fn draft_longform_body_append(
    ctx: RouteContext,
    user: &'static mut CaracalUser,
) -> Response {
    let Some(text) = decode_query(&ctx) else {
        return Response::input("Gemtext ?");
    };

    let Some(draft_id) = ctx.draft_id() else {
        return resp_invalid_params();
    };

    match user.storage.lf_draft_append_gemtext(&draft_id, &text) {
        Ok(_) => Response::temporary_redirect(format!(
            "gemini://{}/draft/longform/{}/edit",
            ctx.url.authority(),
            draft_id
        )),
        Err(_) => Response::temporary_failure("Draft store failure"),
    }
}

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

    match user.storage.lf_draft_delete_last(&draft_id) {
        Ok(_) => Response::temporary_redirect(format!(
            "/draft/longform/{}/edit",
            draft_id
        )),
        Err(_) => Response::temporary_failure("Draft store failure"),
    }
}

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

    let Ok(content) = user.storage.lf_draft_get_content(draft_id) else {
        return resp_error_generic();
    };

    Response::success(content)
}

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

    let Some(mut segments) = ctx.url.path_segments() else {
        return resp_invalid_params();
    };

    let Ok(mut rtxn) = user.storage.get_read_txn() else {
        return resp_error_generic();
    };

    let Ok(mut draft) = user.storage.lf_draft_get(&draft_id, &mut rtxn) else {
        return resp_error_generic();
    };

    let last_segment = segments.next_back();

    if let Some("title") = last_segment {
        let Some(query) = ctx.url.query() else {
            return Response::input("Title ?");
        };

        draft.title = Some(query);
    }

    if let Some("summary") = last_segment {
        let Some(query) = ctx.url.query() else {
            return Response::input("Summary ?");
        };

        draft.summary = Some(query);
    }

    match user.storage.lf_draft_store(&draft_id, draft) {
        Ok(_) => Response::temporary_redirect(format!(
            "/draft/longform/{}/edit",
            draft_id
        )),
        Err(_) => Response::temporary_failure("Failed to store draft"),
    }
}

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

    let Ok(mut rtxn) = user.storage.get_read_txn() else {
        return resp_invalid_params();
    };

    let Ok(draft) = user.storage.lf_draft_get(&draft_id, &mut rtxn) else {
        return resp_error_generic();
    };

    let Ok(content) = user.storage.lf_draft_get_content(&draft_id) else {
        return resp_error_generic();
    };

    // gemtext to markdown conversion
    let body = gemtext2md(content);

    let mut tags = Vec::new();

    tags.push(Tag::identifier(draft_id));
    tags.push(Tag::from_standardized(TagStandard::PublishedAt(
        Timestamp::now(),
    )));

    if let Some(title) = draft.title {
        tags.push(Tag::from_standardized(TagStandard::Title(
            title.to_string(),
        )));
    }

    if let Some(summary) = draft.summary {
        tags.push(Tag::from_standardized(TagStandard::Summary(
            summary.to_string(),
        )));
    }

    let builder = EventBuilder::long_form_text_note(&body).tags(tags);

    match user.send_builder(builder).await {
        Ok(output) => Response::temporary_redirect(format!(
            "/note/{}/thread",
            output.id()
        )),
        Err(_) => resp_error_send_note(),
    }
}