summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAdam T. Carpenter <atc@53hor.net>2024-09-14 11:22:54 -0400
committerAdam T. Carpenter <atc@53hor.net>2024-09-14 11:22:54 -0400
commit340a804e550cb5b733bd2e64e515e79740bb6338 (patch)
tree1cfcc87357ddc144d82b942dfb84742d491d8148 /src
parent0213fd2dcd09ca4b1252cdc45415a765a887d679 (diff)
downloadcarpentertutoring-340a804e550cb5b733bd2e64e515e79740bb6338.tar.xz
carpentertutoring-340a804e550cb5b733bd2e64e515e79740bb6338.zip
feat: impl tutors/about/team view
Diffstat (limited to 'src')
-rw-r--r--src/main.rs211
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))