caracal 0.4.3

Nostr client for Gemini
use super::route_prelude::*;
use std::collections::HashMap;

#[derive(Template, Clone)]
#[template(path = "bookmarks/public.gmi", escape = "txt")]
struct PublicBookmarksListTemplate {
    bookmarks: Bookmarks,
    events: HashMap<EventId, Event>,
}

pub fn event_to_bookmarks(event: Event) -> Bookmarks {
    let mut bookmarks = Bookmarks::default();
    bookmarks.event_ids.extend(event.tags.event_ids());
    bookmarks
        .hashtags
        .extend(event.tags.hashtags().map(str::to_string));
    bookmarks
        .coordinate
        .extend(event.tags.coordinates().map(|c| c.to_owned()));
    bookmarks
}

/// Add an [`EventId`] or a hashtag to the public [`Bookmarks`]
pub async fn bookmark_public_add(
    ctx: RouteContext,
    user: &'static mut CaracalUser,
) -> Response {
    let Ok(public_key) = user.public_key().await else {
        return resp_signer_error();
    };

    let Ok(events) = user
        .fetch_quick(
            Filter::new()
                .kind(Kind::Bookmarks)
                .author(public_key)
                .limit(1),
        )
        .await
    else {
        return resp_fetch_events_error();
    };

    let mut bookmarks = match events.first_owned() {
        Some(event) => event_to_bookmarks(event),
        None => {
            // No bookmarks found
            Bookmarks::default()
        }
    };

    if let Some(event_id) = ctx.event_id() {
        // Add the event ID of the note in the bookmarks
        bookmarks.event_ids.push(event_id);
    } else if let Some(hashtag) = ctx.hashtag() {
        // Add a hashtag
        bookmarks.hashtags.push(hashtag);
    }

    match user.send_builder(EventBuilder::bookmarks(bookmarks)).await {
        Ok(_) => Response::temporary_redirect("/bookmarks/public"),
        Err(err) => Response::temporary_failure(format!("{err}")),
    }
}

/// Show public bookmarks
pub async fn public_bookmarks(
    _ctx: RouteContext,
    user: &'static mut CaracalUser,
) -> Response {
    let Ok(public_key) = user.public_key().await else {
        return resp_signer_error();
    };

    let Ok(events) = user
        .fetch_quick(
            Filter::new()
                .kind(Kind::Bookmarks)
                .author(public_key)
                .limit(1),
        )
        .await
    else {
        return resp_fetch_events_error();
    };

    let bookmarks = if let Some(event) = events.first_owned() {
        event_to_bookmarks(event)
    } else {
        Bookmarks::default()
    };

    let Ok(bm_events) = user
        .fetch_quick(Filter::new().ids(bookmarks.event_ids.clone()))
        .await
    else {
        return resp_fetch_events_error();
    };

    let mut events: HashMap<EventId, Event> = HashMap::new();
    for event in bm_events {
        events.insert(event.id, event);
    }

    Response::success(WindTemplate::render(PublicBookmarksListTemplate {
        bookmarks,
        events,
    }))
}

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

    let Ok(public_key) = user.public_key().await else {
        return resp_signer_error();
    };

    let Ok(Some(event)) = user
        .fetch_quick(
            Filter::new()
                .kind(Kind::Bookmarks)
                .author(public_key)
                .limit(1),
        )
        .await
        .map(|events| events.first_owned())
    else {
        return resp_fetch_events_error();
    };

    let mut bookmarks = event_to_bookmarks(event);

    if bookmarks.event_ids.contains(&event_id) {
        bookmarks.event_ids.retain(|id| *id != event_id);
    }

    match user.send_builder(EventBuilder::bookmarks(bookmarks)).await {
        Ok(_) => Response::temporary_redirect("/bookmarks/public"),
        Err(err) => Response::temporary_failure(format!("{err}")),
    }
}