123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189 |
- use std::env;
- use std::ffi::OsStr;
- use std::fs::{read_dir, File};
- use std::io::prelude::*;
- use std::io::{Error, ErrorKind};
- use std::path::PathBuf;
-
- use async_std::fs::read_to_string;
- use chrono::prelude::Local;
- use dotenv;
- use serde::{Deserialize, Serialize};
- use serde_json;
- use tera::{Context, Tera};
- use tide::utils::After;
- use tide::{Body, Response, StatusCode};
- use uuid::Uuid;
-
- #[derive(Debug, Serialize, Deserialize)]
- struct Post {
- id: String,
- title: String,
- body: String,
- date: String,
- }
-
- #[derive(Debug, Serialize, Deserialize)]
- struct User {
- username: String,
- password: String,
- }
-
- impl Post {
- fn save(&mut self) -> std::io::Result<()> {
- let mut path: PathBuf = get_posts_directory()?;
- let filename = format!("{}.json", self.id);
- path = path.join(&filename);
- let mut file = File::create(&path)?;
- file.write_all(serde_json::to_string(&self)?.as_bytes())?;
- Ok(())
- }
- }
-
- fn get_posts_directory() -> Result<PathBuf, Error> {
- match env::var("POSTS_DIR") {
- Ok(dir) => Ok(dir.into()),
- Err(_) => Err(Error::new(
- ErrorKind::Other,
- "Posts directory environment variable not set",
- )),
- }
- }
-
- async fn read_all_posts() -> Result<Vec<Post>, Error> {
- let path = match get_posts_directory() {
- Ok(p) => Ok(p),
- Err(_) => Err(Error::new(ErrorKind::Other, "POSTS_DIR variable is not set"))
- }?;
- let mut posts: Vec<Post> = vec![];
-
- let files = match read_dir(path) {
- Ok(f) => Ok(f),
- Err(_) => Err(Error::new(ErrorKind::Other, "Posts directory does not exist"))
- }?;
-
- for file in files {
- let file = file?;
- if let Some("json") = file.path().extension().and_then(OsStr::to_str) {
- let contents = match read_to_string(file.path()).await {
- Ok(c) => Ok(c),
- Err(_) => Err(Error::new(ErrorKind::Other, format!("Error reading post {:?}", file.path())))
- }?;
- let mut post: Post = match serde_json::from_str(&contents) {
- Ok(p) => Ok(p),
- Err(_) => Err(Error::new(ErrorKind::Other, format!("Error deserializing post")))
- }?;
- post.body = post.body.replace("\n", "<br>");
- posts.push(post);
- }
- }
-
- Ok(posts)
- }
-
- fn render(template: &str, context: &Context) -> Result<Body, tide::Error> {
- let tera = Tera::new("templates/**/*.html")?;
- let html = tera.render(template, &context)?;
- Ok(Body::from_string(html))
- }
-
- #[async_std::main]
- async fn main() -> std::io::Result<()> {
- dotenv::dotenv().ok();
- tide::log::start();
- let mut app = tide::new();
-
- app.with(After(|mut res: Response| async {
- if let Some(err) = res.downcast_error::<async_std::io::Error>() {
- let mut context = Context::new();
- context.insert("error", &err.to_string());
- res.set_body(render("error.html", &context)?);
- res.set_status(StatusCode::InternalServerError);
- }
-
- Ok(res)
- }));
-
- app.with(After(|mut res: Response| async {
- res.set_content_type(tide::http::mime::HTML);
- Ok(res)
- }));
-
- app.with(tide::sessions::SessionMiddleware::new(
- tide::sessions::MemoryStore::new(),
- std::env::var("TIDE_SECRET")
- .expect(
- "Please provide a TIDE_SECRET value of at \
- least 32 bytes in order to run this example",
- )
- .as_bytes(),
- ));
-
- app.at("/").get(|req: tide::Request<()>| async move {
- let posts = read_all_posts().await?;
- let mut context = Context::new();
- context.insert("posts", &posts);
- let logged_in: bool = req.session().get("logged_in").unwrap_or(false);
- context.insert("logged_in", &logged_in);
- render("index.html", &context)
- });
-
- app.at("/posts")
- .post(|mut req: tide::Request<()>| async move {
- let logged_in: bool = req.session().get("logged_in").unwrap_or(false);
- if !logged_in {
- Ok(tide::Redirect::new("/login"))
- } else {
- let mut post: Post = req.body_form().await?;
- post.id = Uuid::new_v4().to_string();
- post.date = Local::now().date().naive_local().to_string();
- post.body = post.body.trim().to_owned();
- post.save()?;
- Ok(tide::Redirect::new("/"))
- }
- });
-
- app.at("/login")
- .get(|mut req: tide::Request<()>| async move {
- let mut context = Context::new();
- let logged_in: bool = req.session().get("logged_in").unwrap_or(false);
- context.insert("logged_in", &logged_in);
- match req.session_mut().get::<String>("flash_error") {
- Some(error) => {
- req.session_mut().remove("flash_error");
- &context.insert("error", &error);
- }
- None => {}
- }
- render("login.html", &context)
- })
- .post(|mut req: tide::Request<()>| async move {
- let logged_in: bool = req.session().get("logged_in").unwrap_or(false);
- if logged_in {
- return Ok(tide::Redirect::new("/"));
- }
-
- let username = env::var("ADMIN_USERNAME")?;
- let password = env::var("ADMIN_PASSWORD")?;
- let user: User = req.body_form().await?;
- if user.username == username && user.password == password {
- req.session_mut().remove("logged_in");
- req.session_mut().insert("logged_in", true)?;
- Ok(tide::Redirect::new("/"))
- } else {
- req.session_mut().remove("logged_in");
- req.session_mut().insert("flash_error", "Invalid credentials")?;
- Ok(tide::Redirect::new("/login"))
- }
- });
-
- app.at("/logout").get(|mut req: tide::Request<()>| async move {
- req.session_mut().remove("logged_in");
- req.session_mut().insert("logged_in", false)?;
- Ok(tide::Redirect::new("/"))
- });
-
- app.listen("127.0.0.1:8080").await?;
-
- Ok(())
- }
|