Browse Source

Create fs module, implement single post route

master
Dylan Baker 3 years ago
parent
commit
5d3f02e268
5 changed files with 122 additions and 63 deletions
  1. 64
    0
      src/fs.rs
  2. 29
    59
      src/main.rs
  3. 7
    4
      templates/index.html
  4. 5
    0
      templates/layout.html
  5. 17
    0
      templates/single.html

+ 64
- 0
src/fs.rs View File

@@ -0,0 +1,64 @@
1
+use async_std::fs::read_to_string;
2
+use std::env;
3
+use std::ffi::OsStr;
4
+use std::fs::read_dir;
5
+use std::io::{Error, ErrorKind};
6
+use std::path::PathBuf;
7
+
8
+use crate::Post;
9
+
10
+pub async fn get_posts_directory_path() -> Result<PathBuf, Error> {
11
+    match env::var("POSTS_DIR") {
12
+        Ok(dir) => Ok(dir.into()),
13
+        Err(_) => Err(Error::new(
14
+            ErrorKind::Other,
15
+            "Posts directory environment variable not set",
16
+        )),
17
+    }
18
+}
19
+
20
+pub async fn get_all_posts() -> Result<Vec<Post>, Error> {
21
+    let mut posts: Vec<Post> = vec![];
22
+
23
+    for file in post_directory_contents().await? {
24
+        let file = file?;
25
+        if let Some("json") = file.path().extension().and_then(OsStr::to_str) {
26
+            let contents = read_post_from_disk(&file.path()).await?;
27
+            let post = Post::from_str(&contents)?;
28
+            posts.push(post);
29
+        }
30
+    }
31
+
32
+    Ok(posts)
33
+}
34
+
35
+pub async fn get_one_post(post_id: String) -> Result<Post, Error> {
36
+    let posts_dir = get_posts_directory_path().await?;
37
+    let path = posts_dir.join(format!("{}.json", post_id));
38
+    let contents = read_post_from_disk(&path).await?;
39
+    let post = Post::from_str(&contents)?;
40
+
41
+    Ok(post)
42
+}
43
+
44
+async fn read_post_from_disk(path: &PathBuf) -> Result<String, Error> {
45
+    match read_to_string(&path).await {
46
+        Ok(c) => Ok(c),
47
+        Err(_) => Err(Error::new(
48
+            ErrorKind::Other,
49
+            format!("Error reading post {:?}", &path),
50
+        )),
51
+    }
52
+}
53
+
54
+async fn post_directory_contents() -> Result<std::fs::ReadDir, Error> {
55
+    let path = get_posts_directory_path().await?;
56
+
57
+    match read_dir(path) {
58
+        Ok(f) => Ok(f),
59
+        Err(_) => Err(Error::new(
60
+            ErrorKind::Other,
61
+            "Posts directory does not exist",
62
+        )),
63
+    }
64
+}

+ 29
- 59
src/main.rs View File

@@ -1,11 +1,9 @@
1 1
 use std::env;
2
-use std::ffi::OsStr;
3
-use std::fs::{read_dir, File};
2
+use std::fs::File;
4 3
 use std::io::prelude::*;
5 4
 use std::io::{Error, ErrorKind};
6 5
 use std::path::PathBuf;
7 6
 
8
-use async_std::fs::read_to_string;
9 7
 use chrono::prelude::Local;
10 8
 use dotenv;
11 9
 use serde::{Deserialize, Serialize};
@@ -15,8 +13,10 @@ use tide::utils::After;
15 13
 use tide::{Body, Response, StatusCode};
16 14
 use uuid::Uuid;
17 15
 
16
+mod fs;
17
+
18 18
 #[derive(Debug, Serialize, Deserialize)]
