Added routes and database logic to handle comments

This commit is contained in:
Imbus 2023-11-28 01:26:19 +01:00
parent 4abd2a3e43
commit 89ebbf9269
6 changed files with 236 additions and 2 deletions

View file

@ -0,0 +1,72 @@
{
"db_name": "PostgreSQL",
"query": "SELECT * FROM comments WHERE parent_post_id = $1 ORDER BY created_at DESC LIMIT $2 OFFSET $3",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int8"
},
{
"ordinal": 1,
"name": "parent_post_id",
"type_info": "Int8"
},
{
"ordinal": 2,
"name": "parent_comment_id",
"type_info": "Int8"
},
{
"ordinal": 3,
"name": "author_user_id",
"type_info": "Int8"
},
{
"ordinal": 4,
"name": "content",
"type_info": "Text"
},
{
"ordinal": 5,
"name": "upvotes",
"type_info": "Int4"
},
{
"ordinal": 6,
"name": "downvotes",
"type_info": "Int4"
},
{
"ordinal": 7,
"name": "created_at",
"type_info": "Timestamp"
},
{
"ordinal": 8,
"name": "updated_at",
"type_info": "Timestamp"
}
],
"parameters": {
"Left": [
"Int8",
"Int8",
"Int8"
]
},
"nullable": [
false,
false,
true,
false,
false,
false,
false,
false,
false
]
},
"hash": "345472dbe81319923bf40fc39a1f8609a54f8ba99bc55f208fb01cda5dd219f7"
}

View file

@ -0,0 +1,17 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO comments (parent_post_id, parent_comment_id, author_user_id, content) VALUES ($1, $2, $3, $4)",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int8",
"Int8",
"Int8",
"Text"
]
},
"nullable": []
},
"hash": "fe72509852c87463cea9775d9606e89a9851b372b39d68a10c16961acd968eef"
}

View file

@ -1,4 +1,4 @@
use crate::routes::{Post, User};
use crate::routes::{Comment, Post, User};
use argon2::{
password_hash::{rand_core::OsRng, SaltString},
Argon2, PasswordHasher, PasswordVerifier,
@ -6,6 +6,50 @@ use argon2::{
use log::{info, warn};
use sqlx::PgPool;
pub async fn db_new_comment(
pool: &PgPool,
parent_post_id: i64,
parent_comment_id: Option<i64>,
user_id: i64,
content: &str,
) -> bool {
let insert_query = sqlx::query!(
"INSERT INTO comments (parent_post_id, parent_comment_id, author_user_id, content) VALUES ($1, $2, $3, $4)",
parent_post_id,
parent_comment_id.unwrap_or(-1), // This is a bit of a hack
user_id,
content
)
.execute(pool)
.await;
if insert_query.is_err() {
let s = insert_query.err().unwrap();
warn!("Error inserting comment into database: {}", s);
return false;
}
true
}
pub async fn db_get_comments(
pool: &PgPool,
parent_post_id: i64,
limit: i64,
offset: i64,
) -> Vec<Comment> {
sqlx::query_as!(
Comment,
"SELECT * FROM comments WHERE parent_post_id = $1 ORDER BY created_at DESC LIMIT $2 OFFSET $3",
parent_post_id,
limit,
offset
)
.fetch_all(pool)
.await
.unwrap()
}
/// Gets the latest posts from the database, ordered by created_at
pub async fn db_get_latest_posts(pool: &PgPool, limit: i64, offset: i64) -> Vec<Post> {
sqlx::query_as!(

View file

@ -10,7 +10,7 @@ mod routes;
mod state;
mod util;
use routes::{get_posts, login, new_post, post_by_id, register};
use routes::{get_comments, get_posts, login, new_comment, new_post, post_by_id, register};
use state::CaptchaState;
use state::ServerState;
use util::hex_string;
@ -41,6 +41,8 @@ async fn main() -> std::io::Result<()> {
scope("/api")
.service(get_posts)
.service(new_post)
.service(new_comment)
.service(get_comments)
.service(post_by_id)
.service(login)
.service(register)

View file

@ -0,0 +1,97 @@
use crate::db::{db_get_comments, db_new_comment};
use crate::jwt::validate_token;
use crate::ServerState;
use actix_web::get;
use actix_web::web::{Data, Query};
use actix_web::{post, web::Json, HttpResponse, Responder, Result};
use log::info;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
pub struct NewComment {
pub parent_post_id: i64,
pub parent_comment_id: Option<i64>,
pub user_token: String,
pub content: String,
}
#[derive(Debug, Serialize, Deserialize, Clone, sqlx::FromRow)]
pub struct Comment {
pub id: i64,
pub parent_post_id: i64,
pub parent_comment_id: Option<i64>,
pub author_user_id: i64,
pub upvotes: i64,
pub downvotes: i64,
pub content: String,
pub created_at: chrono::NaiveDateTime,
pub updated_at: chrono::NaiveDateTime,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct CommentQueryParams {
post_id: i64,
limit: Option<i64>,
offset: Option<i64>,
}
#[get("/comments")]
pub async fn get_comments(
commentiflter: Query<CommentQueryParams>,
state: Data<ServerState>,
) -> Result<impl Responder> {
let post_id = commentiflter.post_id;
let limit = commentiflter.limit.unwrap_or(10);
let offset = commentiflter.offset.unwrap_or(0);
info!(
"Getting comments for post {} with limit {} and offset {}",
post_id, limit, offset
);
let comments = db_get_comments(&state.pool, post_id, limit, offset).await;
Ok(HttpResponse::Ok().json(comments))
}
#[post("/comments")]
pub async fn new_comment(
data: Json<NewComment>,
state: Data<ServerState>,
) -> Result<impl Responder> {
let user_claims = validate_token(&data.user_token);
// Bail if the token is invalid
if let Err(e) = user_claims {
info!("Error validating token: {}", e);
return Ok(HttpResponse::BadRequest().json("Error"));
}
let claims = user_claims.unwrap();
info!("User {:?} created a new comment", &claims.sub);
let content = data.content.clone();
let username = claims.sub.clone();
// This one is avoidable if we just store the user id in the token
let userid = sqlx::query!("SELECT id FROM users WHERE username = $1", username)
.fetch_one(&state.pool)
.await
.unwrap()
.id;
let success = db_new_comment(
&state.pool,
data.parent_post_id,
data.parent_comment_id,
userid,
&content,
)
.await;
match success {
true => Ok(HttpResponse::Ok().json("Success")),
false => Ok(HttpResponse::BadRequest().json("Error")),
}
}

View file

@ -1,5 +1,7 @@
mod comment;
mod post;
mod users;
pub use comment::*;
pub use post::*;
pub use users::*;