diff --git a/Dockerfile b/Dockerfile index c42cd5f..9bfdc3f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,6 +18,7 @@ WORKDIR /app COPY --from=builder /usr/local/cargo/bin/ewp /app/ewp COPY --from=builder /usr/src/ewp/LICENSE /app/LICENSE +COPY --from=builder /usr/src/ewp/README.md /app/README.md COPY --from=builder /usr/src/ewp/static /app/static COPY --from=builder /usr/src/ewp/templates /app/templates diff --git a/README.md b/README.md index afacd3e..ae45e61 100644 --- a/README.md +++ b/README.md @@ -6,4 +6,4 @@ Easy WebPage generator [![status-badge](https://ci.mylloon.fr/api/badges/Anri/mylloon.fr/status.svg)](https://ci.mylloon.fr/Anri/mylloon.fr) - See [issues](https://git.mylloon.fr/Anri/mylloon.fr/issues) -- See [documentation](./Documentation.md) +- See [documentation](https://git.mylloon.fr/Anri/mylloon.fr/src/branch/main/Documentation.md) diff --git a/src/config.rs b/src/config.rs index 9d89d67..abe83e2 100644 --- a/src/config.rs +++ b/src/config.rs @@ -69,13 +69,20 @@ impl FileConfig { } } +// Paths where files are stored +#[derive(Clone, Debug)] +pub struct Locations { + pub static_dir: String, + pub data_dir: String, +} + /// Configuration used internally in the app #[derive(Clone, Debug)] pub struct Config { /// Information given in the config file pub fc: FileConfig, /// Location where the static files are stored - pub static_location: String, + pub locations: Locations, /// Informations about templates pub tmpl: Template, } @@ -110,7 +117,10 @@ pub fn get_config(file_path: &str) -> Config { Config { fc: internal_config.to_owned(), - static_location: format!("{}/{}", files_root, static_dir), + locations: Locations { + static_dir: format!("{}/{}", files_root, static_dir), + data_dir: String::from("data"), + }, tmpl: Template { directory: format!("{}/{}", files_root, templates_dir), app_name: internal_config.app_name.unwrap(), diff --git a/src/main.rs b/src/main.rs index 5058cc3..289fa22 100644 --- a/src/main.rs +++ b/src/main.rs @@ -60,7 +60,7 @@ async fn main() -> Result<()> { .service(portfolio::page) .service(setup::page) .service(web3::page) - .service(Files::new("/", config.static_location.to_owned())) + .service(Files::new("/", config.locations.static_dir.to_owned())) .default_service(web::to(not_found::page)) }) .bind(addr)? diff --git a/src/misc/markdown.rs b/src/misc/markdown.rs index 7fe8fb6..325fb7b 100644 --- a/src/misc/markdown.rs +++ b/src/misc/markdown.rs @@ -6,10 +6,7 @@ use ramhorns::Content; use serde::{Deserialize, Deserializer}; use std::fs; -/// Regular markdown files, no metadata -#[derive(Content, Debug, Default, Deserialize)] -pub struct FileNoMetadata {} - +/// Metadata for blog posts #[derive(Content, Debug, Default, Deserialize)] pub struct FileMetadataBlog { pub title: Option, @@ -20,39 +17,7 @@ pub struct FileMetadataBlog { pub toc: Option, } -#[derive(Content, Debug, Default, Deserialize)] -pub struct FileMetadataContact { - pub title: String, - pub custom: Option, - pub user: Option, - pub link: Option, - pub newtab: Option, - pub description: Option, -} - -#[derive(Content, Debug, Default, Deserialize)] -pub struct FileMetadataPortfolio { - pub title: Option, - pub link: Option, - pub description: Option, - pub language: Option, -} - -pub enum TypeFileMetadata { - Blog, - Contact, - Portfolio, - Cours, -} - -#[derive(Content, Debug, Default, Deserialize)] -pub struct FileMetadata { - pub blog: Option, - pub contact: Option, - pub portfolio: Option, - pub cours: Option, -} - +/// A tag, related to post blog #[derive(Content, Debug, Clone)] pub struct Tag { pub name: String, @@ -73,6 +38,55 @@ impl<'de> Deserialize<'de> for Tag { } } +/// Metadata for contact entry +#[derive(Content, Debug, Default, Deserialize)] +pub struct FileMetadataContact { + pub title: String, + pub custom: Option, + pub user: Option, + pub link: Option, + pub newtab: Option, + pub description: Option, +} + +/// Metadata for index page +#[derive(Content, Debug, Default, Deserialize)] +pub struct FileMetadataIndex { + pub name: Option, + pub pronouns: Option, + pub avatar: Option, + pub avatar_caption: Option, +} + +/// Metadata for portfolio cards +#[derive(Content, Debug, Default, Deserialize)] +pub struct FileMetadataPortfolio { + pub title: Option, + pub link: Option, + pub description: Option, + pub language: Option, +} + +/// List of available metadata types +pub enum TypeFileMetadata { + Blog, + Contact, + Generic, + Index, + Portfolio, +} + +/// Structure who holds all the metadata the file have +/// Usually all fields are None except one +#[derive(Content, Debug, Default, Deserialize)] +pub struct FileMetadata { + pub blog: Option, + pub contact: Option, + pub index: Option, + pub portfolio: Option, +} + +/// Global metadata #[derive(Content, Debug)] pub struct Metadata { pub info: FileMetadata, @@ -81,6 +95,7 @@ pub struct Metadata { pub syntax_highlight: bool, } +/// File description #[derive(Content, Debug)] pub struct File { pub metadata: Metadata, @@ -220,6 +235,7 @@ pub fn get_metadata<'a>(root: &'a AstNode<'a>, mtype: TypeFileMetadata) -> FileM match root .children() .find_map(|node| match &node.data.borrow().value { + // Extract metadata from frontmatter NodeValue::FrontMatter(text) => Some(match mtype { TypeFileMetadata::Blog => FileMetadata { blog: Some(deserialize_metadata(text)), @@ -238,18 +254,22 @@ pub fn get_metadata<'a>(root: &'a AstNode<'a>, mtype: TypeFileMetadata) -> FileM ..FileMetadata::default() } } - TypeFileMetadata::Portfolio => FileMetadata { - portfolio: Some(deserialize_metadata(text)), + TypeFileMetadata::Generic => FileMetadata { ..FileMetadata::default() }, - TypeFileMetadata::Cours => FileMetadata { - cours: Some(deserialize_metadata(text)), + TypeFileMetadata::Index => FileMetadata { + index: Some(deserialize_metadata(text)), + ..FileMetadata::default() + }, + TypeFileMetadata::Portfolio => FileMetadata { + portfolio: Some(deserialize_metadata(text)), ..FileMetadata::default() }, }), _ => None, }) { Some(data) => data, + // No metadata None => match mtype { TypeFileMetadata::Blog => FileMetadata { blog: Some(FileMetadataBlog::default()), @@ -259,12 +279,15 @@ pub fn get_metadata<'a>(root: &'a AstNode<'a>, mtype: TypeFileMetadata) -> FileM contact: Some(FileMetadataContact::default()), ..FileMetadata::default() }, - TypeFileMetadata::Portfolio => FileMetadata { - portfolio: Some(FileMetadataPortfolio::default()), + TypeFileMetadata::Generic => FileMetadata { ..FileMetadata::default() }, - TypeFileMetadata::Cours => FileMetadata { - cours: Some(FileNoMetadata::default()), + TypeFileMetadata::Index => FileMetadata { + index: Some(FileMetadataIndex::default()), + ..FileMetadata::default() + }, + TypeFileMetadata::Portfolio => FileMetadata { + portfolio: Some(FileMetadataPortfolio::default()), ..FileMetadata::default() }, }, diff --git a/src/routes/api_v1.rs b/src/routes/api_v1.rs index 5068f02..464e9f3 100644 --- a/src/routes/api_v1.rs +++ b/src/routes/api_v1.rs @@ -1,6 +1,7 @@ use actix_web::{get, HttpResponse, Responder}; use serde::Serialize; +/// Response #[derive(Serialize)] struct Info { unix_epoch: u32, diff --git a/src/routes/blog.rs b/src/routes/blog.rs index 066e518..c40a36d 100644 --- a/src/routes/blog.rs +++ b/src/routes/blog.rs @@ -42,7 +42,7 @@ struct BlogIndexTemplate { #[once(time = 60)] fn build_index(config: Config) -> String { - let mut posts = get_posts("data/blog"); + let mut posts = get_posts(format!("{}/blog", config.locations.data_dir)); // Sort from newest to oldest posts.sort_by_cached_key(|p| (p.date.year, p.date.month, p.date.day)); @@ -81,8 +81,8 @@ struct Post { impl Post { // Fetch the file content - fn fetch_content(&mut self) { - let blog_dir = "data/blog"; + fn fetch_content(&mut self, data_dir: &str) { + let blog_dir = format!("{}/blog", data_dir); let ext = ".md"; if let Some(file) = read_file( @@ -102,8 +102,8 @@ impl Hash for Post { } } -fn get_posts(location: &str) -> Vec { - let entries = match std::fs::read_dir(location) { +fn get_posts(location: String) -> Vec { + let entries = match std::fs::read_dir(&location) { Ok(res) => res .flatten() .filter(|f| match f.path().extension() { @@ -190,7 +190,12 @@ async fn page(path: web::Path<(String,)>, config: web::Data) -> impl Res fn build_post(file: String, config: Config) -> String { let mut post = None; - let (infos, toc) = get_post(&mut post, file, config.fc.name.unwrap_or_default()); + let (infos, toc) = get_post( + &mut post, + file, + config.fc.name.unwrap_or_default(), + config.locations.data_dir, + ); config.tmpl.render( "blog/post.html", @@ -206,8 +211,13 @@ fn build_post(file: String, config: Config) -> String { ) } -fn get_post(post: &mut Option, filename: String, name: String) -> (Infos, String) { - let blog_dir = "data/blog"; +fn get_post( + post: &mut Option, + filename: String, + name: String, + data_dir: String, +) -> (Infos, String) { + let blog_dir = format!("{}/blog", data_dir); let ext = ".md"; *post = read_file( @@ -268,7 +278,7 @@ async fn rss(config: web::Data) -> impl Responder { #[once(time = 10800)] // 3h fn build_rss(config: Config) -> String { - let mut posts = get_posts("data/blog"); + let mut posts = get_posts(format!("{}/blog", config.locations.data_dir)); // Sort from newest to oldest posts.sort_by_cached_key(|p| (p.date.year, p.date.month, p.date.day)); @@ -315,7 +325,7 @@ fn build_rss(config: Config) -> String { .iter_mut() .map(|p| { // Get post data - p.fetch_content(); + p.fetch_content(&config.locations.data_dir); // Build item Item { diff --git a/src/routes/contact.rs b/src/routes/contact.rs index 91b7775..77aba1d 100644 --- a/src/routes/contact.rs +++ b/src/routes/contact.rs @@ -30,6 +30,7 @@ async fn page(config: web::Data) -> impl Responder { Html(build_page(config.get_ref().to_owned())) } +/// Contact node #[derive(Clone, Debug)] struct ContactLink { service: String, @@ -38,13 +39,12 @@ struct ContactLink { } #[once(time = 60)] -fn find_links() -> Vec { - // TOML file location - let contacts_dir = "data/contacts"; +fn find_links(directory: String) -> Vec { + // TOML filename let toml_file = "links.toml"; // Read the TOML file and parse it - let toml_str = read_to_string(format!("{contacts_dir}/{toml_file}")).unwrap_or_default(); + let toml_str = read_to_string(format!("{directory}/{toml_file}")).unwrap_or_default(); let mut redirections = vec![]; match toml::de::from_str::(&toml_str) { @@ -74,9 +74,9 @@ fn find_links() -> Vec { #[routes] #[get("/{service}")] #[get("/{service}/{scope}")] -async fn service_redirection(req: HttpRequest) -> impl Responder { +async fn service_redirection(config: web::Data, req: HttpRequest) -> impl Responder { let info = req.match_info(); - let link = find_links() + let link = find_links(format!("{}/contacts", config.locations.data_dir)) .iter() // Find requested service .filter(|&x| x.service == *info.query("service")) @@ -123,7 +123,7 @@ fn remove_paragraphs(list: &mut [File]) { #[once(time = 60)] fn build_page(config: Config) -> String { - let contacts_dir = "data/contacts"; + let contacts_dir = format!("{}/contacts", config.locations.data_dir); let ext = ".md"; let socials_dir = "socials"; diff --git a/src/routes/contrib.rs b/src/routes/contrib.rs index 8775b48..f87e538 100644 --- a/src/routes/contrib.rs +++ b/src/routes/contrib.rs @@ -116,20 +116,20 @@ async fn build_page(config: Config) -> String { error: false, projects: Some( data.iter() + .filter(|&p| !p.pulls_merged.is_empty()) .cloned() - .filter(|p| !p.pulls_merged.is_empty()) .collect(), ), waiting: Some( data.iter() + .filter(|&p| !p.pulls_open.is_empty()) .cloned() - .filter(|p| !p.pulls_open.is_empty()) .collect(), ), closed: Some( data.iter() + .filter(|&p| !p.pulls_closed.is_empty()) .cloned() - .filter(|p| !p.pulls_closed.is_empty()) .collect(), ), } diff --git a/src/routes/cours.rs b/src/routes/cours.rs index f843bd5..1474baa 100644 --- a/src/routes/cours.rs +++ b/src/routes/cours.rs @@ -91,7 +91,10 @@ fn get_content( return None; } - read_file(&format!("{cours_dir}/{filename}"), TypeFileMetadata::Cours) + read_file( + &format!("{cours_dir}/{filename}"), + TypeFileMetadata::Generic, + ) } // #[once(time = 60)] diff --git a/src/routes/index.rs b/src/routes/index.rs index b8291d8..bf9db99 100644 --- a/src/routes/index.rs +++ b/src/routes/index.rs @@ -4,7 +4,10 @@ use ramhorns::Content; use crate::{ config::Config, - misc::utils::{make_kw, Html}, + misc::{ + markdown::{read_file, File, TypeFileMetadata}, + utils::{make_kw, Html}, + }, template::{Infos, NavBar}, }; @@ -16,11 +19,37 @@ async fn page(config: web::Data) -> impl Responder { #[derive(Content, Debug)] struct IndexTemplate { navbar: NavBar, - fullname: String, + name: String, + pronouns: Option, + content: Option, + avatar: String, + avatar_caption: String, } #[once(time = 60)] fn build_page(config: Config) -> String { + let mut file = read_file( + &format!("{}/index.md", config.locations.data_dir), + TypeFileMetadata::Index, + ); + + // Default values + let mut name = config.fc.fullname.to_owned().unwrap_or_default(); + let mut pronouns = None; + let mut avatar = "/icons/apple-touch-icon.png".to_owned(); + let mut avatar_caption = "EWP avatar".to_owned(); + + if let Some(f) = &file { + if let Some(m) = &f.metadata.info.index { + name = m.name.to_owned().unwrap_or(name); + avatar = m.avatar.to_owned().unwrap_or(avatar); + pronouns = m.pronouns.to_owned(); + avatar_caption = m.avatar_caption.to_owned().unwrap_or(avatar_caption); + } + } else { + file = read_file("README.md", TypeFileMetadata::Generic); + } + config.tmpl.render( "index.html", IndexTemplate { @@ -28,16 +57,16 @@ fn build_page(config: Config) -> String { index: true, ..NavBar::default() }, - fullname: config - .fc - .fullname - .to_owned() - .unwrap_or("Fullname".to_owned()), + content: file, + name, + pronouns, + avatar, + avatar_caption, }, Infos { page_title: config.fc.fullname, page_desc: Some("Page principale".into()), - page_kw: make_kw(&["index", "étudiant"]), + page_kw: make_kw(&["index", "étudiant", "accueil"]), }, ) } diff --git a/src/routes/portfolio.rs b/src/routes/portfolio.rs index e0fad8d..9b3f1ee 100644 --- a/src/routes/portfolio.rs +++ b/src/routes/portfolio.rs @@ -29,7 +29,7 @@ struct PortfolioTemplate<'a> { #[once(time = 60)] fn build_page(config: Config) -> String { - let projects_dir = "data/projects"; + let projects_dir = format!("{}/projects", config.locations.data_dir); let ext = ".md"; // Get apps @@ -39,7 +39,7 @@ fn build_page(config: Config) -> String { .collect::>(); let appdata = if apps.is_empty() { - (None, Some(projects_dir)) + (None, Some(projects_dir.as_str())) } else { (Some(apps), None) }; diff --git a/src/template.rs b/src/template.rs index 5db9efb..673a8f1 100644 --- a/src/template.rs +++ b/src/template.rs @@ -24,6 +24,7 @@ pub struct Infos { pub page_kw: Option, } +/// Information on what page the user is currently #[derive(Content, Debug, Default)] pub struct NavBar { pub index: bool, diff --git a/static/badges/friends/jas.webp b/static/badges/friends/jas.webp new file mode 100644 index 0000000..d483d3b --- /dev/null +++ b/static/badges/friends/jas.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8a87d0ecb14c364bd370c1c593462c0f60190b52e1f2cae3d396e9af4665fbbd +size 1614 diff --git a/static/css/index.css b/static/css/index.css index ac60ac0..6c654d4 100644 --- a/static/css/index.css +++ b/static/css/index.css @@ -64,8 +64,8 @@ h1 { opacity: 1; } -#friends a { - padding-right: 10px; +#friends a:not(h1 > a) { + padding-right: 5px; } #friends h1 { diff --git a/templates/index.html b/templates/index.html index f82e6ae..6c06b7b 100644 --- a/templates/index.html +++ b/templates/index.html @@ -10,70 +10,24 @@ {{#data}}
- {{fullname}} - (il/lui, he/him) + {{name}} + {{#pronouns}}{{pronouns}}{{/pronouns}} Avatar

-
-

Qui suis-je ?

-

Je m'appelle Anri, mon pseudo est Mylloon.

-

- J'aime beaucoup l'informatique depuis très petit, ce site est écrit de - A à Z par moi-même (modulo la quantité astronomique de librairie - utilisé) en Rust. J'adore le monde de l'open source, l'immense - majorité de mes projets sont sous licence - AGPLv3. -

-

- En ce moment, je suis en master d'informatique à Paris Cité - (anciennement Paris 7), c'est marrant on fait de l'OCaml 🤓☝️. -

-
- -
-

Personnes incroyables

- 21_12 - - Azazouille - - 102jjwy -
- - {{/data}} + {{#content}} {{&content}} {{/content}} {{^content}} +

+ Welcome to EWP, create a index.md file inside your + data/ directory to get started. +

+ {{/content}} {{/data}}