19
-struct Post {
19
+pub struct Post {
20 20
     id: String,
21 21
     title: String,
22 22
     body: String,
@@ -30,67 +30,26 @@ struct User {
30 30
 }
31 31
 
32 32
 impl Post {
33
-    fn save(&mut self) -> std::io::Result<()> {
34
-        let mut path: PathBuf = get_posts_directory()?;
33
+    async fn save(&mut self) -> std::io::Result<()> {
34
+        let mut path: PathBuf = fs::get_posts_directory_path().await?;
35 35
         let filename = format!("{}.json", self.id);
36 36
         path = path.join(&filename);
37 37
         let mut file = File::create(&path)?;
38 38
         file.write_all(serde_json::to_string(&self)?.as_bytes())?;
39 39
         Ok(())
40 40
     }
41
-}
42
-
43
-fn get_posts_directory() -> Result<PathBuf, Error> {
44
-    match env::var("POSTS_DIR") {
45
-        Ok(dir) => Ok(dir.into()),
46
-        Err(_) => Err(Error::new(
47
-            ErrorKind::Other,
48
-            "Posts directory environment variable not set",
49
-        )),
50
-    }
51
-}
52 41
 
53
-async fn read_all_posts() -> Result<Vec<Post>, Error> {
54
-    let path = match get_posts_directory() {
55
-        Ok(p) => Ok(p),
56
-        Err(_) => Err(Error::new(
57
-            ErrorKind::Other,
58
-            "POSTS_DIR variable is not set",
59
-        )),
60
-    }?;
61
-    let mut posts: Vec<Post> = vec![];
62
-
63
-    let files = match read_dir(path) {
64
-        Ok(f) => Ok(f),
65
-        Err(_) => Err(Error::new(
66
-            ErrorKind::Other,
67
-            "Posts directory does not exist",
68
-        )),
69
-    }?;
70
-
71
-    for file in files {
72
-        let file = file?;
73
-        if let Some("json") = file.path().extension().and_then(OsStr::to_str) {
74
-            let contents = match read_to_string(file.path()).await {
75
-                Ok(c) => Ok(c),
76
-                Err(_) => Err(Error::new(
77
-                    ErrorKind::Other,
78
-                    format!("Error reading post {:?}", file.path()),
79
-                )),
80
-            }?;
81
-            let mut post: Post = match serde_json::from_str(&contents) {
82
-                Ok(p) => Ok(p),
83
-                Err(_) => Err(Error::new(
84
-                    ErrorKind::Other,
85
-                    format!("Error deserializing post"),
86
-                )),
87
-            }?;
88
-            post.body = post.body.replace("\n", "<br>");
89
-            posts.push(post);
90
-        }
42
+    fn from_str(blob: &str) -> Result<Post, Error> {
43
+        let mut post: Post = match serde_json::from_str(blob) {
44
+            Ok(p) => Ok(p),
45
+            Err(_) => Err(Error::new(
46
+                ErrorKind::Other,
47
+                format!("Error deserializing post"),
48
+            )),
49
+        }?;
50
+        post.body = post.body.replace("\n", "<br>");
51
+        Ok(post)
91 52
     }
92
-
93
-    Ok(posts)
94 53
 }
95 54
 
96 55
 fn render(template: &str, context: &Context) -> Result<Body, tide::Error> {
@@ -132,7 +91,7 @@ async fn main() -> std::io::Result<()> {
132 91
     ));
133 92
 
134 93
     app.at("/").get(|req: tide::Request<()>| async move {
135
-        let posts = read_all_posts().await?;
94
+        let posts: Vec<Post> = fs::get_all_posts().await?;
136 95
         let mut context = Context::new();
137 96
         context.insert("posts", &posts);
138 97
         let logged_in: bool = req.session().get("logged_in").unwrap_or(false);
@@ -150,11 +109,22 @@ async fn main() -> std::io::Result<()> {
150 109
                 post.id = Uuid::new_v4().to_string();
151 110
                 post.date = Local::now().date().naive_local().to_string();
152 111
                 post.body = post.body.trim().to_owned();
153
-                post.save()?;
112
+                post.save().await?;
154 113
                 Ok(tide::Redirect::new("/"))
155 114
             }
156 115
         });
157 116
 
117
+    app.at("/posts/:id")
118
+        .get(|req: tide::Request<()>| async move {
119
+            let mut context = Context::new();
120
+            let logged_in: bool = req.session().get("logged_in").unwrap_or(false);
121
+            context.insert("logged_in", &logged_in);
122
+            let post_id = req.param("id")?;
123
+            let post = fs::get_one_post(post_id).await?;
124
+            context.insert("post", &post);
125
+            render("single.html", &context)
126
+        });
127
+
158 128
     app.at("/login")
159 129
         .get(|mut req: tide::Request<()>| async move {
160 130
             let mut context = Context::new();

+ 7
- 4
templates/index.html View File

@@ -21,10 +21,13 @@
21 21
   {% for post in posts %}
22 22
   <div class="posts__post post">
23 23
     <h3 class="post__heading">
24
-      {% if logged_in %}
25
-      <a class="post__edit-link" href="/posts/{{ post.id }}/edit">edit</a>
26
-      {% endif %}
27
-      <span>
24
+      <div class="post__links">
25
+        <a class="post__link" href="/posts/{{ post.id }}">view</a>
26
+        {% if logged_in %}
27
+        <a class="post__link" href="/posts/{{ post.id }}/edit">edit</a>
28
+        {% endif %}
29
+      </div>
30
+      <span class="post__title">
28 31
         {{ post.title }} :: {{ post.date }}
29 32
       </span>
30 33
     </h3>

+ 5
- 0
templates/layout.html View File

@@ -74,6 +74,11 @@
74 74
         padding: 0.5em;
75 75
       }
76 76
 
77
+      .post__title {
78
+        flex: 1;
79
+        text-align: right;
80
+      }
81
+
77 82
       .post__link {
78 83
         font-style: normal;
79 84
         font-weight: normal;

+ 17
- 0
templates/single.html View File

@@ -0,0 +1,17 @@
1
+{% extends "layout.html" %} {% block content %}
2
+<div class="posts">
3
+  <div class="posts__post post">
4
+    <h3 class="post__heading">
5
+      {% if logged_in %}
6
+      <a class="post__link" href="/posts/{{ post.id }}/edit">edit</a>
7
+      {% endif %}
8
+      <span class="post__title">
9
+        {{ post.title }} :: {{ post.date }}
10
+      </span>
11
+    </h3>
12
+    <div class="post__body">
13
+      {{ post.body | safe }}
14
+    </div>
15
+  </div>
16
+</div>
17
+{% endblock %}

Loading…
Cancel
Save