From 2572fcbffd9b2733a184bf8799bf7a18b81aa616 Mon Sep 17 00:00:00 2001 From: Imbus Date: Sat, 21 Oct 2023 00:02:58 +0200 Subject: [PATCH 01/17] Added init-sqlx target to justfile, to quickly get sqlx up to speed --- justfile | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/justfile b/justfile index 5bce120..7c73356 100644 --- a/justfile +++ b/justfile @@ -33,6 +33,12 @@ build-container-release: start-release: build-container-release remove-podman-containers {{runtime}} run -d -p 8080:8080 --name frostbyte fb-server +init-sqlx: + echo "DATABASE_URL=sqlite:debug.db" > server/.env + cd server && sqlx database create + cd server && sqlx migrate run + cd server && cargo sqlx prepare + # Removes and stops any containers related to the project [private] remove-podman-containers: From cf607ae34556288bbee1839b0454841cd3a053ea Mon Sep 17 00:00:00 2001 From: Imbus Date: Sat, 21 Oct 2023 00:59:21 +0200 Subject: [PATCH 02/17] Added some triggers and formatting to the migration scripts --- server/migrations/0001_users_table.sql | 38 +++++++++++++++----- server/migrations/0002_posts_table.sql | 50 +++++++++++++++++++------- 2 files changed, 68 insertions(+), 20 deletions(-) diff --git a/server/migrations/0001_users_table.sql b/server/migrations/0001_users_table.sql index 76976d2..b9ccfdb 100644 --- a/server/migrations/0001_users_table.sql +++ b/server/migrations/0001_users_table.sql @@ -1,9 +1,31 @@ -CREATE TABLE IF NOT EXISTS users ( - id INTEGER PRIMARY KEY, - username TEXT NOT NULL, - password TEXT NOT NULL, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP -); +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 index users_username_index on users (username); \ No newline at end of file +-- 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 index c5d05e5..3863b63 100644 --- a/server/migrations/0002_posts_table.sql +++ b/server/migrations/0002_posts_table.sql @@ -1,12 +1,38 @@ -CREATE TABLE IF NOT EXISTS posts ( - id INTEGER PRIMARY KEY NOT NULL, - user_id INTEGER NOT NULL, - content TEXT NOT NULL, - upvotes INT NOT NULL DEFAULT 0, - downvotes INT 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 index IF NOT EXISTS posts_user_id_index on posts (user_id); -create index IF NOT EXISTS posts_id_index on posts (id); \ No newline at end of file +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 your_table (created_at DESC); \ No newline at end of file From db0783fe2e4ead50f5a6a66e79ce8406f644d246 Mon Sep 17 00:00:00 2001 From: Imbus Date: Sat, 21 Oct 2023 00:59:35 +0200 Subject: [PATCH 03/17] Slight improvement of the api --- server/src/db.rs | 2 +- server/src/routes/post.rs | 7 ++----- server/src/state.rs | 29 +++++++++-------------------- 3 files changed, 12 insertions(+), 26 deletions(-) diff --git a/server/src/db.rs b/server/src/db.rs index c2f0f39..65b74f4 100644 --- a/server/src/db.rs +++ b/server/src/db.rs @@ -1,6 +1,6 @@ use crate::routes::{NewPost, Post}; use log::warn; -use sqlx::{Row, SqlitePool}; +use sqlx::SqlitePool; // Gets all posts from the database pub async fn db_get_posts(pool: &SqlitePool) -> Vec { diff --git a/server/src/routes/post.rs b/server/src/routes/post.rs index cc7970c..0555e47 100755 --- a/server/src/routes/post.rs +++ b/server/src/routes/post.rs @@ -1,4 +1,4 @@ -use crate::db::db_new_post; +use crate::db::{db_get_posts, db_new_post}; use crate::ServerState; use actix_web::web::Data; @@ -38,10 +38,7 @@ pub struct User { #[get("/posts")] pub async fn get_posts(state: Data) -> Result { - let stream = sqlx::query_as!(Post, "SELECT * FROM posts"); - - let posts = stream.fetch_all(&state.pool).await.unwrap(); - Ok(HttpResponse::Ok().json(posts)) + Ok(HttpResponse::Ok().json(db_get_posts(&state.pool).await)) } #[post("/posts")] diff --git a/server/src/state.rs b/server/src/state.rs index 783b08d..fbda6c8 100644 --- a/server/src/state.rs +++ b/server/src/state.rs @@ -23,7 +23,7 @@ impl ServerState { sqlx::migrate!("./migrations").run(&pool).await.unwrap(); #[cfg(debug_assertions)] - debug_setup(&pool).await; + debug_setup(&pool).await.unwrap(); Self { pool } } @@ -32,27 +32,16 @@ impl ServerState { // Inserts a bunch of dummy data into the database // Mostly useful for debugging new posts, as we need to satisfy foreign key constraints. #[cfg(debug_assertions)] -async fn debug_setup(pool: &SqlitePool) { - use chrono::NaiveDateTime; +async fn debug_setup(pool: &SqlitePool) -> Result<(), sqlx::Error> { use sqlx::query; - let now = NaiveDateTime::from_timestamp(0, 0); + query!("INSERT INTO users (username, password) VALUES ('testuser', 'testpassword')",) + .execute(pool) + .await?; - query!( - "INSERT INTO users (username, password, created_at, updated_at) VALUES ('test', 'test', ?, ?)", - now, - now - ) - .execute(pool) - .await - .unwrap(); + query!("INSERT INTO posts (user_id, content) VALUES (1, 'Hello world!')",) + .execute(pool) + .await?; - query!( - "INSERT INTO posts (user_id, content, created_at, updated_at) VALUES (1, 'Hello world!', ?, ?)", - now, - now - ) - .execute(pool) - .await - .unwrap(); + Ok(()) } From 204ed8ec41540e246095ff80ab1f5f85b5248b9d Mon Sep 17 00:00:00 2001 From: Imbus Date: Sat, 21 Oct 2023 01:55:07 +0200 Subject: [PATCH 04/17] Fixing typo --- server/migrations/0002_posts_table.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/migrations/0002_posts_table.sql b/server/migrations/0002_posts_table.sql index 3863b63..1b9601b 100644 --- a/server/migrations/0002_posts_table.sql +++ b/server/migrations/0002_posts_table.sql @@ -35,4 +35,4 @@ 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 your_table (created_at DESC); \ No newline at end of file +CREATE INDEX idx_created_at_desc ON posts (created_at DESC); \ No newline at end of file From 4fc00eaf23596fed6fcfb6df2836a88ad904a206 Mon Sep 17 00:00:00 2001 From: Imbus Date: Sat, 21 Oct 2023 01:55:27 +0200 Subject: [PATCH 05/17] Tidying up routes, query parameters for posts, better database api --- server/src/db.rs | 17 +++++++++++------ server/src/jwt.rs | 4 ---- server/src/routes/post.rs | 30 +++++++++++++++++++++++++----- server/src/routes/users.rs | 4 ---- server/src/state.rs | 4 +++- 5 files changed, 39 insertions(+), 20 deletions(-) diff --git a/server/src/db.rs b/server/src/db.rs index 65b74f4..710fb97 100644 --- a/server/src/db.rs +++ b/server/src/db.rs @@ -2,12 +2,17 @@ use crate::routes::{NewPost, Post}; use log::warn; use sqlx::SqlitePool; -// Gets all posts from the database -pub async fn db_get_posts(pool: &SqlitePool) -> Vec { - sqlx::query_as!(Post, "SELECT * FROM posts") - .fetch_all(pool) - .await - .unwrap() +// Gets the latest posts from the database, ordered by created_at +pub async fn db_get_latest_posts(pool: &SqlitePool, limit: i64, offset: i64) -> Vec { + sqlx::query_as!( + Post, + "SELECT * FROM posts ORDER BY created_at DESC LIMIT ? OFFSET ?", + limit, + offset + ) + .fetch_all(pool) + .await + .unwrap() } // Inserts a new post to the database diff --git a/server/src/jwt.rs b/server/src/jwt.rs index 2e4bc44..5989cf9 100755 --- a/server/src/jwt.rs +++ b/server/src/jwt.rs @@ -1,7 +1,3 @@ -// use crate::{ -// config::{DAYS_VALID, JWT_SECRET}, -// Claims, -// }; use jsonwebtoken::{ decode, encode, errors::Result as JwtResult, DecodingKey, EncodingKey, Header, Validation, }; diff --git a/server/src/routes/post.rs b/server/src/routes/post.rs index 0555e47..2b71ca1 100755 --- a/server/src/routes/post.rs +++ b/server/src/routes/post.rs @@ -1,7 +1,7 @@ -use crate::db::{db_get_posts, db_new_post}; +use crate::db::{db_get_latest_posts, db_new_post}; use crate::ServerState; -use actix_web::web::Data; +use actix_web::web::{Data, Query}; use actix_web::{get, post, web::Json, HttpResponse, Responder, Result}; use log::info; use serde::{Deserialize, Serialize}; @@ -27,6 +27,7 @@ pub struct Post { 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, @@ -36,11 +37,30 @@ pub struct User { pub updated_at: chrono::NaiveDateTime, } -#[get("/posts")] -pub async fn get_posts(state: Data) -> Result { - Ok(HttpResponse::Ok().json(db_get_posts(&state.pool).await)) +// 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)] +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, + state: Data, +) -> Result { + if let (Some(lim), Some(ofs)) = (query.limit, query.offset) { + return Ok(HttpResponse::Ok() + .json(db_get_latest_posts(&state.pool, std::cmp::min(lim, 30), ofs).await)); + } + Ok(HttpResponse::Ok().json(db_get_latest_posts(&state.pool, 30, 0).await)) +} + +/// Creates a new post, requires a token in release mode #[post("/posts")] pub async fn new_post(new_post: Json, state: Data) -> Result { return match db_new_post(new_post.into_inner(), &state.pool).await { diff --git a/server/src/routes/users.rs b/server/src/routes/users.rs index e5cdec8..62f504b 100755 --- a/server/src/routes/users.rs +++ b/server/src/routes/users.rs @@ -74,10 +74,6 @@ pub async fn register( #[post("/login")] pub async fn login(data: Json, state: Data) -> Result { - // let q = "SELECT password FROM users WHERE username = ?"; - // let query = sqlx::query(q).bind(&data.username); - // let result = query.fetch_one(&state.pool).await.ok(); - let uname = data.username.clone(); let q = sqlx::query!("SELECT password FROM users WHERE username = ?", uname) .fetch_one(&state.pool) diff --git a/server/src/state.rs b/server/src/state.rs index fbda6c8..f43f91b 100644 --- a/server/src/state.rs +++ b/server/src/state.rs @@ -35,10 +35,12 @@ impl ServerState { async fn debug_setup(pool: &SqlitePool) -> Result<(), sqlx::Error> { use sqlx::query; - query!("INSERT INTO users (username, password) VALUES ('testuser', 'testpassword')",) + // Or ignore is just to silence the error if the user already exists + query!("INSERT OR IGNORE INTO users (username, password) VALUES ('testuser', 'testpassword')",) .execute(pool) .await?; + // This requires that the user with id 1 exists in the user table query!("INSERT INTO posts (user_id, content) VALUES (1, 'Hello world!')",) .execute(pool) .await?; From 4c398a40f6c55cf4274ae248b1990d798f6fd534 Mon Sep 17 00:00:00 2001 From: Imbus Date: Sat, 21 Oct 2023 01:55:47 +0200 Subject: [PATCH 06/17] Removed unused menu entries --- client-solid/src/Root.tsx | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/client-solid/src/Root.tsx b/client-solid/src/Root.tsx index 254424a..60c0b15 100644 --- a/client-solid/src/Root.tsx +++ b/client-solid/src/Root.tsx @@ -26,7 +26,7 @@ function Root() { function Navbar() { return (