summaryrefslogblamecommitdiff
path: root/src/main.rs
blob: d07d201fe624fa2d2846cd8b7279be983e0af0b7 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
                                   











                                 




                             
 
                        
                   
                                       


                                      





                                   













                                                                  
                          



                                                                      

                                        
















                                                                




                                                                 













                                      


















                                                                  
                           


                                                                                                                                                    


     

                                           







                               

                                            





                                                     








                                               
                                                                                                                         
         


     




                                                                                                   




                                                                              





































                                                                                













                                              

 







                                              

                 

                                                                             
 
                           
                                       
                                            
                          
                                                  





                                                                  



                                                                                
use tower_http::services::ServeDir;
use axum::response::Html;
use axum::extract::State;
use std::sync::Arc;
use askama_axum::Template;
use axum::{routing::get, Router};
use std::borrow::Cow;
use std::ffi::OsString;
use std::fmt;
use std::fs;
use std::io::*;
use std::path::Path;
use std::path::PathBuf;
use chrono::Datelike;

fn current_year() -> i32 {
    chrono::Utc::now().year()
}

trait Post: fmt::Debug {
    fn dump(&self);
    fn display_name(&self) -> Cow<str>;
    fn get_content(&self) -> Cow<str>;
}

trait Tutor: fmt::Debug {
    fn id(&self) -> &str;
    fn display_name(&self) -> &str;
    fn blurb(&self) -> &str;
}

struct MdFilePost {
    path: PathBuf,
    name: OsString,
}

impl MdFilePost {
    fn new(path: &Path) -> Self {
        Self {
            path: path.to_path_buf(),
            name: path.file_stem().unwrap_or_default().to_owned(),
        }
    }
}

impl Post for MdFilePost {
    fn dump(&self) {
        println!("{}: {}", &self.display_name(), &self.get_content());
    }

    fn display_name(&self) -> Cow<str> {
        self.name.to_string_lossy()
    }

    fn get_content(&self) -> Cow<str> {
        let mut file = std::fs::File::open(&self.path).unwrap();
        let mut markdown = String::new();
        file.read_to_string(&mut markdown).unwrap();
        Cow::Owned(markdown)
    }
}

impl fmt::Debug for MdFilePost {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "MdFilePost '{}'", self.display_name())
    }
}

trait Posts {
    fn get_posts(&self) -> impl IntoIterator<Item = impl Post>;
}

trait Tutors {
    fn get_tutors(&self) -> impl IntoIterator<Item = impl Tutor>;
}

struct FsDirPosts {
    path: PathBuf,
}

impl FsDirPosts {
    fn new(path: &str) -> Self {
        Self {
            path: PathBuf::from(path),
        }
    }
}

struct FsDirTutors {
    path: PathBuf,
}

impl Tutors for FsDirTutors {
    fn get_tutors(&self) -> impl IntoIterator<Item = impl Tutor> {
        let test: Vec<MdTutor> = Vec::new();
        test
    }
}

impl FsDirTutors {
    fn new(path: &str) -> Self {
        Self {
            path: PathBuf::from(path),
        }
    }
}

impl Posts for FsDirPosts {
    fn get_posts(&self) -> impl IntoIterator<Item = impl Post> {
        let files = fs::read_dir(&self.path).unwrap();
        files.flatten().filter(|d| !d.path().file_stem().unwrap().to_str().unwrap_or_default().starts_with('.')).map(|d| MdFilePost::new(&d.path()))
    }
}

trait Page: Template {
    fn from_post(post: &impl Post) -> Self;
}

#[derive(Template)]
#[template(path = "post.html")]
struct MdPage {
    article: String,
}

impl Page for MdPage {
    fn from_post(post: &impl Post) -> Self {
        Self {
            article: post.get_content().into_owned(),
        }
    }
}

#[derive(Template)]
#[template(path = "posts.html")]
struct PostsView {
    post_titles: Vec<String>
}

impl PostsView {
    fn with_posts(posts: &impl Posts) -> Self {
        Self {
            post_titles: posts.get_posts().into_iter().map(|p| p.display_name().into_owned()).map(String::from).collect()
        }
    }
}

// TODO: one single Markdown struct with id, name, and content can take over for both MdTutor and
// MdPost if it implements the Tutor and Post traits
// With this, I could have a repo which does all the same loading but returns things of a generic T
// as long as that T is provided it'll map values into those fields?

async fn posts_handler(State(posts): State<Arc<impl Posts>>) -> Html<String> {
    let view = PostsView::with_posts(&*posts);
    Html(view.render().unwrap())
}

#[derive(Debug)]
struct MdTutor {
    id: String,
    name: String,
    blurb: String,
}

impl Tutor for MdTutor {
    fn id(&self) -> &str {
        self.id.as_str()
    }

    fn display_name(&self) -> &str {
        self.name.as_str()
    }

    fn blurb(&self) -> &str { 
        self.blurb.as_str()
    }
}

#[derive(Template)]
#[template(path = "about/index.html")]
struct AboutView<T: Tutor> {
    tutors: Vec<T>,
}

impl<T: Tutor> AboutView<T> {
    fn with_tutors(tutors: &impl Tutors) -> Self {
        todo!()
    }
}

async fn about_handler(State(tutors): State<Arc<impl Tutors>>) -> Html<String> {
    let view: AboutView<MdTutor> = AboutView::with_tutors(&*tutors);
    Html(view.render().unwrap())
}

#[derive(Template)]
#[template(path = "index.html")]
struct IndexTemplate;

async fn index_handler() -> Html<String> {
    Html(IndexTemplate {}.render().unwrap())
}

#[derive(Template)]
#[template(path = "policies.html")]
struct PoliciesTemplate;

async fn policies_handler() -> Html<String> {
    Html(PoliciesTemplate{}.render().unwrap())
}

#[derive(Template)]
#[template(path = "brochure/index.html")]
struct BrochureTemplate;

async fn brochure_handler() -> Html<String> {
    Html(BrochureTemplate{}.render().unwrap())
}

#[tokio::main]
async fn main() {
    let posts = Arc::new(FsDirPosts::new(&format!("/data/ct/{}", "blog")));
    let tutors = Arc::new(FsDirTutors::new(&format!("/data/ct/{}", "team")));

    let app = Router::new()
        .route("/", get(index_handler))
        .route("/posts", get(posts_handler))
        .with_state(posts)
        .route("/policies", get(policies_handler))
        .route("/brochure", get(brochure_handler))
        .route("/about", get(about_handler))
        .with_state(tutors)
        .nest_service("/assets", ServeDir::new("/data/ct/assets"))
        .nest_service("/team", ServeDir::new("/data/ct/team"))
        .fallback_service(ServeDir::new("static"));

    let listener = tokio::net::TcpListener::bind("0.0.0.0:8000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}