diff options
author | Adam T. Carpenter <atc@53hor.net> | 2024-09-14 11:22:54 -0400 |
---|---|---|
committer | Adam T. Carpenter <atc@53hor.net> | 2024-09-14 11:22:54 -0400 |
commit | 340a804e550cb5b733bd2e64e515e79740bb6338 (patch) | |
tree | 1cfcc87357ddc144d82b942dfb84742d491d8148 /src | |
parent | 0213fd2dcd09ca4b1252cdc45415a765a887d679 (diff) | |
download | carpentertutoring-340a804e550cb5b733bd2e64e515e79740bb6338.tar.xz carpentertutoring-340a804e550cb5b733bd2e64e515e79740bb6338.zip |
feat: impl tutors/about/team view
Diffstat (limited to 'src')
-rw-r--r-- | src/main.rs | 211 |
1 files changed, 95 insertions, 116 deletions
diff --git a/src/main.rs b/src/main.rs index d07d201..7b5ddfc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,15 +1,13 @@ +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::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; @@ -18,177 +16,158 @@ fn current_year() -> i32 { } 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; + fn get_title(&self) -> &str; + fn get_article(&self) -> Cow<str>; } -struct MdFilePost { - path: PathBuf, - name: OsString, +#[derive(Debug)] +struct FsPost { + file: PathBuf, } -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 FsPost { + fn get_title(&self) -> &str { + self.file.file_name().unwrap().to_str().unwrap() } -} -impl Post for MdFilePost { - fn dump(&self) { - println!("{}: {}", &self.display_name(), &self.get_content()); + fn get_article(&self) -> Cow<str> { + let article = fs::read_to_string(&self.file).unwrap(); + Cow::Owned(article) } +} - fn display_name(&self) -> Cow<str> { - self.name.to_string_lossy() - } +trait PostRepo { + fn load(&self) -> impl IntoIterator<Item = impl Post>; +} - 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) - } +struct FsPostRepo { + dir: PathBuf, } -impl fmt::Debug for MdFilePost { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "MdFilePost '{}'", self.display_name()) +impl FsPostRepo { + fn with_dir(path: impl Into<PathBuf>) -> Self { + Self { + dir: path.into() + } } } -trait Posts { - fn get_posts(&self) -> impl IntoIterator<Item = impl Post>; +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() }) + } } -trait Tutors { - fn get_tutors(&self) -> impl IntoIterator<Item = impl Tutor>; -} -struct FsDirPosts { - path: PathBuf, +#[derive(Template)] +#[template(path = "posts.html")] +struct PostsView<P: Post> { + posts: Vec<P> } -impl FsDirPosts { - fn new(path: &str) -> Self { +impl<P: Post> PostsView<P> { + fn with_posts(posts: impl IntoIterator<Item = P>) -> Self { Self { - path: PathBuf::from(path), + posts: posts.into_iter().collect() } } } -struct FsDirTutors { - path: PathBuf, +async fn posts_handler(State(repo): State<Arc<impl PostRepo>>) -> Html<String> { + let view = PostsView::with_posts(repo.load()); + Html(view.render().unwrap()) } -impl Tutors for FsDirTutors { - fn get_tutors(&self) -> impl IntoIterator<Item = impl Tutor> { - let test: Vec<MdTutor> = Vec::new(); - test - } +trait Tutor: fmt::Debug + Ord { + fn get_name(&self) -> &str; + fn get_id(&self) -> &str; + fn get_blurb(&self) -> Cow<str>; } -impl FsDirTutors { - fn new(path: &str) -> Self { - Self { - path: PathBuf::from(path), - } - } +#[derive(Debug, Eq)] +struct FsTutor { + dir: PathBuf } -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())) +impl Tutor for FsTutor { + fn get_id(&self) -> &str { + self.dir.file_name().unwrap().to_str().unwrap() } -} -trait Page: Template { - fn from_post(post: &impl Post) -> Self; -} + fn get_name(&self) -> &str { + self.get_id() + } -#[derive(Template)] -#[template(path = "post.html")] -struct MdPage { - article: String, + 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 Page for MdPage { - fn from_post(post: &impl Post) -> Self { - Self { - article: post.get_content().into_owned(), - } +impl Ord for FsTutor { + fn cmp(&self, other: &Self) -> Ordering { + self.get_id().cmp(other.get_id()) } } -#[derive(Template)] -#[template(path = "posts.html")] -struct PostsView { - post_titles: Vec<String> +impl PartialOrd for FsTutor { + fn partial_cmp(&self, other: &Self) -> Option<Ordering> { + Some(self.cmp(other)) + } } -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() - } +impl PartialEq for FsTutor { + fn eq(&self, other: &Self) -> bool { + self.get_id() == other.get_id() } } -// 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()) +trait TutorRepo { + fn load(&self) -> impl IntoIterator<Item = impl Tutor>; } -#[derive(Debug)] -struct MdTutor { - id: String, - name: String, - blurb: String, +struct FsTutorRepo { + dir: PathBuf } -impl Tutor for MdTutor { - fn id(&self) -> &str { - self.id.as_str() +impl FsTutorRepo { + fn with_dir(path: impl Into<PathBuf>) -> Self { + Self { + dir: path.into() + } } +} - fn display_name(&self) -> &str { - self.name.as_str() - } - fn blurb(&self) -> &str { - self.blurb.as_str() +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> AboutView<T> { - fn with_tutors(tutors: &impl Tutors) -> Self { - todo!() +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(tutors): State<Arc<impl Tutors>>) -> Html<String> { - let view: AboutView<MdTutor> = AboutView::with_tutors(&*tutors); +async fn about_handler(State(repo): State<Arc<impl TutorRepo>>) -> Html<String> { + let view = AboutView::with_tutors(repo.load()); Html(view.render().unwrap()) } @@ -218,8 +197,8 @@ async fn brochure_handler() -> Html<String> { #[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 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)) |