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();
}