feat: add comments
parent
fed9c01370
commit
cc69935a88
|
@ -1,6 +1,8 @@
|
|||
use r2d2_sqlite::rusqlite::{self, Row};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::{DbError, DbPool};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Comment {
|
||||
id: i32,
|
||||
|
@ -8,6 +10,12 @@ pub struct Comment {
|
|||
comment: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct NewComment {
|
||||
plant_id: i32,
|
||||
comment: String,
|
||||
}
|
||||
|
||||
impl Comment {
|
||||
pub fn from_row(row: &Row<'_>) -> Result<Self, rusqlite::Error> {
|
||||
Ok(Self {
|
||||
|
@ -17,3 +25,13 @@ impl Comment {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl NewComment {
|
||||
pub fn insert(self, pool: &DbPool) -> Result<Comment, DbError> {
|
||||
let conn = pool.get()?;
|
||||
|
||||
let mut stmt =
|
||||
conn.prepare("insert into comments (plant_id, comment) values ($1, $2) returning *")?;
|
||||
Ok(stmt.query_row((self.plant_id, self.comment), Comment::from_row)?)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ use r2d2_sqlite::{rusqlite, SqliteConnectionManager};
|
|||
|
||||
use std::{error::Error, fmt};
|
||||
|
||||
pub use comment::Comment;
|
||||
pub use comment::{Comment, NewComment};
|
||||
pub use plant::{NewPlant, Plant};
|
||||
|
||||
pub type DbPool = r2d2::Pool<SqliteConnectionManager>;
|
||||
|
|
|
@ -43,7 +43,7 @@ impl Plant {
|
|||
|
||||
pub fn comments(&self, pool: &DbPool) -> Result<Vec<Comment>, DbError> {
|
||||
let conn = pool.get()?;
|
||||
let mut stmt = conn.prepare("select * from plant_comments where plant_id = $1")?;
|
||||
let mut stmt = conn.prepare("select * from comments where plant_id = $1")?;
|
||||
|
||||
let comments: Result<Vec<_>, _> = stmt.query_map((self.id,), Comment::from_row)?.collect();
|
||||
Ok(comments?)
|
||||
|
|
|
@ -99,6 +99,10 @@ fn load_templates() -> Tera {
|
|||
"partials/plant_info.html",
|
||||
include_str!("templates/partials/plant_info.html"),
|
||||
),
|
||||
(
|
||||
"partials/comment_li.html",
|
||||
include_str!("templates/partials/comment_li.html"),
|
||||
),
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
use axum::{extract::State, response::Html, routing::post, Form, Router};
|
||||
use tera::Context;
|
||||
|
||||
use crate::db::{Comment, NewComment};
|
||||
|
||||
pub fn app(ctx: crate::Context) -> axum::Router {
|
||||
Router::new().route("/", post(post_comment)).with_state(ctx)
|
||||
}
|
||||
|
||||
async fn post_comment(
|
||||
State(ctx): State<crate::Context>,
|
||||
Form(comment): Form<NewComment>,
|
||||
) -> Html<String> {
|
||||
let comment = tokio::task::spawn_blocking(move || comment.insert(&ctx.pool))
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
let mut context = Context::new();
|
||||
context.insert("comment", &comment);
|
||||
Html(
|
||||
ctx.tera
|
||||
.render("partials/comment_li.html", &context)
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
mod comments;
|
||||
mod plants;
|
||||
|
||||
use axum::{
|
||||
|
@ -29,7 +30,8 @@ pub fn app(ctx: crate::Context) -> axum::Router {
|
|||
let mut router = Router::new()
|
||||
.route("/", get(get_index))
|
||||
.with_state(ctx.clone())
|
||||
.nest("/plants", plants::app(ctx.clone()));
|
||||
.nest("/plants", plants::app(ctx.clone()))
|
||||
.nest("/comments", comments::app(ctx.clone()));
|
||||
|
||||
for (name, content) in crate::STATIC_FILES {
|
||||
router = router.route(&format!("/static/{}", name), get(content))
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<li>{{ comment.comment }}</li>
|
|
@ -12,7 +12,8 @@
|
|||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<form hx-post="/plants/{{ plant.id }}/comments" hx-target="#comments" hx-swap="beforeend">
|
||||
<form hx-post="/comments" hx-target="#comments" hx-swap="beforeend">
|
||||
<input type="hidden" id="plant_id" name="plant_id" value="{{ plant.id }}">
|
||||
<label for="comment">Comment:</label>
|
||||
<textarea id="comment" name="comment" rows=4></textarea></br>
|
||||
<input type="submit">
|
||||
|
|
Loading…
Reference in New Issue