mod error;
mod live;
mod posts;
mod scrobbles;
mod skweets;
use std::borrow::Cow;
use std::collections::HashSet;
use atom_syndication::Feed;
use rocket::fs::{relative, FileServer};
use rocket::http::{ContentType, Status};
use rocket::{Request, State};
use rocket_dyn_templates::{context, Template};
use error::BlossomError;
type Result<T> = std::result::Result<T, BlossomError>;
struct Clients {
listenbrainz: listenbrainz::raw::Client,
skinnyverse: mastodon_async::Mastodon,
reqwest: reqwest::Client,
}
#[macro_use]
extern crate rocket;
#[get("/")]
async fn home(clients: &State<Clients>) -> Template {
let (live, listenbrainz, skweets, blogposts) = tokio::join!(
live::get_live_status(&clients.reqwest),
scrobbles::get_now_playing(&clients.listenbrainz),
skweets::get_recents(&clients.skinnyverse),
posts::get_blogposts()
);
let is_live = live.unwrap_or_default().online;
let listenbrainz = listenbrainz.unwrap_or_default();
let skweets = skweets.unwrap_or_default();
let blogposts = blogposts.unwrap_or_default();
Template::render(
"home",
context! {
is_live,
listenbrainz,
skweets,
blogposts,
},
)
}
#[get("/blog/<blogpost>")]
async fn blogpost(blogpost: &str) -> Result<Template> {
let mut blogpost = posts::get_blogpost(blogpost).await?;
blogpost.render().await?;
Ok(Template::render(
"blogpost",
context! {
blogpost,
},
))
}
#[get("/blog?<filter>")]
async fn blog(filter: Vec<String>) -> Result<Template> {
let mut blogposts = posts::get_blogposts().await?;
let tags: Vec<String> = posts::get_tags(&blogposts)
.into_iter()
.map(|tag| tag.to_owned())
.collect();
let mut filter_hashset: HashSet<String> = HashSet::new();
if !filter.is_empty() {
filter_hashset.extend(filter.into_iter());
blogposts = posts::filter_by_tags(blogposts, &filter_hashset);
}
for blogpost in &mut blogposts {
blogpost.render().await?;
}
let reverse = true;
Ok(Template::render(
"blog",
context! {
reverse,
tags,
filter_hashset,
blogposts,
},
))
}
#[get("/feed")]
async fn feed() -> Result<(Status, (ContentType, String))> {
let posts = posts::get_blogposts().await?;
let feed = posts::syndication::atom(posts).await;
let feed: String = String::from_utf8(feed.write_to(Vec::new())?)?;
Ok((
Status::new(200),
(ContentType::new("application", "atom+xml"), feed),
))
}
#[get("/contact")]
async fn contact() -> Template {
Template::render("contact", context! {})
}
#[get("/plants")]
async fn plants() -> Result<Template> {
Err(BlossomError::Unimplemented(Status::NotImplemented))
}
#[catch(default)]
fn catcher(status: Status, req: &Request) -> Template {
let message;
if status.code == 404 {
message = "i either haven't built this page yet or it looks like you're a little lost";
} else if status.code == 500 {
message = "omg the server went kaputt!!";
} else if status.code == 501 {
message = "it looks like this is not yet here!!!";
} else {
message = "idk i got bored";
}
let status = format!("{}", status);
Template::render(
"error",
context! { status: status, req: req.uri(), message: message },
)
}
#[tokio::main]
async fn main() -> std::result::Result<(), rocket::Error> {
let mut skinny_data = mastodon_async::Data::default();
skinny_data.base = Cow::from("https://skinnyver.se");
let _rocket = rocket::build()
.manage(Clients {
listenbrainz: listenbrainz::raw::Client::new(),
skinnyverse: mastodon_async::Mastodon::from(skinny_data),
reqwest: reqwest::Client::new(),
})
.attach(Template::custom(|engines| {
engines.tera.autoescape_on(vec![]);
}))
.mount("/", routes![home, contact, blog, blogpost, feed, plants])
.register("/", catchers![catcher])
.mount("/", FileServer::from("./static"))
.launch()
.await?;
Ok(())
}