aboutsummaryrefslogblamecommitdiffstats
path: root/src/main.rs
blob: 5ee3b2a39aa19848f1aaa728529d58edb050cc56 (plain) (tree)
1
2
3
4
5
6
7
8
9


                             

         
          
         
           
          

              


              


                                                




                                    
                        


                               
                                   
                          
 
                        
                       
                                      
 

                            
                        
                       
 
                                                      
 


                     
 


                                                                         
                                                       

                                                         
                                                      
                                
      
                                                  
                                                        
                                                  









                                                                                
                                                           



                                    

 


                      

 


                                                                                      
                                                        
                                                             



                                                             


                                                                            
     




                                    

 

                                     
                                                







                                                       
                                                                      



                                             

 


                                          

 


                                    

 













                                                                        







                                                              
              
                                                            


                                                                                                    

                                                             
 



                                             

                                           



                                                                 
                                      
                      

                                      


                                                        
           
 


                                                  
 
#![feature(path_file_prefix)]

mod article;
mod atom;
mod blog;
mod error;
mod live;
mod poetry;
mod posts;
mod scrobbles;
mod skweets;
mod templates;
mod utils;

use std::{collections::HashSet, time::Duration};

use poem::http::StatusCode;
use poem::{
    endpoint::EmbeddedFilesEndpoint,
    get, handler,
    listener::TcpListener,
    middleware::AddData,
    middleware::Tracing,
    web::{Data, Path, Query},
    EndpointExt, Route, Server,
};
use poem::{IntoResponse, Response};
use rust_embed::RustEmbed;

use error::BlossomError;
use serde::Deserialize;
use tracing_subscriber::FmtSubscriber;

use crate::article::Article;
use crate::blog::Blogpost;
use crate::poetry::Poem;
use crate::posts::Post;

type Result<T> = std::result::Result<T, BlossomError>;

#[derive(RustEmbed)]
#[folder = "static/"]
struct Static;

#[handler]
async fn home(Data(reqwest): Data<&reqwest::Client>) -> templates::Home {
    let listenbrainz_client = listenbrainz::raw::Client::new();
    let (live, listenbrainz, blogposts) = tokio::join!(
        live::get_live_status(reqwest),
        scrobbles::get_now_playing(&listenbrainz_client),
        // skweets::get_recents(&clients.skinnyverse),
        Blogpost::get_articles()
    );
    let is_live = live.unwrap_or_default().online;
    let listenbrainz = listenbrainz.unwrap_or_default();
    let blogposts = blogposts.unwrap_or_default();
    templates::Home {
        is_live,
        listenbrainz,
        blogposts,
    }
}

// #[get("/blog/<blogpost>")]
#[handler]
async fn blogpost(Path(blogpost): Path<String>) -> Result<templates::Blogpost> {
    let blogpost = Blogpost::get_article(&blogpost).await?;
    Ok(templates::Blogpost {
        blogpost,
        filter_tags: HashSet::new(),
    })
}

#[derive(Deserialize)]
struct FilterTags {
    filter: String,
}

// #[get("/blog?<filter>")]
#[handler]
async fn get_blog(filter_tags: Option<Query<FilterTags>>) -> Result<templates::Blog> {
    let mut blogposts = Blogpost::get_articles().await?;
    let tags: Vec<String> = posts::Post::get_tags(&blogposts)
        .into_iter()
        .map(|tag| tag.to_owned())
        .collect();
    let mut filter_hashset: HashSet<String> = HashSet::new();
    if let Some(Query(FilterTags { filter })) = filter_tags {
        filter_hashset.insert(filter);
        blogposts = posts::Post::filter_by_tags(blogposts, &filter_hashset);
    }
    Ok(templates::Blog {
        blogposts,
        tags,
        filter_tags: filter_hashset,
    })
}

#[handler]
async fn feed() -> Result<Response> {
    let posts = Blogpost::get_articles().await?;
    // TODO: i18n
    let context = atom::Context {
        page_title: "celeste's hard drive".to_owned(),
        page_url: "https://en.blos.sm".to_owned(),
        self_url: "https://en.blos.sm/feed".to_owned(),
        lang: "en".to_owned(),
    };
    let feed = atom::atom(context, posts).await;
    let feed: String = String::from_utf8(feed.write_to(Vec::new())?)?;
    Ok(Response::builder()
        .status(StatusCode::OK)
        .content_type("application/atom+xml")
        .body(feed))
}

#[handler]
async fn contact() -> templates::Contact {
    templates::Contact
}

#[handler]
async fn plants() -> Result<()> {
    Err(BlossomError::Unimplemented)
}

#[handler]
async fn get_poem(Path(poem): Path<String>) -> Result<templates::Poem> {
    let poem = Poem::get_article(&poem).await?;
    Ok(templates::Poem { poem, jiggle: 4 })
}

#[handler]
async fn get_poetry() -> Result<templates::Poetry> {
    let mut poems = Poem::get_articles().await?;
    poems.sort_by_key(|poem| poem.created_at);
    poems.reverse();
    Ok(templates::Poetry { poems, jiggle: 16 })
}

async fn custom_error(err: poem::Error) -> impl IntoResponse {
    templates::Error {
        status: err.status(),
        message: err.to_string(),
    }
    .with_status(err.status())
}

#[tokio::main]
async fn main() -> std::result::Result<(), std::io::Error> {
    let subscriber = FmtSubscriber::new();
    tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed");

    // let mut skinny_data = mastodon_async::Data::default();
    // skinny_data.base = Cow::from("https://skinnyver.se");

    let blossom = Route::new()
        .at("/", get(home))
        .at("/blog", get(get_blog))
        .at("/blog/:blogpost", get(blogpost))
        .at("/poetry", get(get_poetry))
        .at("/poetry/:poem", get(get_poem))
        .at("/feed", get(feed))
        .at("/contact", get(contact))
        .at("/plants", get(plants))
        .nest("/static/", EmbeddedFilesEndpoint::<Static>::new())
        .catch_all_error(custom_error)
        .with(Tracing)
        .with(AddData::new(
            reqwest::Client::builder()
                .connect_timeout(Duration::from_secs(1))
                .build()
                .unwrap(),
        ));

    Server::new(TcpListener::bind("0.0.0.0:3000"))
        .run(blossom)
        .await
}