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

                       
                                   




                                 

             
                       




                             
 
                        

                                      

 


                  

 


                                                        
     
 


                                                              
     
 
 


                                                          
 

                   

 




                                                   


     







                                                                          

 
 



                                

 

                                                               
              
                                              



         


                                                                                

 



                                    

 


                    

 


                                                       
     
 


                                
 





                                                      

 


                                             


     



                                                             

 


                                        


     

                                                           

 

                    

 




                                                   
     
 
 
 



                                                                                                                                                  


     
 




                                      




                                                                 


     

                                                                                 


                                













                                              

 







                                              

                 

                                                                                 
 
                           
                                       
                                            
                          
                                                  





                                                                  



                                                                                
use std::cmp::Ordering;
use std::borrow::Cow;
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::fmt;
use std::fs;
use std::path::PathBuf;
use chrono::Datelike;

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

trait Post: fmt::Debug {
    fn get_title(&self) -> &str;
    fn get_article(&self) -> Cow<str>;
}

#[derive(Debug)]
struct FsPost {
    file: PathBuf,
}

impl Post for FsPost {
    fn get_title(&self) -> &str {
        self.file.file_name().unwrap().to_str().unwrap()
    }

    fn get_article(&self) -> Cow<str> {
        let article = fs::read_to_string(&self.file).unwrap();
        Cow::Owned(article)
    }
}

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

struct FsPostRepo {
    dir: PathBuf,
}

impl FsPostRepo {
    fn with_dir(path: impl Into<PathBuf>) -> Self {
        Self {
            dir: path.into()
        }
    }
}

impl PostRepo for FsPostRepo {
    fn load(&self) -> impl IntoIterator<Item = FsPost> {
        let files = fs::read_dir(&self.dir).unwrap();
        files
            .flatten()
            .filter(|d| !d.file_name().to_string_lossy().starts_with('.'))
            .map(|d| FsPost { file: d.path() })
    }
}


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

impl<P: Post> PostsView<P> {
    fn with_posts(posts: impl IntoIterator<Item = P>) -> Self {
        Self {
            posts: posts.into_iter().collect()
        }
    }
}

async fn posts_handler(State(repo): State<Arc<impl PostRepo>>) -> Html<String> {
    let view = PostsView::with_posts(repo.load());
    Html(view.render().unwrap())
}

trait Tutor: fmt::Debug + Ord {
    fn get_name(&self) -> &str;
    fn get_id(&self) -> &str;
    fn get_blurb(&self) -> Cow<str>;
}

#[derive(Debug, Eq)]
struct FsTutor {
    dir: PathBuf
}

impl Tutor for FsTutor {
    fn get_id(&self) -> &str {
        self.dir.file_name().unwrap().to_str().unwrap()
    }

    fn get_name(&self) -> &str {
        self.get_id()
    }

    fn get_blurb(&self) -> Cow<str> {
        let mut path = self.dir.to_owned();
        path.push(format!("{}.md", self.get_id()));
        let blurb = fs::read_to_string(path).unwrap();
        Cow::Owned(blurb)
    }
}

impl Ord for FsTutor {
    fn cmp(&self, other: &Self) -> Ordering {
        self.get_id().cmp(other.get_id())
    }
}

impl PartialOrd for FsTutor {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl PartialEq for FsTutor {
    fn eq(&self, other: &Self) -> bool {
        self.get_id() == other.get_id()
    }
}

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

struct FsTutorRepo {
    dir: PathBuf
}

impl FsTutorRepo {
    fn with_dir(path: impl Into<PathBuf>) -> Self {
        Self {
            dir: path.into()
        }
    }
}


impl TutorRepo for FsTutorRepo {
    fn load(&self) -> impl IntoIterator<Item = FsTutor> {
        let dirs = fs::read_dir(&self.dir).unwrap();
        dirs.flatten().filter(|d| !d.path().file_stem().unwrap().to_str().unwrap_or_default().starts_with('.')).map(|d| FsTutor { dir: d.path() })
    }
}


#[derive(Template)]
#[template(path = "about/index.html")]
struct AboutView<T: Tutor> {
    tutors: Vec<T>,
}
impl<T: Tutor + Ord> AboutView<T> {
    fn with_tutors(tutors: impl IntoIterator<Item = T>) -> Self {
        let mut tutors: Vec<T> = tutors.into_iter().collect();
        tutors.sort();
        Self { tutors }
    }
}

async fn about_handler(State(repo): State<Arc<impl TutorRepo>>) -> Html<String> {
    let view = AboutView::with_tutors(repo.load());
    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(FsPostRepo::with_dir(format!("/data/ct/{}", "blog")));
    let tutors = Arc::new(FsTutorRepo::with_dir(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();
}