From f3cab8759299726da5599ab35a03693295fcc331 Mon Sep 17 00:00:00 2001 From: Imbus Date: Tue, 28 Nov 2023 00:32:38 +0100 Subject: [PATCH 01/13] Comments table draft --- server/migrations_pg/0003_comments_table.sql | 51 ++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 server/migrations_pg/0003_comments_table.sql diff --git a/server/migrations_pg/0003_comments_table.sql b/server/migrations_pg/0003_comments_table.sql new file mode 100644 index 0000000..19cdf61 --- /dev/null +++ b/server/migrations_pg/0003_comments_table.sql @@ -0,0 +1,51 @@ +CREATE TABLE IF NOT EXISTS comments ( + id BIGSERIAL PRIMARY KEY, + parent_post_id BIGINT NOT NULL, + parent_comment_id BIGINT, + author_user_id BIGINT NOT NULL, + content TEXT NOT NULL, + upvotes INTEGER NOT NULL DEFAULT 0, + downvotes INTEGER NOT NULL DEFAULT 0, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (parent_post_id) REFERENCES posts (id), + FOREIGN KEY (parent_comment_id) REFERENCES comments (id), + FOREIGN KEY (author_user_id) REFERENCES users (id) +); + +-- Create a function to set created_at and updated_at on INSERT +CREATE OR REPLACE FUNCTION comments_set_timestamps_on_insert() RETURNS TRIGGER AS $$ +BEGIN + NEW.created_at = NOW(); + NEW.updated_at = NOW(); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +-- Create a trigger to call the function after INSERT +CREATE TRIGGER comments_set_timestamps_on_insert +BEFORE INSERT ON posts +FOR EACH ROW +EXECUTE FUNCTION set_timestamps_on_insert(); + +-- Create a function to set updated_at on UPDATE +CREATE OR REPLACE FUNCTION comments_set_updated_at() RETURNS TRIGGER AS $$ +BEGIN + NEW.updated_at = NOW(); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +-- Create a trigger to call the function after UPDATE +CREATE TRIGGER comments_set_updated_at +BEFORE UPDATE ON posts +FOR EACH ROW +EXECUTE FUNCTION comments_set_updated_at(); + +CREATE INDEX comments_parent_post_id_index ON comments (parent_post_id); +CREATE INDEX comments_parent_comment_id_index ON comments (parent_comment_id); +CREATE INDEX comments_user_id_index ON comments (author_user_id); + +-- CREATE INDEX posts_user_id_index ON posts (user_id); +-- CREATE INDEX posts_id_index ON posts (id); +-- CREATE INDEX idx_created_at_desc ON posts (created_at DESC); \ No newline at end of file From 13c5a05bd6252e5c0b1571e9b421de3e3e0414be Mon Sep 17 00:00:00 2001 From: Imbus Date: Tue, 28 Nov 2023 00:33:52 +0100 Subject: [PATCH 02/13] Removed sqlite migraction scripts --- server/migrations/0001_users_table.sql | 31 --------------------- server/migrations/0002_posts_table.sql | 38 -------------------------- 2 files changed, 69 deletions(-) delete mode 100644 server/migrations/0001_users_table.sql delete mode 100644 server/migrations/0002_posts_table.sql diff --git a/server/migrations/0001_users_table.sql b/server/migrations/0001_users_table.sql deleted file mode 100644 index b9ccfdb..0000000 --- a/server/migrations/0001_users_table.sql +++ /dev/null @@ -1,31 +0,0 @@ -CREATE TABLE - IF NOT EXISTS users ( - id INTEGER PRIMARY KEY NOT NULL, - username TEXT NOT NULL UNIQUE, - password TEXT NOT NULL, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP - ); - --- Create a trigger to set created_at and updated_at on INSERT -CREATE TRIGGER IF NOT EXISTS set_created_at AFTER INSERT ON users BEGIN -UPDATE users -SET - created_at = CURRENT_TIMESTAMP -WHERE - id = NEW.id; - -END; - --- Create a trigger to set updated_at on UPDATE -CREATE TRIGGER IF NOT EXISTS set_updated_at AFTER -UPDATE ON users BEGIN -UPDATE users -SET - updated_at = CURRENT_TIMESTAMP -WHERE - id = NEW.id; - -END; - -CREATE INDEX users_username_index ON users (username); \ No newline at end of file diff --git a/server/migrations/0002_posts_table.sql b/server/migrations/0002_posts_table.sql deleted file mode 100644 index 1b9601b..0000000 --- a/server/migrations/0002_posts_table.sql +++ /dev/null @@ -1,38 +0,0 @@ -CREATE TABLE - IF NOT EXISTS posts ( - id INTEGER PRIMARY KEY NOT NULL, - user_id INTEGER NOT NULL, - content TEXT NOT NULL, - upvotes INTEGER NOT NULL DEFAULT 0, - downvotes INTEGER NOT NULL DEFAULT 0, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (user_id) REFERENCES users (id) - ); - --- Create a trigger to set created_at and updated_at on INSERT -CREATE TRIGGER IF NOT EXISTS set_created_at AFTER INSERT ON posts BEGIN -UPDATE posts -SET - created_at = CURRENT_TIMESTAMP -WHERE - id = NEW.id; - -END; - --- Create a trigger to set updated_at on UPDATE -CREATE TRIGGER IF NOT EXISTS set_updated_at AFTER -UPDATE ON posts BEGIN -UPDATE posts -SET - updated_at = CURRENT_TIMESTAMP -WHERE - id = NEW.id; - -END; - -create INDEX IF NOT EXISTS posts_user_id_index ON posts (user_id); - -create INDEX IF NOT EXISTS posts_id_index ON posts (id); - -CREATE INDEX idx_created_at_desc ON posts (created_at DESC); \ No newline at end of file From 1652419d9433af858b2491c1d0193d4bdd1e6502 Mon Sep 17 00:00:00 2001 From: Imbus Date: Tue, 28 Nov 2023 00:36:11 +0100 Subject: [PATCH 03/13] Moved migrations_pg -> migrations and updated relevant references --- justfile | 4 ++-- server/{migrations_pg => migrations}/0001_users_table.sql | 0 server/{migrations_pg => migrations}/0002_posts_table.sql | 0 server/{migrations_pg => migrations}/0003_comments_table.sql | 0 server/src/state.rs | 2 +- 5 files changed, 3 insertions(+), 3 deletions(-) rename server/{migrations_pg => migrations}/0001_users_table.sql (100%) rename server/{migrations_pg => migrations}/0002_posts_table.sql (100%) rename server/{migrations_pg => migrations}/0003_comments_table.sql (100%) diff --git a/justfile b/justfile index 20a3fa9..0b6f273 100644 --- a/justfile +++ b/justfile @@ -39,7 +39,7 @@ start-release: start-postgres-dev clean-podman init-sqlx build-container-release init-sqlx: echo {{env_local}} > server/.env cd server && sqlx database create --connect-timeout 40 # Postgres takes a while to start up - cd server && sqlx migrate run --source migrations_pg + cd server && sqlx migrate run cd server && cargo sqlx prepare # Starts a postgres container for development @@ -72,4 +72,4 @@ clean: clean-podman clean-images rm -rf client-solid/node_modules rm -rf server/public rm -rf server/target - @echo "Cleaned up! Make sure to clean up podman volumes and networks." \ No newline at end of file + @echo "Cleaned up! Make sure to clean up podman volumes and networks." diff --git a/server/migrations_pg/0001_users_table.sql b/server/migrations/0001_users_table.sql similarity index 100% rename from server/migrations_pg/0001_users_table.sql rename to server/migrations/0001_users_table.sql diff --git a/server/migrations_pg/0002_posts_table.sql b/server/migrations/0002_posts_table.sql similarity index 100% rename from server/migrations_pg/0002_posts_table.sql rename to server/migrations/0002_posts_table.sql diff --git a/server/migrations_pg/0003_comments_table.sql b/server/migrations/0003_comments_table.sql similarity index 100% rename from server/migrations_pg/0003_comments_table.sql rename to server/migrations/0003_comments_table.sql diff --git a/server/src/state.rs b/server/src/state.rs index aafca2b..ae75e23 100644 --- a/server/src/state.rs +++ b/server/src/state.rs @@ -43,7 +43,7 @@ impl ServerState { .await .unwrap(); - sqlx::migrate!("./migrations_pg").run(&pool).await.unwrap(); + sqlx::migrate!("./migrations").run(&pool).await.unwrap(); match crate::db::db_new_user("imbus".to_string(), "kartellen1234".to_string(), &pool).await { From 4abd2a3e43702f2a626c15c0f95810ba9f27f5b5 Mon Sep 17 00:00:00 2001 From: Imbus Date: Tue, 28 Nov 2023 00:37:09 +0100 Subject: [PATCH 04/13] Cleaning comments in migration script --- server/migrations/0003_comments_table.sql | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/server/migrations/0003_comments_table.sql b/server/migrations/0003_comments_table.sql index 19cdf61..bbb261a 100644 --- a/server/migrations/0003_comments_table.sql +++ b/server/migrations/0003_comments_table.sql @@ -44,8 +44,4 @@ EXECUTE FUNCTION comments_set_updated_at(); CREATE INDEX comments_parent_post_id_index ON comments (parent_post_id); CREATE INDEX comments_parent_comment_id_index ON comments (parent_comment_id); -CREATE INDEX comments_user_id_index ON comments (author_user_id); - --- CREATE INDEX posts_user_id_index ON posts (user_id); --- CREATE INDEX posts_id_index ON posts (id); --- CREATE INDEX idx_created_at_desc ON posts (created_at DESC); \ No newline at end of file +CREATE INDEX comments_user_id_index ON comments (author_user_id); \ No newline at end of file From 89ebbf9269f161047c5545e8cf1d19e0f094aeda Mon Sep 17 00:00:00 2001 From: Imbus Date: Tue, 28 Nov 2023 01:26:19 +0100 Subject: [PATCH 05/13] Added routes and database logic to handle comments --- ...f8609a54f8ba99bc55f208fb01cda5dd219f7.json | 72 ++++++++++++++ ...6e89a9851b372b39d68a10c16961acd968eef.json | 17 ++++ server/src/db.rs | 46 ++++++++- server/src/main.rs | 4 +- server/src/routes/comment.rs | 97 +++++++++++++++++++ server/src/routes/mod.rs | 2 + 6 files changed, 236 insertions(+), 2 deletions(-) create mode 100644 server/.sqlx/query-345472dbe81319923bf40fc39a1f8609a54f8ba99bc55f208fb01cda5dd219f7.json create mode 100644 server/.sqlx/query-fe72509852c87463cea9775d9606e89a9851b372b39d68a10c16961acd968eef.json create mode 100644 server/src/routes/comment.rs diff --git a/server/.sqlx/query-345472dbe81319923bf40fc39a1f8609a54f8ba99bc55f208fb01cda5dd219f7.json b/server/.sqlx/query-345472dbe81319923bf40fc39a1f8609a54f8ba99bc55f208fb01cda5dd219f7.json new file mode 100644 index 0000000..80b1d33 --- /dev/null +++ b/server/.sqlx/query-345472dbe81319923bf40fc39a1f8609a54f8ba99bc55f208fb01cda5dd219f7.json @@ -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" +} diff --git a/server/.sqlx/query-fe72509852c87463cea9775d9606e89a9851b372b39d68a10c16961acd968eef.json b/server/.sqlx/query-fe72509852c87463cea9775d9606e89a9851b372b39d68a10c16961acd968eef.json new file mode 100644 index 0000000..c95db9c --- /dev/null +++ b/server/.sqlx/query-fe72509852c87463cea9775d9606e89a9851b372b39d68a10c16961acd968eef.json @@ -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" +} diff --git a/server/src/db.rs b/server/src/db.rs index 3c52b93..1baed3e 100644 --- a/server/src/db.rs +++ b/server/src/db.rs @@ -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, + 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 { + 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 { sqlx::query_as!( diff --git a/server/src/main.rs b/server/src/main.rs index beba099..e57aaf0 100755 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -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) diff --git a/server/src/routes/comment.rs b/server/src/routes/comment.rs new file mode 100644 index 0000000..e67a2a2 --- /dev/null +++ b/server/src/routes/comment.rs @@ -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, + 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, + 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, + offset: Option, +} + +#[get("/comments")] +pub async fn get_comments( + commentiflter: Query, + state: Data, +) -> Result { + 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, + state: Data, +) -> Result { + 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")), + } +} diff --git a/server/src/routes/mod.rs b/server/src/routes/mod.rs index c02003b..b16b7fb 100644 --- a/server/src/routes/mod.rs +++ b/server/src/routes/mod.rs @@ -1,5 +1,7 @@ +mod comment; mod post; mod users; +pub use comment::*; pub use post::*; pub use users::*; From 1058c8b4774a63da5a8aee7864eed355b7959578 Mon Sep 17 00:00:00 2001 From: Imbus Date: Tue, 28 Nov 2023 02:19:49 +0100 Subject: [PATCH 06/13] Splitting all types into types directory --- server/src/db.rs | 2 +- server/src/main.rs | 1 + server/src/routes/comment.rs | 38 ++++--------------------------- server/src/routes/post.rs | 44 ++---------------------------------- server/src/routes/users.rs | 27 +--------------------- server/src/types/comment.rs | 33 +++++++++++++++++++++++++++ server/src/types/mod.rs | 7 ++++++ server/src/types/post.rs | 31 +++++++++++++++++++++++++ server/src/types/user.rs | 42 ++++++++++++++++++++++++++++++++++ 9 files changed, 123 insertions(+), 102 deletions(-) create mode 100644 server/src/types/comment.rs create mode 100644 server/src/types/mod.rs create mode 100644 server/src/types/post.rs create mode 100644 server/src/types/user.rs diff --git a/server/src/db.rs b/server/src/db.rs index 1baed3e..14e9db9 100644 --- a/server/src/db.rs +++ b/server/src/db.rs @@ -1,4 +1,4 @@ -use crate::routes::{Comment, Post, User}; +use crate::types::{Comment, Post, User}; use argon2::{ password_hash::{rand_core::OsRng, SaltString}, Argon2, PasswordHasher, PasswordVerifier, diff --git a/server/src/main.rs b/server/src/main.rs index e57aaf0..06ed9ce 100755 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -8,6 +8,7 @@ mod db; mod jwt; mod routes; mod state; +mod types; mod util; use routes::{get_comments, get_posts, login, new_comment, new_post, post_by_id, register}; diff --git a/server/src/routes/comment.rs b/server/src/routes/comment.rs index e67a2a2..3df7e00 100644 --- a/server/src/routes/comment.rs +++ b/server/src/routes/comment.rs @@ -1,49 +1,21 @@ use crate::db::{db_get_comments, db_new_comment}; use crate::jwt::validate_token; +use crate::types::{CommentQueryParams, NewComment}; 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, - 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, - 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, - offset: Option, -} #[get("/comments")] pub async fn get_comments( - commentiflter: Query, + comment_filter: Query, state: Data, ) -> Result { - let post_id = commentiflter.post_id; - let limit = commentiflter.limit.unwrap_or(10); - let offset = commentiflter.offset.unwrap_or(0); + let post_id = comment_filter.post_id; + let limit = comment_filter.limit.unwrap_or(10); + let offset = comment_filter.offset.unwrap_or(0); info!( "Getting comments for post {} with limit {} and offset {}", diff --git a/server/src/routes/post.rs b/server/src/routes/post.rs index 286ac56..0eb4ec5 100755 --- a/server/src/routes/post.rs +++ b/server/src/routes/post.rs @@ -1,57 +1,17 @@ use crate::db::{db_get_latest_posts, db_get_post, db_new_post}; use crate::jwt::validate_token; +use crate::types::{NewPost, PostQueryParams}; use crate::ServerState; use actix_web::web::{Data, Path, Query}; use actix_web::{get, post, web::Json, HttpResponse, Responder, Result}; use log::info; -use serde::{Deserialize, Serialize}; -use sqlx::FromRow; - -// The post as it is received from the client -// The token is used to identify the user -#[derive(Debug, Serialize, Deserialize)] -pub struct NewPost { - pub content: String, - pub token: String, -} - -// The post as it is stored in the database, with all the related metadata -#[derive(Debug, Serialize, Deserialize, Clone, FromRow)] -pub struct Post { - pub id: i64, - pub user_id: i64, - pub content: String, - pub upvotes: i64, - pub downvotes: i64, - pub created_at: chrono::NaiveDateTime, - pub updated_at: chrono::NaiveDateTime, -} - -/// The user as it is stored in the database, with all the related metadata -#[derive(Debug, Serialize, Deserialize, Clone, FromRow)] -pub struct User { - pub id: i64, - pub username: String, - pub password: String, - pub created_at: chrono::NaiveDateTime, - pub updated_at: chrono::NaiveDateTime, -} - -// These look like /posts?limit=10&offset=20 in the URL -// Note that these are optional -/// Query parameters for the /posts endpoint -#[derive(Debug, Serialize, Deserialize)] -pub struct QueryParams { - limit: Option, - offset: Option, -} /// Gets all posts from the database, query parameters are optional /// If limit is not specified, it defaults to a sane value #[get("/posts")] pub async fn get_posts( - query: Query, + query: Query, state: Data, ) -> Result { if let (Some(lim), Some(ofs)) = (query.limit, query.offset) { diff --git a/server/src/routes/users.rs b/server/src/routes/users.rs index 60cfc4f..21013dd 100755 --- a/server/src/routes/users.rs +++ b/server/src/routes/users.rs @@ -1,32 +1,13 @@ use crate::db::{db_new_user, db_user_login}; use crate::jwt::token_factory; use crate::state::CaptchaState; +use crate::types::{AuthResponse, LoginData, RegisterData}; use crate::ServerState; use actix_web::web::Data; use actix_web::{post, web::Json, HttpResponse, Responder, Result}; use biosvg::BiosvgBuilder; use log::*; -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Serialize, Deserialize)] -pub struct LoginData { - username: String, - password: String, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct AuthResponse { - username: String, - token: String, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct RegisterData { - username: String, - password: String, - captcha: String, -} #[post("/register")] pub async fn register( @@ -77,12 +58,6 @@ pub async fn login(data: Json, state: Data) -> Result, + pub user_token: String, + pub content: String, +} + +/// The comment as it is stored in the database, with all the related metadata +/// This is also the comment as it is sent to the client +#[derive(Debug, Serialize, Deserialize, Clone, sqlx::FromRow)] +pub struct Comment { + pub id: i64, + pub parent_post_id: i64, + pub parent_comment_id: Option, + pub author_user_id: i64, + pub upvotes: i64, + pub downvotes: i64, + pub content: String, + pub created_at: chrono::NaiveDateTime, + pub updated_at: chrono::NaiveDateTime, +} + +/// Query parameters for the /comments endpoint +#[derive(Debug, Serialize, Deserialize)] +pub struct CommentQueryParams { + pub post_id: i64, + pub limit: Option, + pub offset: Option, +} diff --git a/server/src/types/mod.rs b/server/src/types/mod.rs new file mode 100644 index 0000000..2cb8992 --- /dev/null +++ b/server/src/types/mod.rs @@ -0,0 +1,7 @@ +mod comment; +mod post; +mod user; + +pub use comment::*; +pub use post::*; +pub use user::*; diff --git a/server/src/types/post.rs b/server/src/types/post.rs new file mode 100644 index 0000000..f880766 --- /dev/null +++ b/server/src/types/post.rs @@ -0,0 +1,31 @@ +use serde::{Deserialize, Serialize}; +use sqlx::FromRow; + +// The post as it is received from the client +// The token is used to identify the user +#[derive(Debug, Serialize, Deserialize)] +pub struct NewPost { + pub content: String, + pub token: String, +} + +// The post as it is stored in the database, with all the related metadata +#[derive(Debug, Serialize, Deserialize, Clone, FromRow)] +pub struct Post { + pub id: i64, + pub user_id: i64, + pub content: String, + pub upvotes: i64, + pub downvotes: i64, + pub created_at: chrono::NaiveDateTime, + pub updated_at: chrono::NaiveDateTime, +} + +// These look like /posts?limit=10&offset=20 in the URL +// Note that these are optional +/// Query parameters for the /posts endpoint +#[derive(Debug, Serialize, Deserialize)] +pub struct PostQueryParams { + pub limit: Option, + pub offset: Option, +} diff --git a/server/src/types/user.rs b/server/src/types/user.rs new file mode 100644 index 0000000..4caa229 --- /dev/null +++ b/server/src/types/user.rs @@ -0,0 +1,42 @@ +use serde::{Deserialize, Serialize}; +use sqlx::FromRow; + +/// The user as it is stored in the database, with all the related metadata +#[derive(Debug, Serialize, Deserialize, Clone, FromRow)] +pub struct User { + pub id: i64, + pub username: String, + pub password: String, + pub created_at: chrono::NaiveDateTime, + pub updated_at: chrono::NaiveDateTime, +} + +/// The data recieved from the login form +#[derive(Debug, Serialize, Deserialize)] +pub struct LoginData { + pub username: String, + pub password: String, +} + +/// The data recieved from the registration form +#[derive(Debug, Serialize, Deserialize)] +pub struct RegisterData { + pub username: String, + pub password: String, + pub captcha: String, +} + +/// The response sent to the client after a successful login or registration +#[derive(Debug, Serialize, Deserialize)] +pub struct AuthResponse { + pub username: String, + pub token: String, +} + +/// Data sent to the client to render the captcha +/// The captcha_id is used to identify the captcha in the database +#[derive(Debug, Serialize, Deserialize)] +pub struct CaptchaResponse { + pub captcha_svg: String, + pub captcha_id: i32, +} From dc54accd2290e0b8b0279c7b06e1619090fe5aa1 Mon Sep 17 00:00:00 2001 From: Imbus Date: Tue, 28 Nov 2023 03:03:56 +0100 Subject: [PATCH 07/13] Included install sqlx-cli in justfile --- justfile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/justfile b/justfile index 0b6f273..44f5aa8 100644 --- a/justfile +++ b/justfile @@ -36,12 +36,15 @@ start-release: start-postgres-dev clean-podman init-sqlx build-container-release podman run -d --network {{network}} -e {{env_string}} -p 8080:8080 --name frostbyte fb-server # Initializes the database, runs migrations and then prepares sqlx -init-sqlx: +init-sqlx: install-sqlx echo {{env_local}} > server/.env cd server && sqlx database create --connect-timeout 40 # Postgres takes a while to start up cd server && sqlx migrate run cd server && cargo sqlx prepare +install-sqlx: + cargo install sqlx-cli + # Starts a postgres container for development [private] start-postgres-dev: create-network From d0b50a3a37b2951c29a6591454c0afc4d8b87156 Mon Sep 17 00:00:00 2001 From: Imbus Date: Tue, 28 Nov 2023 03:28:38 +0100 Subject: [PATCH 08/13] HTML meta tag to stop dark reader from breaking the style --- client-solid/index.html | 1 + 1 file changed, 1 insertion(+) diff --git a/client-solid/index.html b/client-solid/index.html index 540a3d2..af1abcf 100644 --- a/client-solid/index.html +++ b/client-solid/index.html @@ -3,6 +3,7 @@ + From 67b915d722bbe07cc11bd46249ff06df022c0a00 Mon Sep 17 00:00:00 2001 From: Imbus Date: Tue, 28 Nov 2023 04:20:00 +0100 Subject: [PATCH 09/13] Simple cors --- server/Cargo.lock | 16 ++++++++++++++++ server/Cargo.toml | 1 + server/src/main.rs | 7 +++++++ 3 files changed, 24 insertions(+) diff --git a/server/Cargo.lock b/server/Cargo.lock index bfc7374..7b87062 100755 --- a/server/Cargo.lock +++ b/server/Cargo.lock @@ -19,6 +19,21 @@ dependencies = [ "tracing", ] +[[package]] +name = "actix-cors" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b340e9cfa5b08690aae90fb61beb44e9b06f44fe3d0f93781aaa58cfba86245e" +dependencies = [ + "actix-utils", + "actix-web", + "derive_more", + "futures-util", + "log", + "once_cell", + "smallvec", +] + [[package]] name = "actix-files" version = "0.6.2" @@ -1840,6 +1855,7 @@ dependencies = [ name = "server" version = "0.1.0" dependencies = [ + "actix-cors", "actix-files", "actix-web", "argon2", diff --git a/server/Cargo.toml b/server/Cargo.toml index d3046e3..70899a1 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +actix-cors = "0.6.4" actix-files = "0.6.2" actix-web = "4.4.0" argon2 = { version = "0.5.2", features = ["zeroize"] } diff --git a/server/src/main.rs b/server/src/main.rs index 06ed9ce..7979244 100755 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -1,3 +1,4 @@ +use actix_cors::Cors; use actix_files::Files; use actix_web::middleware; use actix_web::web::Data; @@ -34,7 +35,13 @@ async fn main() -> std::io::Result<()> { info!("Spinning up server on http://localhost:8080"); HttpServer::new(move || { + let cors = Cors::default() + .allowed_origin("https://shitpost.se") + .allowed_methods(vec!["GET", "POST"]) + .max_age(3600); + App::new() + .wrap(cors) .wrap(middleware::Compress::default()) .wrap(middleware::Logger::default()) .wrap(middleware::NormalizePath::trim()) From 69e971980c0b5de7a0b0e28c826e6dca593cae60 Mon Sep 17 00:00:00 2001 From: Imbus Date: Sat, 9 Dec 2023 11:52:52 +0100 Subject: [PATCH 10/13] Footer content --- client-solid/src/Containers/Footer.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/client-solid/src/Containers/Footer.tsx b/client-solid/src/Containers/Footer.tsx index 7f0ceb2..9781d28 100644 --- a/client-solid/src/Containers/Footer.tsx +++ b/client-solid/src/Containers/Footer.tsx @@ -46,8 +46,7 @@ export function Footer(): JSXElement { From 7c0fdd142ff061d5171606482dc72a2c383bf900 Mon Sep 17 00:00:00 2001 From: Imbus Date: Sat, 9 Dec 2023 11:53:10 +0100 Subject: [PATCH 11/13] Post styling --- client-solid/src/Components/Posts.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client-solid/src/Components/Posts.tsx b/client-solid/src/Components/Posts.tsx index 596185a..ad4d8a9 100644 --- a/client-solid/src/Components/Posts.tsx +++ b/client-solid/src/Components/Posts.tsx @@ -28,7 +28,7 @@ export function PostSegment(props: { post: Post }): JSXElement { return (
-

{props.post?.content}

+

{props.post?.content}