caracal 0.2.0

Nostr client for Gemini
#![allow(dead_code)]

mod aska;
mod certgen;
mod events;
mod hookstr;
mod keyutil;
mod nips;
mod nostru;
mod rendering;
mod routes;
pub mod storage;
mod ts;
mod urls;
mod user;
mod util;

use clap::Arg;
use qpidfile::Pidfile;
use standard_paths::{LocationType, StandardPaths};
use std::path::PathBuf;

use routes::guardian;

use hookstr::Hookstr;

use windmark_titanesque::response::Response;

use nostr_sdk::prelude::*;

rust_i18n::i18n!("locales");

pub fn parse_args() -> clap::ArgMatches {
    clap::Command::new(env!("CARGO_PKG_NAME"))
        .version(env!("CARGO_PKG_VERSION"))
        .arg(
            Arg::new("port")
                .default_value("1965")
                .value_parser(clap::value_parser!(i32))
                .short('p')
                .help("Gemini service port number"),
        )
        .arg(
            Arg::new("pid-file")
                .default_value("caracal.pid")
                .long("pid-file")
                .help("Process id (pid) file path"),
        )
        .get_matches()
}

macro_rules! mkroute {
    ($handler:expr) => {
        move |ctx| guardian(ctx, $handler)
    };
}

#[windmark_titanesque::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let sp = StandardPaths::without_org(env!("CARGO_PKG_NAME"));

    let matches = parse_args();
    let port = matches.get_one::<i32>("port").copied().unwrap();
    let pid_file_path =
        PathBuf::from(matches.get_one::<String>("pid-file").unwrap());

    let _pidfile = Pidfile::new(pid_file_path);

    let app_data_path = sp
        .writable_location(LocationType::AppLocalDataLocation)
        .expect("Cannot find a writable location for data files");

    let gemini_data_path = app_data_path.join("gemini");

    std::fs::create_dir_all(gemini_data_path.clone())?;

    let cert_path = gemini_data_path.join("cert.pem");
    let key_path = gemini_data_path.join("key.pem");

    if !cert_path.exists() || !key_path.exists() {
        certgen::gen_cert(
            vec!["localhost".to_string()],
            &cert_path,
            &key_path,
        )?;
    }

    windmark_titanesque::router::Router::new()
        .set_port(port)
        .set_private_key_file(key_path.display().to_string())
        .set_certificate_file(cert_path.display().to_string())
        .attach_async(Hookstr::default())
        .mount("/", mkroute!(routes::index))
        .mount("/contacts", mkroute!(routes::contact_list_index))
        .mount("/connect", routes::nostr_connect_index)
        .mount("/connect/bunker", routes::nostr_connect_change)
        .mount("/search/notes", routes::search_content)
        .mount("/search/notes/:searchq", routes::search_content)
        .mount("/search/notes/:searchq/:page", routes::search_content)
        .mount("/feed", mkroute!(routes::feed))
        .mount("/feed/:limit", mkroute!(routes::feed))
        .mount("/follow", routes::follow)
        .mount("/follow/:npub", routes::follow)
        .mount("/hashtags", mkroute!(routes::hashtags_index))
        .mount("/hashtags/:tag/list", mkroute!(routes::notes_by_hashtag))
        .mount("/hashtags/:tag/notes", mkroute!(routes::notes_by_hashtag))
        .mount("/post", mkroute!(routes::post_note))
        .mount("/post/pow/:pow", mkroute!(routes::post_note))
        .mount("/profile", routes::profile_index)
        .mount("/profile/upload/picture", routes::profile_upload)
        .mount("/profile/upload/banner", routes::profile_upload)
        .mount("/profile/edit/:attr", routes::profile_edit)
        .mount("/profile/reset", routes::profile_init_reset)
        .mount("/profile/init", routes::profile_init_reset)
        .mount("/p/:npub", mkroute!(routes::person_profile))
        .mount("/p/:npub/follow", mkroute!(routes::person_follow))
        .mount("/p/:npub/unfollow", mkroute!(routes::person_unfollow))
        .mount("/p/:npub/send_dm", routes::person_send_dm)
        .mount("/p/:npub/conversation", routes::person_dm_conversation)
        .mount("/note/:event_id/thread", mkroute!(routes::note_thread))
        .mount("/note/:event_id/reply", mkroute!(routes::note_reply))
        .mount("/note/:event_id/repost", mkroute!(routes::note_repost))
        .mount("/note/:event_id/react", mkroute!(routes::note_react))
        .mount("/draft/new", mkroute!(routes::draft_new))
        .mount("/draft/:draft_id/edit", mkroute!(routes::draft_edit))
        .mount("/draft/:draft_id/post", mkroute!(routes::draft_post))
        .mount("/inbox", mkroute!(routes::inbox))
        .mount("/nsite/ls", mkroute!(routes::nsites_index))
        .mount(
            "/draft/:draft_id/upload_file",
            mkroute!(routes::draft_upload_file),
        )
        .mount("/polls", mkroute!(routes::polls_list))
        .mount(
            "/polls/:event_id/respond/:option_id",
            mkroute!(routes::poll_respond),
        )
        .mount("/polls/:event_id", mkroute!(routes::poll_details))
        .add_footer(|_context: windmark_titanesque::context::RouteContext| {
            format!("caracal version {}", env!("CARGO_PKG_VERSION"))
        })
        .set_error_handler(|_| Response::permanent_failure("Error!"))
        .run()
        .await
}