Added routes and database logic to handle comments
This commit is contained in:
		
							parent
							
								
									4abd2a3e43
								
							
						
					
					
						commit
						89ebbf9269
					
				
					 6 changed files with 236 additions and 2 deletions
				
			
		
							
								
								
									
										72
									
								
								server/.sqlx/query-345472dbe81319923bf40fc39a1f8609a54f8ba99bc55f208fb01cda5dd219f7.json
									
										
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								server/.sqlx/query-345472dbe81319923bf40fc39a1f8609a54f8ba99bc55f208fb01cda5dd219f7.json
									
										
									
										generated
									
									
									
										Normal 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" | ||||||
|  | } | ||||||
							
								
								
									
										17
									
								
								server/.sqlx/query-fe72509852c87463cea9775d9606e89a9851b372b39d68a10c16961acd968eef.json
									
										
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								server/.sqlx/query-fe72509852c87463cea9775d9606e89a9851b372b39d68a10c16961acd968eef.json
									
										
									
										generated
									
									
									
										Normal 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" | ||||||
|  | } | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| use crate::routes::{Post, User}; | use crate::routes::{Comment, Post, User}; | ||||||
| use argon2::{ | use argon2::{ | ||||||
|     password_hash::{rand_core::OsRng, SaltString}, |     password_hash::{rand_core::OsRng, SaltString}, | ||||||
|     Argon2, PasswordHasher, PasswordVerifier, |     Argon2, PasswordHasher, PasswordVerifier, | ||||||
|  | @ -6,6 +6,50 @@ use argon2::{ | ||||||
| use log::{info, warn}; | use log::{info, warn}; | ||||||
| use sqlx::PgPool; | 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
 | /// 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> { | pub async fn db_get_latest_posts(pool: &PgPool, limit: i64, offset: i64) -> Vec<Post> { | ||||||
|     sqlx::query_as!( |     sqlx::query_as!( | ||||||
|  |  | ||||||
|  | @ -10,7 +10,7 @@ mod routes; | ||||||
| mod state; | mod state; | ||||||
| mod util; | 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::CaptchaState; | ||||||
| use state::ServerState; | use state::ServerState; | ||||||
| use util::hex_string; | use util::hex_string; | ||||||
|  | @ -41,6 +41,8 @@ async fn main() -> std::io::Result<()> { | ||||||
|                 scope("/api") |                 scope("/api") | ||||||
|                     .service(get_posts) |                     .service(get_posts) | ||||||
|                     .service(new_post) |                     .service(new_post) | ||||||
|  |                     .service(new_comment) | ||||||
|  |                     .service(get_comments) | ||||||
|                     .service(post_by_id) |                     .service(post_by_id) | ||||||
|                     .service(login) |                     .service(login) | ||||||
|                     .service(register) |                     .service(register) | ||||||
|  |  | ||||||
							
								
								
									
										97
									
								
								server/src/routes/comment.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								server/src/routes/comment.rs
									
										
									
									
									
										Normal 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")), | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -1,5 +1,7 @@ | ||||||
|  | mod comment; | ||||||
| mod post; | mod post; | ||||||
| mod users; | mod users; | ||||||
| 
 | 
 | ||||||
|  | pub use comment::*; | ||||||
| pub use post::*; | pub use post::*; | ||||||
| pub use users::*; | pub use users::*; | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Imbus
						Imbus