macroblog.rs @ 6a31a30b98f7febe9ac0db74211ef074aefc7ad3

feat: Add project tab
  1diff --git a/content/projects/index.md b/content/projects/index.md
  2new file mode 100644
  3index 0000000000000000000000000000000000000000..2ae70153c0cc4d27455343070d192ea48b1355e4
  4--- /dev/null
  5+++ b/content/projects/index.md
  6@@ -0,0 +1,46 @@
  7+Just a list of some projects I have done and find useful on my day-to-day life.
  8+
  9+-   [Hub Watcher](https://hub-watcher.gabrielgio.me/)
 10+    [[gitlab](https://gitlab.com/gabrielgio/hub-watcher)]
 11+
 12+    A small project to monitor changes in a docker image from [docker
 13+    hub](https://hub.docker.com/). By default every 5 minutes it will fetch the
 14+    digest of image and compare with the previous returned digest, if they are
 15+    different it will make a post request to a given url.
 16+
 17+    I created it so I can automatically trigger my gitlab pipeline to build my
 18+    custom nextcloud image everytime Nextcloud GmbH updates their image.
 19+
 20+-   [Reddit to Nextcloud
 21+    importer](https://gabrielgio.gitlab.io/reddit-nextcloud-importer/)
 22+    [[github](https://gitlab.com/gabrielgio/reddit-nextcloud-importer)]
 23+
 24+    A small project that monitors user\'s saved posts on reddit, downloads its
 25+    media and uploads to a nextcloud instance.
 26+
 27+    It combines 3 projects: [praw](https://github.com/praw-dev/praw) to read and
 28+    motitor user's saved feed, [gallery-dl](https://github.com/mikf/gallery-dl)
 29+    to download media from several sources, and
 30+    [nextcloud-api-wrapper](https://github.com/luffah/nextcloud-API) to manage
 31+    folder and upload files to nexcloud instance.
 32+
 33+-   [Filter for Nerdcast
 34+    (pt-BR)](https://gabrielgio.gitlab.io/jn_filter/)
 35+    [[gitlab](https://gitlab.com/gabrielgio/jn_filter)]
 36+
 37+    Just a small podcast filter to remove and/or split a feed from
 38+    [Nerdcast](https://www.jovemnerd.com.br/nerdcast/) into different segments.
 39+    The current feed its quite clustered with many programs/segments and this
 40+    project just helps to clean up so only the segment you want shows up on you
 41+    podcast client.
 42+
 43+-   [Password generator](https://genpass.gabrielgio.me/)
 44+    [[gitlab](https://gitlab.com/gabrielgio/genpass)]
 45+
 46+    It started with me having fun with clojure script ([last
 47+    commit](https://gitlab.com/gabrielgio/genpass/-/tree/2db3d88503fbe219e99c464c4cc8e768613e1359)).
 48+    Now I have been using it as a playground to play a bit with rust/wasm and it
 49+    is a quite interesting comparacion to make. The cljs implementation could
 50+    not handle more than 1k chars, while the wasm can easly handle >100k. Is it
 51+    useful for a password generator? Probabally not, but if it is your use case
 52+    now I got you covered.
 53diff --git a/src/assets.rs b/src/assets.rs
 54index 2c39d1b71ae6e763aeb0c338f6e729173c2dee39..32d26e9a9fd0640c1d042be0cbc8760f774b48b6 100644
 55--- a/src/assets.rs
 56+++ b/src/assets.rs
 57@@ -11,10 +11,21 @@ #[derive(RustEmbed)]
 58 #[folder = "content/posts/"]
 59 pub struct PostAsset;
 60 
 61+#[derive(RustEmbed)]
 62+#[folder = "content/projects/"]
 63+pub struct ProjectsAsset;
 64+
 65 #[derive(TemplateOnce)]
 66 #[template(path = "index.html")]
 67 pub struct IndexTemplate {
 68     pub posts: Vec<BlogEntry>,
 69+}
 70+
 71+
 72+#[derive(TemplateOnce)]
 73+#[template(path = "projects.html")]
 74+pub struct ProjectsTemplate {
 75+    pub content: String,
 76 }
 77 
 78 #[derive(TemplateOnce)]
 79diff --git a/src/bin/actix.rs b/src/bin/actix.rs
 80index 101fe2e4dc8116e33656ba2f00ad23c5dec0d138..978e8ed20001affa0cb82bd096ad50e8dc651828 100644
 81--- a/src/bin/actix.rs
 82+++ b/src/bin/actix.rs
 83@@ -1,11 +1,20 @@
 84 use actix_web::{get, web, middleware, App, HttpResponse, HttpServer, Responder, http::header::ContentType};
 85-use macroblog::blog::{render_index_page, render_post_page};
 86+use macroblog::blog::{render_index_page, render_post_page, render_projects};
 87 use macroblog::router::blog_post_exists;
 88 use std::env;
 89 
 90 #[get("/")]
 91 async fn index() -> impl Responder {
 92     let body = render_index_page();
 93+
 94+    HttpResponse::Ok()
 95+        .content_type(ContentType::html())
 96+        .body(body)
 97+}
 98+
 99+#[get("/projects")]
100+async fn projects() -> impl Responder {
101+    let body = render_projects();
102 
103     HttpResponse::Ok()
104         .content_type(ContentType::html())
105@@ -36,6 +45,7 @@     HttpServer::new(|| {
106         App::new()
107             .wrap(middleware::Compress::default())
108             .service(index)
109+            .service(projects)
110             .service(posts)
111     })
112     .bind(("0.0.0.0", port))?
113diff --git a/src/bin/hyper.rs b/src/bin/hyper.rs
114index 3f23f18797072248065804ea49477ce5eac0d8bc..f24cbe46b848319154fbfdacb7eff5bf00d45968 100644
115--- a/src/bin/hyper.rs
116+++ b/src/bin/hyper.rs
117@@ -1,11 +1,10 @@
118-use std::convert::Infallible;
119-use std::{env};
120-use std::net::SocketAddr;
121+use hyper::service::{make_service_fn, service_fn};
122 use hyper::{Body, Request, Response, Server};
123-use hyper::service::{make_service_fn, service_fn};
124+use macroblog::blog::{render_index_page, render_post_page, render_projects};
125 use macroblog::router::Router;
126-use macroblog::blog::{render_index_page, render_post_page};
127-
128+use std::convert::Infallible;
129+use std::env;
130+use std::net::SocketAddr;
131 
132 async fn not_found() -> Result<Response<Body>, Infallible> {
133     let resp: Response<Body> = Response::builder()
134@@ -14,7 +13,6 @@         .body("Not Found".into())
135         .unwrap();
136     Ok(resp)
137 }
138-
139 
140 async fn index() -> Result<Response<Body>, Infallible> {
141     let body = render_index_page();
142@@ -28,6 +26,17 @@
143     Ok(resp)
144 }
145 
146+async fn projects() -> Result<Response<Body>, Infallible> {
147+    let body = render_projects();
148+
149+    let resp: Response<Body> = Response::builder()
150+        .status(200)
151+        .header("posts-type", "text/html")
152+        .body(body.into())
153+        .unwrap();
154+
155+    Ok(resp)
156+}
157 
158 async fn post(path: &String) -> Result<Response<Body>, Infallible> {
159     let body = render_post_page(path);
160@@ -46,20 +55,21 @@     let path = req.uri().path();
161 
162     match Router::new(path) {
163         Router::Index => index().await,
164+        Router::Projects => projects().await,
165         Router::Post { page } => post(&page).await,
166-        Router::NotFound => not_found().await
167+        Router::NotFound => not_found().await,
168     }
169 }
170-
171 
172 #[tokio::main]
173 async fn main() {
174-    let port = env::var("PORT").unwrap_or("3000".into()).parse::<u16>().unwrap_or(3000);
175+    let port = env::var("PORT")
176+        .unwrap_or("3000".into())
177+        .parse::<u16>()
178+        .unwrap_or(3000);
179     let addr = SocketAddr::from(([0, 0, 0, 0], port));
180 
181-    let make_svc = make_service_fn(|_conn| async {
182-        Ok::<_, Infallible>(service_fn(request))
183-    });
184+    let make_svc = make_service_fn(|_conn| async { Ok::<_, Infallible>(service_fn(request)) });
185 
186     let server = Server::bind(&addr).serve(make_svc);
187 
188diff --git a/src/blog.rs b/src/blog.rs
189index a1586f8fa4eea24c83f086c589af9c8fd34f9c64..8c3af524d120c63ff807cb6769f029aef797ad66 100644
190--- a/src/blog.rs
191+++ b/src/blog.rs
192@@ -1,8 +1,7 @@
193+use crate::assets::{BlogEntry, IndexTemplate, PostAsset, PostTemplate, ProjectsAsset, ProjectsTemplate};
194 use pulldown_cmark::{html, Options, Parser};
195 use sailfish::TemplateOnce;
196 use std::str;
197-use crate::assets::{BlogEntry, PostAsset, IndexTemplate, PostTemplate};
198-
199 
200 pub fn read_assets() -> Vec<BlogEntry> {
201     let mut entries: Vec<BlogEntry> = PostAsset::iter()
202@@ -26,6 +25,24 @@     html::push_html(&mut html_output, parser);
203     return html_output.to_string();
204 }
205 
206+fn get_projects_content() -> String {
207+    let buffer = ProjectsAsset::get("index.md").unwrap().data.into_owned();
208+    let md = String::from_utf8(buffer).unwrap();
209+    let mut options = Options::empty();
210+    options.insert(Options::ENABLE_FOOTNOTES);
211+    let parser = Parser::new_ext(&md, options);
212+    let mut html_output = &mut String::new();
213+    html::push_html(&mut html_output, parser);
214+    return html_output.to_string();
215+}
216+
217+pub fn render_projects() -> String {
218+    ProjectsTemplate {
219+        content: get_projects_content(),
220+    }
221+    .render_once()
222+    .unwrap()
223+}
224 
225 pub fn render_post_page(path: &String) -> String {
226     let blog = BlogEntry::new(path);
227diff --git a/src/router.rs b/src/router.rs
228index 3227d66d789853ff4751bcd266e37aec3e78c99e..c5efd9c982c675da05df8a583978ccbcb5bf140d 100644
229--- a/src/router.rs
230+++ b/src/router.rs
231@@ -1,11 +1,12 @@
232 use crate::assets::PostAsset;
233 use regex::Regex;
234 
235-const ACTION_REGEX: &str = r"/{0,1}(?P<action>\w*)/(?P<id>.+)";
236+const ACTION_REGEX: &str = r"/{0,1}(?P<action>\w*)/{0,1}(?P<id>.*)";
237 
238 pub enum Router {
239     NotFound,
240     Index,
241+    Projects,
242     Post { page: String },
243 }
244 
245@@ -17,11 +18,14 @@ impl Router {
246     pub fn new(path: &str) -> Router {
247         let re = Regex::new(ACTION_REGEX).unwrap();
248         let caps = re.captures(path);
249-        let action = match caps {
250+        let mut action = match caps {
251             Some(ref value) => &value["action"],
252             None => "index",
253         };
254 
255+        if action == "" {
256+            action = "index"
257+        }
258 
259         // this 7 means the "/posts/" from the full path
260         let trimmed_path: String = path.chars().skip(7).collect();
261@@ -33,6 +37,7 @@         match action {
262             "posts" => Router::Post {
263                 page: caps.unwrap()["id"].to_string(),
264             },
265+            "projects" => Router::Projects,
266             "index" => Router::Index,
267             _ => Router::NotFound,
268         }
269diff --git a/templates/header.html b/templates/header.html
270index c830273fec5e96c3e65e4832315f6ab9d164aec4..b81a09e67cd7204906fc0ce079d1fe44ea21a629 100644
271--- a/templates/header.html
272+++ b/templates/header.html
273@@ -5,6 +5,7 @@             <h2 class="title">Yet Another Blog</h2>
274         </a>
275         <nav class="container-fluid">
276             <ul>
277+                <li><a href="/projects" class="secondary">Projects</a></li>
278                 <li><a href="https://gitlab.com/gabrielgio/cv/-/raw/main/cv.pdf?inline=false" class="secondary">Resume</a></li>
279             </ul>
280         </nav>
281diff --git a/templates/projects.html b/templates/projects.html
282new file mode 100644
283index 0000000000000000000000000000000000000000..9aff1a6a2bd2f83ca09718d5c6d555c4b06fd58f
284--- /dev/null
285+++ b/templates/projects.html
286@@ -0,0 +1,12 @@
287+<!DOCTYPE html>
288+<html data-theme="light" lang="en">
289+    <head>
290+        <% include!("head.html"); %>
291+    </head>
292+    <body>
293+        <div class="layout">
294+            <% include!("header.html"); %>
295+            <%- content %>
296+        </div>
297+    </body>
298+</html>
299diff --git a/tests/test_router.rs b/tests/test_router.rs
300index cfd4c32bc8b356bfad745090c76a31d304614cdf..97f344cf2c64ee3090fd4ab0c826fca2867977b3 100644
301--- a/tests/test_router.rs
302+++ b/tests/test_router.rs
303@@ -5,15 +5,28 @@ fn test_router_new_posts() {
304     match Router::new("/posts/2021-12-26Enable_NFS_on_K3S.md") {
305         Router::NotFound => assert!(false, "Wrong type parse"),
306         Router::Index => assert!(false, "Wrong type parse"),
307+        Router::Projects => assert!(false, "Wrong type parse"),
308         Router::Post { page } => assert_eq!(page, "2021-12-26Enable_NFS_on_K3S.md".to_string()),
309     };
310 }
311+
312+
313+#[test]
314+fn test_router_projects() {
315+    match Router::new("/projects") {
316+        Router::NotFound => assert!(false, "Wrong type parse"),
317+        Router::Index => assert!(false, "Wrong type parse"),
318+        Router::Projects => assert!(true),
319+        Router::Post { page: _ } => assert!(false, "Wrong type parse"),
320+    };
321+}
322 
323 #[test]
324 fn test_router_new_index() {
325     match Router::new("/") {
326         Router::Index => assert!(true),
327         Router::NotFound => assert!(false, "Wrong type parse"),
328+        Router::Projects => assert!(false, "Wrong type parse"),
329         Router::Post { page: _ } => assert!(false, "Wrong type parse"),
330     };
331 }
332@@ -23,6 +36,7 @@ fn test_router_new_not_found() {
333     match Router::new("/not_found") {
334         Router::NotFound => assert!(true),
335         Router::Index => assert!(false, "Wrong type parse"),
336+        Router::Projects => assert!(false, "Wrong type parse"),
337         Router::Post { page: _ } => assert!(false, "Wrong type parse"),
338     };
339 }
340@@ -32,6 +46,7 @@ fn test_router_new_not_found_matching_regex() {
341     match Router::new("/posts/2021-12-03Enable_NFS_on_K3S.html") {
342         Router::NotFound => assert!(true),
343         Router::Index => assert!(false, "Wrong type parse"),
344+        Router::Projects => assert!(false, "Wrong type parse"),
345         Router::Post { page: _ } => assert!(false, "Wrong type parse"),
346     };
347 }
348diff --git a/watch b/watch
349index a9a0dbadda835029c3cf626cc86ae6384e305430..5b17a42bf962eecc90a4cfb11855670ffa35cbb5 100755
350--- a/watch
351+++ b/watch
352@@ -4,7 +4,10 @@ case $1 in
353   actix)
354     cargo watch -x "run --bin actix"
355     ;;
356+  hyper)
357+    cargo watch -x "run --bin hyper"
358+    ;;
359   *)
360-    cargo watch -x "run --bin hyper"
361+    cargo watch -x "test"
362     ;;
363 esac