feat: add comments

image-uploads
Jef Roosens 2024-12-29 20:11:38 +01:00
parent fed9c01370
commit cc69935a88
No known key found for this signature in database
GPG Key ID: 21FD3D77D56BAF49
8 changed files with 56 additions and 4 deletions

View File

@ -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)?)
}
}

View File

@ -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>;

View File

@ -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?)

View File

@ -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();

View File

@ -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(),
)
}

View File

@ -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))

View File

@ -0,0 +1 @@
<li>{{ comment.comment }}</li>

View File

@ -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">