211 lines
6.0 KiB
Rust
211 lines
6.0 KiB
Rust
mod flash;
|
|
|
|
use axum::{
|
|
extract::{Extension, Form, Path, Query},
|
|
http::StatusCode,
|
|
response::Html,
|
|
routing::{get, get_service, post},
|
|
Router, Server,
|
|
};
|
|
use axum_example_core::{
|
|
sea_orm::{Database, DatabaseConnection},
|
|
Mutation as MutationCore, Query as QueryCore,
|
|
};
|
|
use entity::post;
|
|
use flash::{get_flash_cookie, post_response, PostResponse};
|
|
use migration::{Migrator, MigratorTrait};
|
|
use serde::{Deserialize, Serialize};
|
|
use std::str::FromStr;
|
|
use std::{env, net::SocketAddr};
|
|
use tera::Tera;
|
|
use tower::ServiceBuilder;
|
|
use tower_cookies::{CookieManagerLayer, Cookies};
|
|
use tower_http::services::ServeDir;
|
|
|
|
#[tokio::main]
|
|
async fn start() -> anyhow::Result<()> {
|
|
env::set_var("RUST_LOG", "debug");
|
|
tracing_subscriber::fmt::init();
|
|
|
|
dotenvy::dotenv().ok();
|
|
let db_url = env::var("DATABASE_URL").expect("DATABASE_URL is not set in .env file");
|
|
let host = env::var("HOST").expect("HOST is not set in .env file");
|
|
let port = env::var("PORT").expect("PORT is not set in .env file");
|
|
let server_url = format!("{}:{}", host, port);
|
|
|
|
let conn = Database::connect(db_url)
|
|
.await
|
|
.expect("Database connection failed");
|
|
Migrator::up(&conn, None).await.unwrap();
|
|
let templates = Tera::new(concat!(env!("CARGO_MANIFEST_DIR"), "/templates/**/*"))
|
|
.expect("Tera initialization failed");
|
|
// let state = AppState { templates, conn };
|
|
|
|
let app = Router::new()
|
|
.route("/", get(list_posts).post(create_post))
|
|
.route("/:id", get(edit_post).post(update_post))
|
|
.route("/new", get(new_post))
|
|
.route("/delete/:id", post(delete_post))
|
|
.nest(
|
|
"/static",
|
|
get_service(ServeDir::new(concat!(
|
|
env!("CARGO_MANIFEST_DIR"),
|
|
"/static"
|
|
)))
|
|
.handle_error(|error: std::io::Error| async move {
|
|
(
|
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
format!("Unhandled internal error: {}", error),
|
|
)
|
|
}),
|
|
)
|
|
.layer(
|
|
ServiceBuilder::new()
|
|
.layer(CookieManagerLayer::new())
|
|
.layer(Extension(conn))
|
|
.layer(Extension(templates)),
|
|
);
|
|
|
|
let addr = SocketAddr::from_str(&server_url).unwrap();
|
|
Server::bind(&addr).serve(app.into_make_service()).await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
struct Params {
|
|
page: Option<u64>,
|
|
posts_per_page: Option<u64>,
|
|
}
|
|
|
|
#[derive(Deserialize, Serialize, Debug, Clone)]
|
|
struct FlashData {
|
|
kind: String,
|
|
message: String,
|
|
}
|
|
|
|
async fn list_posts(
|
|
Extension(ref templates): Extension<Tera>,
|
|
Extension(ref conn): Extension<DatabaseConnection>,
|
|
Query(params): Query<Params>,
|
|
cookies: Cookies,
|
|
) -> Result<Html<String>, (StatusCode, &'static str)> {
|
|
let page = params.page.unwrap_or(1);
|
|
let posts_per_page = params.posts_per_page.unwrap_or(5);
|
|
|
|
let (posts, num_pages) = QueryCore::find_posts_in_page(conn, page, posts_per_page)
|
|
.await
|
|
.expect("Cannot find posts in page");
|
|
|
|
let mut ctx = tera::Context::new();
|
|
ctx.insert("posts", &posts);
|
|
ctx.insert("page", &page);
|
|
ctx.insert("posts_per_page", &posts_per_page);
|
|
ctx.insert("num_pages", &num_pages);
|
|
|
|
if let Some(value) = get_flash_cookie::<FlashData>(&cookies) {
|
|
ctx.insert("flash", &value);
|
|
}
|
|
|
|
let body = templates
|
|
.render("index.html.tera", &ctx)
|
|
.map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Template error"))?;
|
|
|
|
Ok(Html(body))
|
|
}
|
|
|
|
async fn new_post(
|
|
Extension(ref templates): Extension<Tera>,
|
|
) -> Result<Html<String>, (StatusCode, &'static str)> {
|
|
let ctx = tera::Context::new();
|
|
let body = templates
|
|
.render("new.html.tera", &ctx)
|
|
.map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Template error"))?;
|
|
|
|
Ok(Html(body))
|
|
}
|
|
|
|
async fn create_post(
|
|
Extension(ref conn): Extension<DatabaseConnection>,
|
|
form: Form<post::Model>,
|
|
mut cookies: Cookies,
|
|
) -> Result<PostResponse, (StatusCode, &'static str)> {
|
|
let form = form.0;
|
|
|
|
MutationCore::create_post(conn, form)
|
|
.await
|
|
.expect("could not insert post");
|
|
|
|
let data = FlashData {
|
|
kind: "success".to_owned(),
|
|
message: "Post succcessfully added".to_owned(),
|
|
};
|
|
|
|
Ok(post_response(&mut cookies, data))
|
|
}
|
|
|
|
async fn edit_post(
|
|
Extension(ref templates): Extension<Tera>,
|
|
Extension(ref conn): Extension<DatabaseConnection>,
|
|
Path(id): Path<i32>,
|
|
) -> Result<Html<String>, (StatusCode, &'static str)> {
|
|
let post: post::Model = QueryCore::find_post_by_id(conn, id)
|
|
.await
|
|
.expect("could not find post")
|
|
.unwrap_or_else(|| panic!("could not find post with id {}", id));
|
|
|
|
let mut ctx = tera::Context::new();
|
|
ctx.insert("post", &post);
|
|
|
|
let body = templates
|
|
.render("edit.html.tera", &ctx)
|
|
.map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Template error"))?;
|
|
|
|
Ok(Html(body))
|
|
}
|
|
|
|
async fn update_post(
|
|
Extension(ref conn): Extension<DatabaseConnection>,
|
|
Path(id): Path<i32>,
|
|
form: Form<post::Model>,
|
|
mut cookies: Cookies,
|
|
) -> Result<PostResponse, (StatusCode, String)> {
|
|
let form = form.0;
|
|
|
|
MutationCore::update_post_by_id(conn, id, form)
|
|
.await
|
|
.expect("could not edit post");
|
|
|
|
let data = FlashData {
|
|
kind: "success".to_owned(),
|
|
message: "Post succcessfully updated".to_owned(),
|
|
};
|
|
|
|
Ok(post_response(&mut cookies, data))
|
|
}
|
|
|
|
async fn delete_post(
|
|
Extension(ref conn): Extension<DatabaseConnection>,
|
|
Path(id): Path<i32>,
|
|
mut cookies: Cookies,
|
|
) -> Result<PostResponse, (StatusCode, &'static str)> {
|
|
MutationCore::delete_post(conn, id)
|
|
.await
|
|
.expect("could not delete post");
|
|
|
|
let data = FlashData {
|
|
kind: "success".to_owned(),
|
|
message: "Post succcessfully deleted".to_owned(),
|
|
};
|
|
|
|
Ok(post_response(&mut cookies, data))
|
|
}
|
|
|
|
pub fn main() {
|
|
let result = start();
|
|
|
|
if let Some(err) = result.err() {
|
|
println!("Error: {}", err);
|
|
}
|
|
}
|