From ae00627e7c99fd21ce4ad8ec0692445f00a349b2 Mon Sep 17 00:00:00 2001 From: "Adam T. Carpenter" Date: Sat, 7 Sep 2024 21:18:54 -0400 Subject: feat: basic markdown post to html page loading --- src/main.rs | 136 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 src/main.rs (limited to 'src/main.rs') diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..43b6fa6 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,136 @@ +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; + +trait Post<'a>: fmt::Debug { + fn dump(&self); + fn display_name(&'a self) -> &'a str; + fn get_content(&self) -> Cow; +} + +#[derive(Debug)] +struct MockPost; + +impl<'a> Post<'a> for MockPost { + fn dump(&self) { + println!("Post content goes here."); + } + + fn display_name(&'a self) -> &'a str { + "" + } + + fn get_content(&self) -> Cow { + Cow::Borrowed("") + } +} + +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<'a> Post<'a> for MdFilePost { + fn dump(&self) { + println!("{}: {}", &self.display_name(), &self.get_content()); + } + + fn display_name(&'a self) -> &'a str { + self.name.to_str().unwrap() + } + + fn get_content(&self) -> Cow { + 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 Iterator; +} + +struct FsDirPosts { + path: PathBuf, +} + +impl FsDirPosts { + fn new(path: &str) -> Self { + Self { + path: PathBuf::from(path), + } + } +} + +impl Posts for FsDirPosts { + fn get_posts(&self) -> impl Iterator { + let dirs = fs::read_dir(&self.path).unwrap(); + dirs.flatten().map(|d| MdFilePost::new(&d.path())) + } +} + +trait Page<'a>: Template { + fn from_post(post: &impl Post<'a>) -> Self; +} + +#[derive(Template)] +#[template(path = "post.html")] +struct MdPage { + article: String, +} + +impl<'a> Page<'a> for MdPage { + fn from_post(post: &impl Post<'a>) -> Self { + Self { + article: post.get_content().into_owned(), + } + } +} + +fn test_get_posts(posts: &impl Posts) { + for post in posts.get_posts() { + let page = MdPage::from_post(&post); + println!("{}", page.render().unwrap()); + } +} + +async fn handler(State(posts): State>) -> Html { + let post = posts.get_posts().next().unwrap(); + let page = MdPage::from_post(&post); + Html(page.render().unwrap()) +} + +#[tokio::main] +async fn main() { + let repo = Arc::new(FsDirPosts::new(&format!("/data/ct/{}", "blog"))); + + let app = Router::new().route("/posts", get(handler)).with_state(repo); + + let listener = tokio::net::TcpListener::bind("0.0.0.0:8000").await.unwrap(); + axum::serve(listener, app).await.unwrap(); +} -- cgit v1.2.3 From f2bd378e1a8cdfa7d1520b3734a748dd1cd9de25 Mon Sep 17 00:00:00 2001 From: "Adam T. Carpenter" Date: Sat, 7 Sep 2024 22:39:41 -0400 Subject: feat: serve static and assets add policies and index from base --- src/main.rs | 82 +++++++++++++++++++++++++++++++++++-------------------------- 1 file changed, 47 insertions(+), 35 deletions(-) (limited to 'src/main.rs') diff --git a/src/main.rs b/src/main.rs index 43b6fa6..ca0a3d9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +use tower_http::services::ServeDir; use axum::response::Html; use axum::extract::State; use std::sync::Arc; @@ -11,29 +12,12 @@ use std::io::*; use std::path::Path; use std::path::PathBuf; -trait Post<'a>: fmt::Debug { +trait Post: fmt::Debug { fn dump(&self); - fn display_name(&'a self) -> &'a str; + fn display_name(&self) -> Cow; fn get_content(&self) -> Cow; } -#[derive(Debug)] -struct MockPost; - -impl<'a> Post<'a> for MockPost { - fn dump(&self) { - println!("Post content goes here."); - } - - fn display_name(&'a self) -> &'a str { - "" - } - - fn get_content(&self) -> Cow { - Cow::Borrowed("") - } -} - struct MdFilePost { path: PathBuf, name: OsString, @@ -48,13 +32,13 @@ impl MdFilePost { } } -impl<'a> Post<'a> for MdFilePost { +impl Post for MdFilePost { fn dump(&self) { println!("{}: {}", &self.display_name(), &self.get_content()); } - fn display_name(&'a self) -> &'a str { - self.name.to_str().unwrap() + fn display_name(&self) -> Cow { + self.name.to_string_lossy() } fn get_content(&self) -> Cow { @@ -94,8 +78,8 @@ impl Posts for FsDirPosts { } } -trait Page<'a>: Template { - fn from_post(post: &impl Post<'a>) -> Self; +trait Page: Template { + fn from_post(post: &impl Post) -> Self; } #[derive(Template)] @@ -104,32 +88,60 @@ struct MdPage { article: String, } -impl<'a> Page<'a> for MdPage { - fn from_post(post: &impl Post<'a>) -> Self { +impl Page for MdPage { + fn from_post(post: &impl Post) -> Self { Self { article: post.get_content().into_owned(), } } } -fn test_get_posts(posts: &impl Posts) { - for post in posts.get_posts() { - let page = MdPage::from_post(&post); - println!("{}", page.render().unwrap()); +#[derive(Template)] +#[template(path = "posts.html")] +struct PostsView { + post_titles: Vec +} + +impl PostsView { + fn with_posts(posts: &impl Posts) -> Self { + Self { + post_titles: posts.get_posts().map(|p| p.display_name().into_owned()).map(String::from).collect() + } } } -async fn handler(State(posts): State>) -> Html { - let post = posts.get_posts().next().unwrap(); - let page = MdPage::from_post(&post); - Html(page.render().unwrap()) +async fn posts_handler(State(posts): State>) -> Html { + let view = PostsView::with_posts(&*posts); + Html(view.render().unwrap()) +} + +#[derive(Template)] +#[template(path = "index.html")] +struct IndexTemplate; + +async fn index_handler() -> Html { + Html(IndexTemplate {}.render().unwrap()) +} + +#[derive(Template)] +#[template(path = "policies.html")] +struct PoliciesTemplate; + +async fn policies_handler() -> Html { + Html(PoliciesTemplate{}.render().unwrap()) } #[tokio::main] async fn main() { let repo = Arc::new(FsDirPosts::new(&format!("/data/ct/{}", "blog"))); - let app = Router::new().route("/posts", get(handler)).with_state(repo); + let app = Router::new() + .nest_service("/static", ServeDir::new("static")) + .nest_service("/assets", ServeDir::new("/data/ct/assets")) + .route("/posts", get(posts_handler)) + .route("/policies", get(policies_handler)) + .route("/", get(index_handler)) + .with_state(repo); let listener = tokio::net::TcpListener::bind("0.0.0.0:8000").await.unwrap(); axum::serve(listener, app).await.unwrap(); -- cgit v1.2.3 From 0213fd2dcd09ca4b1252cdc45415a765a887d679 Mon Sep 17 00:00:00 2001 From: "Adam T. Carpenter" Date: Mon, 9 Sep 2024 22:28:33 -0400 Subject: feat: teams and blurbs but lots of todos --- src/main.rs | 110 ++++++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 100 insertions(+), 10 deletions(-) (limited to 'src/main.rs') diff --git a/src/main.rs b/src/main.rs index ca0a3d9..d07d201 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,6 +11,11 @@ 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); @@ -18,6 +23,12 @@ trait Post: fmt::Debug { fn get_content(&self) -> Cow; } +trait Tutor: fmt::Debug { + fn id(&self) -> &str; + fn display_name(&self) -> &str; + fn blurb(&self) -> &str; +} + struct MdFilePost { path: PathBuf, name: OsString, @@ -56,7 +67,11 @@ impl fmt::Debug for MdFilePost { } trait Posts { - fn get_posts(&self) -> impl Iterator; + fn get_posts(&self) -> impl IntoIterator; +} + +trait Tutors { + fn get_tutors(&self) -> impl IntoIterator; } struct FsDirPosts { @@ -71,10 +86,29 @@ impl FsDirPosts { } } +struct FsDirTutors { + path: PathBuf, +} + +impl Tutors for FsDirTutors { + fn get_tutors(&self) -> impl IntoIterator { + let test: Vec = Vec::new(); + test + } +} + +impl FsDirTutors { + fn new(path: &str) -> Self { + Self { + path: PathBuf::from(path), + } + } +} + impl Posts for FsDirPosts { - fn get_posts(&self) -> impl Iterator { - let dirs = fs::read_dir(&self.path).unwrap(); - dirs.flatten().map(|d| MdFilePost::new(&d.path())) + fn get_posts(&self) -> impl IntoIterator { + 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())) } } @@ -105,16 +139,59 @@ struct PostsView { impl PostsView { fn with_posts(posts: &impl Posts) -> Self { Self { - post_titles: posts.get_posts().map(|p| p.display_name().into_owned()).map(String::from).collect() + 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>) -> Html { 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 { + tutors: Vec, +} + +impl AboutView { + fn with_tutors(tutors: &impl Tutors) -> Self { + todo!() + } +} + +async fn about_handler(State(tutors): State>) -> Html { + let view: AboutView = AboutView::with_tutors(&*tutors); + Html(view.render().unwrap()) +} + #[derive(Template)] #[template(path = "index.html")] struct IndexTemplate; @@ -131,17 +208,30 @@ async fn policies_handler() -> Html { Html(PoliciesTemplate{}.render().unwrap()) } +#[derive(Template)] +#[template(path = "brochure/index.html")] +struct BrochureTemplate; + +async fn brochure_handler() -> Html { + Html(BrochureTemplate{}.render().unwrap()) +} + #[tokio::main] async fn main() { - let repo = Arc::new(FsDirPosts::new(&format!("/data/ct/{}", "blog"))); + let posts = Arc::new(FsDirPosts::new(&format!("/data/ct/{}", "blog"))); + let tutors = Arc::new(FsDirTutors::new(&format!("/data/ct/{}", "team"))); let app = Router::new() - .nest_service("/static", ServeDir::new("static")) - .nest_service("/assets", ServeDir::new("/data/ct/assets")) + .route("/", get(index_handler)) .route("/posts", get(posts_handler)) + .with_state(posts) .route("/policies", get(policies_handler)) - .route("/", get(index_handler)) - .with_state(repo); + .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(); -- cgit v1.2.3 From 340a804e550cb5b733bd2e64e515e79740bb6338 Mon Sep 17 00:00:00 2001 From: "Adam T. Carpenter" Date: Sat, 14 Sep 2024 11:22:54 -0400 Subject: feat: impl tutors/about/team view --- src/main.rs | 211 +++++++++++++++++++++++++++--------------------------------- 1 file changed, 95 insertions(+), 116 deletions(-) (limited to 'src/main.rs') 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; - fn get_content(&self) -> Cow; -} - -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; } -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 { + let article = fs::read_to_string(&self.file).unwrap(); + Cow::Owned(article) } +} - fn display_name(&self) -> Cow { - self.name.to_string_lossy() - } +trait PostRepo { + fn load(&self) -> impl IntoIterator; +} - fn get_content(&self) -> Cow { - 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) -> Self { + Self { + dir: path.into() + } } } -trait Posts { - fn get_posts(&self) -> impl IntoIterator; +impl PostRepo for FsPostRepo { + fn load(&self) -> impl IntoIterator { + 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; -} -struct FsDirPosts { - path: PathBuf, +#[derive(Template)] +#[template(path = "posts.html")] +struct PostsView { + posts: Vec

} -impl FsDirPosts { - fn new(path: &str) -> Self { +impl PostsView

{ + fn with_posts(posts: impl IntoIterator) -> Self { Self { - path: PathBuf::from(path), + posts: posts.into_iter().collect() } } } -struct FsDirTutors { - path: PathBuf, +async fn posts_handler(State(repo): State>) -> Html { + let view = PostsView::with_posts(repo.load()); + Html(view.render().unwrap()) } -impl Tutors for FsDirTutors { - fn get_tutors(&self) -> impl IntoIterator { - let test: Vec = Vec::new(); - test - } +trait Tutor: fmt::Debug + Ord { + fn get_name(&self) -> &str; + fn get_id(&self) -> &str; + fn get_blurb(&self) -> Cow; } -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 { - 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 { + 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 +impl PartialOrd for FsTutor { + fn partial_cmp(&self, other: &Self) -> Option { + 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>) -> Html { - let view = PostsView::with_posts(&*posts); - Html(view.render().unwrap()) +trait TutorRepo { + fn load(&self) -> impl IntoIterator; } -#[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) -> 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 { + 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 { tutors: Vec, } - -impl AboutView { - fn with_tutors(tutors: &impl Tutors) -> Self { - todo!() +impl AboutView { + fn with_tutors(tutors: impl IntoIterator) -> Self { + let mut tutors: Vec = tutors.into_iter().collect(); + tutors.sort(); + Self { tutors } } } -async fn about_handler(State(tutors): State>) -> Html { - let view: AboutView = AboutView::with_tutors(&*tutors); +async fn about_handler(State(repo): State>) -> Html { + let view = AboutView::with_tutors(repo.load()); Html(view.render().unwrap()) } @@ -218,8 +197,8 @@ async fn brochure_handler() -> Html { #[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)) -- cgit v1.2.3 From 9f341d439f7aa5fd2365024169ead2d6bdc3210c Mon Sep 17 00:00:00 2001 From: "Adam T. Carpenter" Date: Sat, 14 Sep 2024 20:30:05 -0400 Subject: feat: rewrite complete --- src/main.rs | 213 ++++-------------------------------------------------------- 1 file changed, 14 insertions(+), 199 deletions(-) (limited to 'src/main.rs') diff --git a/src/main.rs b/src/main.rs index 7b5ddfc..fb15e51 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,199 +1,14 @@ -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; -} - -#[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 { - let article = fs::read_to_string(&self.file).unwrap(); - Cow::Owned(article) - } -} - -trait PostRepo { - fn load(&self) -> impl IntoIterator; -} - -struct FsPostRepo { - dir: PathBuf, -} - -impl FsPostRepo { - fn with_dir(path: impl Into) -> Self { - Self { - dir: path.into() - } - } -} - -impl PostRepo for FsPostRepo { - fn load(&self) -> impl IntoIterator { - 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 { - posts: Vec

-} - -impl PostsView

{ - fn with_posts(posts: impl IntoIterator) -> Self { - Self { - posts: posts.into_iter().collect() - } - } -} - -async fn posts_handler(State(repo): State>) -> Html { - 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; -} - -#[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 { - 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 { - 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; -} - -struct FsTutorRepo { - dir: PathBuf -} - -impl FsTutorRepo { - fn with_dir(path: impl Into) -> Self { - Self { - dir: path.into() - } - } -} - - -impl TutorRepo for FsTutorRepo { - fn load(&self) -> impl IntoIterator { - 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 { - tutors: Vec, -} -impl AboutView { - fn with_tutors(tutors: impl IntoIterator) -> Self { - let mut tutors: Vec = tutors.into_iter().collect(); - tutors.sort(); - Self { tutors } - } -} - -async fn about_handler(State(repo): State>) -> Html { - let view = AboutView::with_tutors(repo.load()); - Html(view.render().unwrap()) -} - -#[derive(Template)] -#[template(path = "index.html")] -struct IndexTemplate; - -async fn index_handler() -> Html { - Html(IndexTemplate {}.render().unwrap()) -} - -#[derive(Template)] -#[template(path = "policies.html")] -struct PoliciesTemplate; - -async fn policies_handler() -> Html { - Html(PoliciesTemplate{}.render().unwrap()) -} - -#[derive(Template)] -#[template(path = "brochure/index.html")] -struct BrochureTemplate; +use tutors::fs_tutor_repo::FsTutorRepo; +use std::sync::Arc; +use tower_http::services::ServeDir; +use posts::fs_post_repo::FsPostRepo; -async fn brochure_handler() -> Html { - Html(BrochureTemplate{}.render().unwrap()) -} +mod helpers; +mod posts; +mod tutors; +mod views; +mod handlers; #[tokio::main] async fn main() { @@ -201,12 +16,12 @@ async fn main() { let tutors = Arc::new(FsTutorRepo::with_dir(format!("/data/ct/{}", "team"))); let app = Router::new() - .route("/", get(index_handler)) - .route("/posts", get(posts_handler)) + .route("/", get(handlers::index_handler)) + .route("/posts", get(handlers::posts_handler)) .with_state(posts) - .route("/policies", get(policies_handler)) - .route("/brochure", get(brochure_handler)) - .route("/about", get(about_handler)) + .route("/policies", get(handlers::policies_handler)) + .route("/brochure", get(handlers::brochure_handler)) + .route("/about", get(handlers::about_handler)) .with_state(tutors) .nest_service("/assets", ServeDir::new("/data/ct/assets")) .nest_service("/team", ServeDir::new("/data/ct/team")) -- cgit v1.2.3 From 18339f611fd17e1300593edd65adf7604a39ad72 Mon Sep 17 00:00:00 2001 From: "Adam T. Carpenter" Date: Sat, 14 Sep 2024 20:54:44 -0400 Subject: feat: working rudimentary blog presentation --- src/main.rs | 1 + 1 file changed, 1 insertion(+) (limited to 'src/main.rs') diff --git a/src/main.rs b/src/main.rs index fb15e51..4314292 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,6 +18,7 @@ async fn main() { let app = Router::new() .route("/", get(handlers::index_handler)) .route("/posts", get(handlers::posts_handler)) + .route("/posts/:post_id", get(handlers::post_handler)) .with_state(posts) .route("/policies", get(handlers::policies_handler)) .route("/brochure", get(handlers::brochure_handler)) -- cgit v1.2.3 From 4b5e92345ff880f9233179191cfce1c04bd4c386 Mon Sep 17 00:00:00 2001 From: "Adam T. Carpenter" Date: Sun, 15 Sep 2024 23:18:28 -0400 Subject: feat: tracing and env vars --- src/main.rs | 40 +++++++++++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 7 deletions(-) (limited to 'src/main.rs') diff --git a/src/main.rs b/src/main.rs index 4314292..81c4427 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,9 @@ +use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter}; +use tower_http::trace::{self, TraceLayer}; +use tracing::{info, Level}; use axum::{routing::get, Router}; use tutors::fs_tutor_repo::FsTutorRepo; -use std::sync::Arc; +use std::{sync::Arc, env}; use tower_http::services::ServeDir; use posts::fs_post_repo::FsPostRepo; @@ -12,9 +15,24 @@ mod handlers; #[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 tracing_filter = EnvFilter::builder() + .with_env_var("CT_LOG") + .try_from_env() + .unwrap_or("carpentertutoring=debug,tower_http=debug,axum::rejection=trace".into()); + tracing_subscriber::registry() + .with(tracing_filter) + .with(tracing_subscriber::fmt::layer()) + .init(); + info!("loading state..."); + let blog_dir = env::var("CT_POSTS").unwrap(); + let tutor_dir = env::var("CT_TEAM").unwrap(); + let assets_dir = env::var("CT_ASSETS").unwrap(); + + let posts = Arc::new(FsPostRepo::with_dir(blog_dir)); + let tutors = Arc::new(FsTutorRepo::with_dir(tutor_dir.clone())); + + info!("initializing router..."); let app = Router::new() .route("/", get(handlers::index_handler)) .route("/posts", get(handlers::posts_handler)) @@ -24,10 +42,18 @@ async fn main() { .route("/brochure", get(handlers::brochure_handler)) .route("/about", get(handlers::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")); + .nest_service("/assets", ServeDir::new(assets_dir)) + .nest_service("/team", ServeDir::new(tutor_dir)) + .fallback_service(ServeDir::new("static")) + .layer( + TraceLayer::new_for_http() + .make_span_with(trace::DefaultMakeSpan::new() + .level(Level::INFO)) + .on_response(trace::DefaultOnResponse::new() + .level(Level::INFO)) + ); - let listener = tokio::net::TcpListener::bind("0.0.0.0:8000").await.unwrap(); + let addr = env::var("CT_BIND").unwrap_or("0.0.0.0:8000".into()); + let listener = tokio::net::TcpListener::bind(addr).await.unwrap(); axum::serve(listener, app).await.unwrap(); } -- cgit v1.2.3 From 45a658e693ab9779ddd364d2acb651178db2dc99 Mon Sep 17 00:00:00 2001 From: "Adam T. Carpenter" Date: Sat, 28 Sep 2024 09:39:00 -0400 Subject: fix: trailing slash route temp fix --- src/main.rs | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src/main.rs') diff --git a/src/main.rs b/src/main.rs index 81c4427..b65a58b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -39,8 +39,11 @@ async fn main() { .route("/posts/:post_id", get(handlers::post_handler)) .with_state(posts) .route("/policies", get(handlers::policies_handler)) + .route("/policies/", get(handlers::policies_handler)) .route("/brochure", get(handlers::brochure_handler)) + .route("/brochure/", get(handlers::brochure_handler)) .route("/about", get(handlers::about_handler)) + .route("/about/", get(handlers::about_handler)) .with_state(tutors) .nest_service("/assets", ServeDir::new(assets_dir)) .nest_service("/team", ServeDir::new(tutor_dir)) -- cgit v1.2.3 From d1c9f549d3a45118f0a563ddbe07c18fbc8ab660 Mon Sep 17 00:00:00 2001 From: "Adam T. Carpenter" Date: Sat, 28 Sep 2024 09:55:53 -0400 Subject: fix: real fix for trailing backslash handling --- src/main.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) (limited to 'src/main.rs') diff --git a/src/main.rs b/src/main.rs index b65a58b..252588d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,8 @@ +use tower::Layer; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter}; -use tower_http::trace::{self, TraceLayer}; +use tower_http::{trace::{self, TraceLayer}, normalize_path::NormalizePathLayer}; use tracing::{info, Level}; -use axum::{routing::get, Router}; +use axum::{routing::get, Router, ServiceExt, extract::Request}; use tutors::fs_tutor_repo::FsTutorRepo; use std::{sync::Arc, env}; use tower_http::services::ServeDir; @@ -39,11 +40,8 @@ async fn main() { .route("/posts/:post_id", get(handlers::post_handler)) .with_state(posts) .route("/policies", get(handlers::policies_handler)) - .route("/policies/", get(handlers::policies_handler)) .route("/brochure", get(handlers::brochure_handler)) - .route("/brochure/", get(handlers::brochure_handler)) .route("/about", get(handlers::about_handler)) - .route("/about/", get(handlers::about_handler)) .with_state(tutors) .nest_service("/assets", ServeDir::new(assets_dir)) .nest_service("/team", ServeDir::new(tutor_dir)) @@ -55,8 +53,9 @@ async fn main() { .on_response(trace::DefaultOnResponse::new() .level(Level::INFO)) ); + let app = NormalizePathLayer::trim_trailing_slash().layer(app); let addr = env::var("CT_BIND").unwrap_or("0.0.0.0:8000".into()); let listener = tokio::net::TcpListener::bind(addr).await.unwrap(); - axum::serve(listener, app).await.unwrap(); + axum::serve(listener, ServiceExt::::into_make_service(app)).await.unwrap(); } -- cgit v1.2.3