From 7462a365156e1dd3598feac2c5d6b179c9255539 Mon Sep 17 00:00:00 2001 From: SadlyNotSappho Date: Fri, 5 Apr 2024 11:24:41 -0700 Subject: [PATCH] clean up tables --- src/lib.rs | 1 - src/main.rs | 3 +- src/routes/images.rs | 2 +- src/routes/mod.rs | 1 + src/routes/posts.rs | 0 src/routes/users.rs | 4 +- src/tables.rs | 536 ------------------------------------------- src/tables/images.rs | 97 ++++++++ src/tables/mod.rs | 9 + src/tables/posts.rs | 251 ++++++++++++++++++++ src/tables/users.rs | 193 ++++++++++++++++ 11 files changed, 556 insertions(+), 541 deletions(-) create mode 100644 src/routes/posts.rs delete mode 100644 src/tables.rs create mode 100644 src/tables/images.rs create mode 100644 src/tables/mod.rs create mode 100644 src/tables/posts.rs create mode 100644 src/tables/users.rs diff --git a/src/lib.rs b/src/lib.rs index 7783a90..64fbd5e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,2 @@ pub mod tables; - pub mod routes; diff --git a/src/main.rs b/src/main.rs index 0eedbcf..223c22e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,7 +15,7 @@ use rocket_db_pools::{ }; use fossil::routes; -use fossil::tables::{Db, Post}; +use fossil::tables::{posts::Post, Db}; #[catch(default)] fn default_catcher(status: Status, _: &Request) -> RawHtml { @@ -158,6 +158,7 @@ async fn main() { routes::images::upload, routes::images::delete_image, routes::images::get_images_by_username, + // posts stuff ], ) .mount("/api", routes![routes::users::api_perms]) diff --git a/src/routes/images.rs b/src/routes/images.rs index 4e626e0..6f0e2d1 100644 --- a/src/routes/images.rs +++ b/src/routes/images.rs @@ -1,4 +1,4 @@ -use crate::tables::{Db, Image, LoginStatus, User}; +use crate::tables::{images::Image, users::LoginStatus, users::User, Db}; use image::io::Reader; use rocket::fs::NamedFile; use rocket::fs::TempFile; diff --git a/src/routes/mod.rs b/src/routes/mod.rs index 6bf2a4e..a81f457 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -1,3 +1,4 @@ pub mod images; pub mod users; pub mod web; +pub mod posts; diff --git a/src/routes/posts.rs b/src/routes/posts.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/routes/users.rs b/src/routes/users.rs index 827cf88..34d7422 100644 --- a/src/routes/users.rs +++ b/src/routes/users.rs @@ -8,7 +8,7 @@ use std::fs; use rocket::http::CookieJar; use rocket::serde::{json::Json, Deserialize}; -use crate::tables::{Db, User}; +use crate::tables::{users::User, Db}; use rocket::{get, post}; #[derive(Deserialize)] @@ -58,7 +58,7 @@ pub async fn createuser( }, Err(why) => { format!("Couldn't create user: {}", why); - status::Custom(Status::InternalServerError, format!("{why}")) + status::Custom(Status::InternalServerError, why.to_string()) } }, }, diff --git a/src/tables.rs b/src/tables.rs deleted file mode 100644 index c10e589..0000000 --- a/src/tables.rs +++ /dev/null @@ -1,536 +0,0 @@ -use base64::{engine::general_purpose, Engine}; -use rand::{RngCore, SeedableRng}; -use regex::Regex; -use rocket::http::{Cookie, CookieJar}; -use rocket_db_pools::sqlx::Executor; -use rocket_db_pools::Connection; -use rocket_db_pools::{ - sqlx::{self, PgPool, Row}, - Database, -}; -use sqlx::FromRow; - -#[derive(Database)] -#[database("fossil_postgres")] -pub struct Db(PgPool); - -#[derive(FromRow)] -pub struct Post { - pub id: i32, - pub uuid: String, - pub text_id: String, - pub title: String, - pub body: String, - pub published: bool, -} -impl Post { - pub async fn create( - mut db: Connection, - title: String, /*ex: Why Trans People Deserve All Your Money*/ - body: String, /*ex: # Because we're cooler than you */ - published: bool, - uuid: String, - text_id: String, /*ex: why-trans-people-deserve-all-your-money */ - ) { - match db - .fetch_all( - sqlx::query( - r#" - INSERT INTO posts (title, body, published, uuid, text_id) - VALUES ($1, $2, $3, $4, $5); - "#, - ) - .bind(title) - .bind(body) - .bind(published) - .bind(uuid) - .bind(text_id), - ) - .await - { - Ok(_) => (), - Err(why) => { - eprintln!("Couldn't create database entry: {why:?}"); - } - } - } - pub async fn get(mut db: Connection, id: i32) -> Post { - let res = db - .fetch_one(sqlx::query("SELECT * FROM posts WHERE id = $1;").bind(id)) - .await - .unwrap(); - Post { - id: res.get::("id"), - uuid: res.get::("uuid"), - text_id: res.get::("text_id"), - title: res.get::("title"), - body: res.get::("body"), - published: res.get::("published"), - } - } - pub async fn get_by_uuid(mut db: Connection, uuid: String) -> Post { - let res = db - .fetch_one(sqlx::query("SELECT * FROM posts WHERE uuid = $1;").bind(uuid)) - .await - .unwrap(); - Post { - id: res.get::("id"), - uuid: res.get::("uuid"), - text_id: res.get::("text_id"), - title: res.get::("title"), - body: res.get::("body"), - published: res.get::("published"), - } - } - pub async fn get_by_text_id(mut db: Connection, text_id: String) -> Post { - let res = db - .fetch_one(sqlx::query("SELECT * FROM posts WHERE text_id = $1;").bind(text_id)) - .await - .unwrap(); - Post { - id: res.get::("id"), - uuid: res.get::("uuid"), - text_id: res.get::("text_id"), - title: res.get::("title"), - body: res.get::("body"), - published: res.get::("published"), - } - } - - pub async fn edit_body( - mut db: Connection, - id: i32, - new_body: String, - ) -> Result<(), String> { - match db - .execute( - sqlx::query("UPDATE posts SET body = $1 WHERE id = $2;") - .bind(new_body) - .bind(id), - ) - .await - { - Ok(_) => Ok(()), - Err(why) => { - eprintln!("{why:?}"); - Err("Couldn't update post".to_string()) - } - } - } - pub async fn edit_body_by_uuid( - mut db: Connection, - uuid: String, - new_body: String, - ) -> Result<(), String> { - match db - .execute( - sqlx::query("UPDATE posts SET body = $1 WHERE uuid = $2;") - .bind(new_body) - .bind(uuid), - ) - .await - { - Ok(_) => Ok(()), - Err(why) => { - eprintln!("{why:?}"); - Err("Couldn't update post".to_string()) - } - } - } - pub async fn edit_body_by_text_id( - mut db: Connection, - text_id: String, - new_body: String, - ) -> Result<(), String> { - match db - .execute( - sqlx::query("UPDATE posts SET body = $1 WHERE text_id = $2;") - .bind(new_body) - .bind(text_id), - ) - .await - { - Ok(_) => Ok(()), - Err(why) => { - eprintln!("{why:?}"); - Err("Couldn't update post".to_string()) - } - } - } - pub async fn edit_title( - mut db: Connection, - id: i32, - new_title: String, - ) -> Result<(), String> { - match db - .execute( - sqlx::query("UPDATE posts SET title = $1 WHERE id = $2;") - .bind(new_title) - .bind(id), - ) - .await - { - Ok(_) => Ok(()), - Err(why) => { - eprintln!("{why:?}"); - Err("Couldn't update post".to_string()) - } - } - } - pub async fn edit_title_by_uuid( - mut db: Connection, - uuid: String, - new_title: String, - ) -> Result<(), String> { - match db - .execute( - sqlx::query("UPDATE posts SET title = $1 WHERE uuid = $2;") - .bind(new_title) - .bind(uuid), - ) - .await - { - Ok(_) => Ok(()), - Err(why) => { - eprintln!("{why:?}"); - Err("Couldn't update post".to_string()) - } - } - } - pub async fn edit_title_by_text_id( - mut db: Connection, - text_id: String, - new_title: String, - ) -> Result<(), String> { - match db - .execute( - sqlx::query("UPDATE posts SET body = $1 WHERE text_id = $2;") - .bind(new_title) - .bind(text_id), - ) - .await - { - Ok(_) => Ok(()), - Err(why) => { - eprintln!("{why:?}"); - Err("Couldn't update post".to_string()) - } - } - } - pub async fn edit_text_id( - mut db: Connection, - id: i32, - new_text_id: String, - ) -> Result<(), String> { - match db - .execute( - sqlx::query("UPDATE posts SET text_id = $1 WHERE id = $2;") - .bind(new_text_id) - .bind(id), - ) - .await - { - Ok(_) => Ok(()), - Err(why) => { - eprintln!("{why:?}"); - Err("Couldn't update post".to_string()) - } - } - } - pub async fn edit_text_id_by_uuid( - mut db: Connection, - uuid: String, - new_text_id: String, - ) -> Result<(), String> { - match db - .execute( - sqlx::query("UPDATE posts SET text_id = $1 WHERE uuid = $2;") - .bind(new_text_id) - .bind(uuid), - ) - .await - { - Ok(_) => Ok(()), - Err(why) => { - eprintln!("{why:?}"); - Err("Couldn't update post".to_string()) - } - } - } -} - -#[derive(FromRow, Debug)] -pub struct User { - pub id: i32, - pub username: String, - pub password: String, - pub token: Option, - pub admin: bool, - pub make_posts: bool, - pub comment: bool, -} - -pub enum LoginStatus { - InvalidToken, - NotLoggedIn, - LoggedIn(User), -} - -impl User { - pub async fn create( - db: &mut Connection, - username: &String, - password: &String, - ) -> Result { - match Regex::new(r"[^A-Za-z0-9_]") { - Ok(r) => { - match r.captures(username) { - Some(_) => - Err("The username contains invalid characters. Only letters, numbers, and underscores are allowed.".to_string()) - , - None => { - if username.len().gt(&32) || username.len().lt(&3) { // i can Never - // remember which symbol is which. this works better for me. - Err("Please choose a username between 3 and 32 characters.".to_string()) - } else { - match db - .execute( - sqlx::query( - r#" - INSERT INTO users (username, password) - VALUES ($1, $2); - "#, - ) - .bind(username) - .bind(sha256::digest(password)), - ).await - { - Ok(_) => - Ok("Created user.".to_string()) - , - Err(why) => { - eprintln!("Couldn't create database entry: {why:?}"); - Err("Failed to create user.".to_string()) - } - } - } - } - } - } - Err(why) => { - eprintln!("Couldn't compile name regex: {why:?}"); - Err("Couldn't compile name regex.".to_string()) - } - } - } - pub async fn get_by_id(db: &mut Connection, id: i32) -> Option { - match db - .fetch_one(sqlx::query("SELECT * FROM users WHERE id = $1;").bind(id)) - .await - { - Ok(user) => Some(User { - id: user.get::("id"), - username: user.get::("username"), - password: user.get::("password"), - token: user.get::, _>("token"), - admin: user.get::("admin"), - make_posts: user.get::("make_posts"), - comment: user.get::("comment"), - }), - Err(_) => None, - } - } - pub async fn get_by_token(db: &mut Connection, token: Cookie<'static>) -> Option { - let to = token.to_string(); - let mut fixed_token = to.split('=').collect::>(); - fixed_token.reverse(); - fixed_token.pop(); - fixed_token.reverse(); - let token_string = fixed_token.join("="); - - match db - .fetch_one(sqlx::query("SELECT * FROM users WHERE token = $1;").bind(token_string)) - .await - { - Ok(user) => Some(User { - id: user.get::("id"), - username: user.get::("username"), - password: user.get::("password"), - token: user.get::, _>("token"), - admin: user.get::("admin"), - make_posts: user.get::("make_posts"), - comment: user.get::("comment"), - }), - Err(_) => None, - } - } - pub async fn get_by_username(db: &mut Connection, username: &String) -> Option { - match db - .fetch_one(sqlx::query("SELECT * FROM users WHERE username = $1;").bind(username)) - .await - { - Ok(user) => Some(User { - id: user.get::("id"), - username: user.get::("username"), - password: user.get::("password"), - token: user.get::, _>("token"), - admin: user.get::("admin"), - make_posts: user.get::("make_posts"), - comment: user.get::("comment"), - }), - Err(_) => None, - } - } - pub async fn set_new_token(&self, db: &mut Connection) -> Result { - let token_end = format!("{}", rand_hc::Hc128Rng::from_entropy().next_u64()); - let token_start = sha256::digest(&self.username); - - let token = format!( - "{}-{}", - general_purpose::STANDARD.encode(token_start), - general_purpose::STANDARD.encode(token_end) - ); - - match db - .execute( - sqlx::query("UPDATE users SET token = $1 WHERE id = $2") - .bind(&token) - .bind(self.id), - ) - .await - { - Ok(_) => Ok(token), - Err(why) => Err(why.to_string()), - } - } - - pub async fn set_role( - &self, - db: &mut Connection, - role: &String, - value: &bool, - ) -> Result { - let mut sql = String::from("UPDATE users SET {{perm}} = $1 WHERE id = $2"); - if role == "admin" { - sql = sql.replace("{{perm}}", "admin"); - } else if role == "make_posts" { - sql = sql.replace("{{perm}}", "make_posts"); - } else if role == &"comment".to_string() { - sql = sql.replace("{{perm}}", "comment"); - } - if sql.contains("{{perm}}") { - Err("Invalid role.".to_string()) - } else { - match db - .execute(sqlx::query(&sql[..]).bind(value).bind(self.id)) - .await - { - Ok(_) => Ok("Succesfully updated role.".to_string()), - Err(why) => Err(why.to_string()), - } - } - } - - pub async fn login_status(db: &mut Connection, cookies: &CookieJar<'_>) -> LoginStatus { - match cookies.get_private("token") { - Some(t) => match User::get_by_token(db, t).await { - Some(user) => LoginStatus::LoggedIn(user), - None => LoginStatus::InvalidToken, - }, - None => LoginStatus::NotLoggedIn, - } - } -} - -pub struct Image { - pub id: i32, - pub uuid: String, - pub owner_name: String, -} - -impl Image { - pub async fn create(db: &mut Connection, uuid: &String, user: User) -> Result<(), String> { - match db - .execute( - sqlx::query( - r#" - INSERT INTO images (uuid, owner_name) - VALUES ($1, $2); - "#, - ) - .bind(uuid) - .bind(user.username), - ) - .await - { - Ok(_) => Ok(()), - Err(why) => { - eprintln!("Couldn't create database entry: {why:?}"); - Err("Couldn't create image.".to_string()) - } - } - } - pub async fn get_by_uuid(db: &mut Connection, uuid: &String) -> Result { - match db - .fetch_one(sqlx::query("SELECT * FROM images WHERE uuid = $1;").bind(uuid)) - .await - { - Ok(img) => Ok(Image { - id: img.get::("id"), - uuid: img.get::("uuid"), - owner_name: img.get::("owner_name"), - }), - Err(_) => Err("Couldn't get image.".to_string()), - } - } - pub async fn is_owned_by( - db: &mut Connection, - uuid: &String, - username: &String, - ) -> Result { - match Image::get_by_uuid(db, uuid).await { - Ok(img) => Ok(&img.owner_name == username), - Err(why) => { - eprintln!("{why:?}"); - Err("couldn't get image".to_string()) - } - } - } - pub async fn get_by_username( - db: &mut Connection, - owner_name: &String, - ) -> Result, String> { - match db - .fetch_all(sqlx::query("SELECT * FROM images WHERE owner_name = $1;").bind(owner_name)) - .await - { - Ok(imgs) => { - let mut res = vec![]; - for img in imgs { - res.push(Image { - id: img.get::("id"), - uuid: img.get::("uuid"), - owner_name: img.get::("owner_name"), - }) - } - Ok(res) - } - Err(_) => Err("Couldn't get image.".to_string()), - } - } - pub async fn delete(db: &mut Connection, uuid: &String) -> Result<(), ()> { - match db - .execute(sqlx::query("DELETE FROM images WHERE uuid = $1").bind(uuid)) - .await - { - Ok(rows_gone) => { - eprintln!("Deleted {rows_gone:?} rows."); - Ok(()) - } - Err(why) => { - eprintln!("Couldn't remove database entry: {why:?}"); - Err(()) - } - } - } -} diff --git a/src/tables/images.rs b/src/tables/images.rs new file mode 100644 index 0000000..e420a7d --- /dev/null +++ b/src/tables/images.rs @@ -0,0 +1,97 @@ +use crate::tables::{users::User, Db}; +use rocket_db_pools::sqlx::Executor; +use rocket_db_pools::sqlx::{self, Row}; +use rocket_db_pools::Connection; + +pub struct Image { + pub id: i32, + pub uuid: String, + pub owner_name: String, +} + +impl Image { + pub async fn create(db: &mut Connection, uuid: &String, user: User) -> Result<(), String> { + match db + .execute( + sqlx::query( + r#" + INSERT INTO images (uuid, owner_name) + VALUES ($1, $2); + "#, + ) + .bind(uuid) + .bind(user.username), + ) + .await + { + Ok(_) => Ok(()), + Err(why) => { + eprintln!("Couldn't create database entry: {why:?}"); + Err("Couldn't create image.".to_string()) + } + } + } + pub async fn get_by_uuid(db: &mut Connection, uuid: &String) -> Result { + match db + .fetch_one(sqlx::query("SELECT * FROM images WHERE uuid = $1;").bind(uuid)) + .await + { + Ok(img) => Ok(Image { + id: img.get::("id"), + uuid: img.get::("uuid"), + owner_name: img.get::("owner_name"), + }), + Err(_) => Err("Couldn't get image.".to_string()), + } + } + pub async fn is_owned_by( + db: &mut Connection, + uuid: &String, + username: &String, + ) -> Result { + match Image::get_by_uuid(db, uuid).await { + Ok(img) => Ok(&img.owner_name == username), + Err(why) => { + eprintln!("{why:?}"); + Err("couldn't get image".to_string()) + } + } + } + pub async fn get_by_username( + db: &mut Connection, + owner_name: &String, + ) -> Result, String> { + match db + .fetch_all(sqlx::query("SELECT * FROM images WHERE owner_name = $1;").bind(owner_name)) + .await + { + Ok(imgs) => { + let mut res = vec![]; + for img in imgs { + res.push(Image { + id: img.get::("id"), + uuid: img.get::("uuid"), + owner_name: img.get::("owner_name"), + }) + } + Ok(res) + } + Err(_) => Err("Couldn't get image.".to_string()), + } + } + pub async fn delete(db: &mut Connection, uuid: &String) -> Result<(), ()> { + match db + .execute(sqlx::query("DELETE FROM images WHERE uuid = $1").bind(uuid)) + .await + { + Ok(rows_gone) => { + eprintln!("Deleted {rows_gone:?} rows."); + Ok(()) + } + Err(why) => { + eprintln!("Couldn't remove database entry: {why:?}"); + Err(()) + } + } + } +} diff --git a/src/tables/mod.rs b/src/tables/mod.rs new file mode 100644 index 0000000..91a1203 --- /dev/null +++ b/src/tables/mod.rs @@ -0,0 +1,9 @@ +pub mod images; +pub mod posts; +pub mod users; + +use rocket_db_pools::{sqlx::PgPool, Database}; + +#[derive(Database)] +#[database("fossil_postgres")] +pub struct Db(PgPool); diff --git a/src/tables/posts.rs b/src/tables/posts.rs new file mode 100644 index 0000000..21cea1e --- /dev/null +++ b/src/tables/posts.rs @@ -0,0 +1,251 @@ +use rocket_db_pools::sqlx::Executor; +use rocket_db_pools::sqlx::{self, Row}; +use rocket_db_pools::Connection; + +use crate::tables::Db; +use sqlx::FromRow; + +#[derive(FromRow)] +pub struct Post { + pub id: i32, + pub uuid: String, + pub text_id: String, + pub title: String, + pub body: String, + pub published: bool, +} +impl Post { + pub async fn create( + mut db: Connection, + title: String, /*ex: Why Trans People Deserve All Your Money*/ + body: String, /*ex: # Because we're cooler than you */ + published: bool, + uuid: String, + text_id: String, /*ex: why-trans-people-deserve-all-your-money */ + ) { + match db + .fetch_all( + sqlx::query( + r#" + INSERT INTO posts (title, body, published, uuid, text_id) + VALUES ($1, $2, $3, $4, $5); + "#, + ) + .bind(title) + .bind(body) + .bind(published) + .bind(uuid) + .bind(text_id), + ) + .await + { + Ok(_) => (), + Err(why) => { + eprintln!("Couldn't create database entry: {why:?}"); + } + } + } + pub async fn get(mut db: Connection, id: i32) -> Post { + let res = db + .fetch_one(sqlx::query("SELECT * FROM posts WHERE id = $1;").bind(id)) + .await + .unwrap(); + Post { + id: res.get::("id"), + uuid: res.get::("uuid"), + text_id: res.get::("text_id"), + title: res.get::("title"), + body: res.get::("body"), + published: res.get::("published"), + } + } + pub async fn get_by_uuid(mut db: Connection, uuid: String) -> Post { + let res = db + .fetch_one(sqlx::query("SELECT * FROM posts WHERE uuid = $1;").bind(uuid)) + .await + .unwrap(); + Post { + id: res.get::("id"), + uuid: res.get::("uuid"), + text_id: res.get::("text_id"), + title: res.get::("title"), + body: res.get::("body"), + published: res.get::("published"), + } + } + pub async fn get_by_text_id(mut db: Connection, text_id: String) -> Post { + let res = db + .fetch_one(sqlx::query("SELECT * FROM posts WHERE text_id = $1;").bind(text_id)) + .await + .unwrap(); + Post { + id: res.get::("id"), + uuid: res.get::("uuid"), + text_id: res.get::("text_id"), + title: res.get::("title"), + body: res.get::("body"), + published: res.get::("published"), + } + } + + pub async fn edit_body( + mut db: Connection, + id: i32, + new_body: String, + ) -> Result<(), String> { + match db + .execute( + sqlx::query("UPDATE posts SET body = $1 WHERE id = $2;") + .bind(new_body) + .bind(id), + ) + .await + { + Ok(_) => Ok(()), + Err(why) => { + eprintln!("{why:?}"); + Err("Couldn't update post".to_string()) + } + } + } + pub async fn edit_body_by_uuid( + mut db: Connection, + uuid: String, + new_body: String, + ) -> Result<(), String> { + match db + .execute( + sqlx::query("UPDATE posts SET body = $1 WHERE uuid = $2;") + .bind(new_body) + .bind(uuid), + ) + .await + { + Ok(_) => Ok(()), + Err(why) => { + eprintln!("{why:?}"); + Err("Couldn't update post".to_string()) + } + } + } + pub async fn edit_body_by_text_id( + mut db: Connection, + text_id: String, + new_body: String, + ) -> Result<(), String> { + match db + .execute( + sqlx::query("UPDATE posts SET body = $1 WHERE text_id = $2;") + .bind(new_body) + .bind(text_id), + ) + .await + { + Ok(_) => Ok(()), + Err(why) => { + eprintln!("{why:?}"); + Err("Couldn't update post".to_string()) + } + } + } + pub async fn edit_title( + mut db: Connection, + id: i32, + new_title: String, + ) -> Result<(), String> { + match db + .execute( + sqlx::query("UPDATE posts SET title = $1 WHERE id = $2;") + .bind(new_title) + .bind(id), + ) + .await + { + Ok(_) => Ok(()), + Err(why) => { + eprintln!("{why:?}"); + Err("Couldn't update post".to_string()) + } + } + } + pub async fn edit_title_by_uuid( + mut db: Connection, + uuid: String, + new_title: String, + ) -> Result<(), String> { + match db + .execute( + sqlx::query("UPDATE posts SET title = $1 WHERE uuid = $2;") + .bind(new_title) + .bind(uuid), + ) + .await + { + Ok(_) => Ok(()), + Err(why) => { + eprintln!("{why:?}"); + Err("Couldn't update post".to_string()) + } + } + } + pub async fn edit_title_by_text_id( + mut db: Connection, + text_id: String, + new_title: String, + ) -> Result<(), String> { + match db + .execute( + sqlx::query("UPDATE posts SET body = $1 WHERE text_id = $2;") + .bind(new_title) + .bind(text_id), + ) + .await + { + Ok(_) => Ok(()), + Err(why) => { + eprintln!("{why:?}"); + Err("Couldn't update post".to_string()) + } + } + } + pub async fn edit_text_id( + mut db: Connection, + id: i32, + new_text_id: String, + ) -> Result<(), String> { + match db + .execute( + sqlx::query("UPDATE posts SET text_id = $1 WHERE id = $2;") + .bind(new_text_id) + .bind(id), + ) + .await + { + Ok(_) => Ok(()), + Err(why) => { + eprintln!("{why:?}"); + Err("Couldn't update post".to_string()) + } + } + } + pub async fn edit_text_id_by_uuid( + mut db: Connection, + uuid: String, + new_text_id: String, + ) -> Result<(), String> { + match db + .execute( + sqlx::query("UPDATE posts SET text_id = $1 WHERE uuid = $2;") + .bind(new_text_id) + .bind(uuid), + ) + .await + { + Ok(_) => Ok(()), + Err(why) => { + eprintln!("{why:?}"); + Err("Couldn't update post".to_string()) + } + } + } +} diff --git a/src/tables/users.rs b/src/tables/users.rs new file mode 100644 index 0000000..1fbb453 --- /dev/null +++ b/src/tables/users.rs @@ -0,0 +1,193 @@ +use crate::tables::Db; +use base64::engine::general_purpose; +use base64::Engine; +use rand::{RngCore, SeedableRng}; +use regex::Regex; +use rocket::http::{Cookie, CookieJar}; +use rocket_db_pools::sqlx::Executor; +use rocket_db_pools::sqlx::{self, Row}; +use rocket_db_pools::Connection; +use sqlx::FromRow; + +#[derive(FromRow, Debug)] +pub struct User { + pub id: i32, + pub username: String, + pub password: String, + pub token: Option, + pub admin: bool, + pub make_posts: bool, + pub comment: bool, +} + +pub enum LoginStatus { + InvalidToken, + NotLoggedIn, + LoggedIn(User), +} + +impl User { + pub async fn create( + db: &mut Connection, + username: &String, + password: &String, + ) -> Result { + match Regex::new(r"[^A-Za-z0-9_]") { + Ok(r) => { + match r.captures(username) { + Some(_) => + Err("The username contains invalid characters. Only letters, numbers, and underscores are allowed.".to_string()) + , + None => { + if username.len().gt(&32) || username.len().lt(&3) { // i can Never + // remember which symbol is which. this works better for me. + Err("Please choose a username between 3 and 32 characters.".to_string()) + } else { + match db + .execute( + sqlx::query( + r#" + INSERT INTO users (username, password) + VALUES ($1, $2); + "#, + ) + .bind(username) + .bind(sha256::digest(password)), + ).await + { + Ok(_) => + Ok("Created user.".to_string()) + , + Err(why) => { + eprintln!("Couldn't create database entry: {why:?}"); + Err("Failed to create user.".to_string()) + } + } + } + } + } + } + Err(why) => { + eprintln!("Couldn't compile name regex: {why:?}"); + Err("Couldn't compile name regex.".to_string()) + } + } + } + pub async fn get_by_id(db: &mut Connection, id: i32) -> Option { + match db + .fetch_one(sqlx::query("SELECT * FROM users WHERE id = $1;").bind(id)) + .await + { + Ok(user) => Some(User { + id: user.get::("id"), + username: user.get::("username"), + password: user.get::("password"), + token: user.get::, _>("token"), + admin: user.get::("admin"), + make_posts: user.get::("make_posts"), + comment: user.get::("comment"), + }), + Err(_) => None, + } + } + pub async fn get_by_token(db: &mut Connection, token: Cookie<'static>) -> Option { + let to = token.to_string(); + let mut fixed_token = to.split('=').collect::>(); + fixed_token.reverse(); + fixed_token.pop(); + fixed_token.reverse(); + let token_string = fixed_token.join("="); + + match db + .fetch_one(sqlx::query("SELECT * FROM users WHERE token = $1;").bind(token_string)) + .await + { + Ok(user) => Some(User { + id: user.get::("id"), + username: user.get::("username"), + password: user.get::("password"), + token: user.get::, _>("token"), + admin: user.get::("admin"), + make_posts: user.get::("make_posts"), + comment: user.get::("comment"), + }), + Err(_) => None, + } + } + pub async fn get_by_username(db: &mut Connection, username: &String) -> Option { + match db + .fetch_one(sqlx::query("SELECT * FROM users WHERE username = $1;").bind(username)) + .await + { + Ok(user) => Some(User { + id: user.get::("id"), + username: user.get::("username"), + password: user.get::("password"), + token: user.get::, _>("token"), + admin: user.get::("admin"), + make_posts: user.get::("make_posts"), + comment: user.get::("comment"), + }), + Err(_) => None, + } + } + pub async fn set_new_token(&self, db: &mut Connection) -> Result { + let token_end = format!("{}", rand_hc::Hc128Rng::from_entropy().next_u64()); + let token_start = sha256::digest(&self.username); + + let token = format!( + "{}-{}", + general_purpose::STANDARD.encode(token_start), + general_purpose::STANDARD.encode(token_end) + ); + + match db + .execute( + sqlx::query("UPDATE users SET token = $1 WHERE id = $2") + .bind(&token) + .bind(self.id), + ) + .await + { + Ok(_) => Ok(token), + Err(why) => Err(why.to_string()), + } + } + + pub async fn set_role( + &self, + db: &mut Connection, + role: &String, + value: &bool, + ) -> Result { + let mut sql = String::from("UPDATE users SET {{perm}} = $1 WHERE id = $2"); + if role == "admin" { + sql = sql.replace("{{perm}}", "admin"); + } else if role == "make_posts" { + sql = sql.replace("{{perm}}", "make_posts"); + } else if role == &"comment".to_string() { + sql = sql.replace("{{perm}}", "comment"); + } + if sql.contains("{{perm}}") { + Err("Invalid role.".to_string()) + } else { + match db + .execute(sqlx::query(&sql[..]).bind(value).bind(self.id)) + .await + { + Ok(_) => Ok("Succesfully updated role.".to_string()), + Err(why) => Err(why.to_string()), + } + } + } + + pub async fn login_status(db: &mut Connection, cookies: &CookieJar<'_>) -> LoginStatus { + match cookies.get_private("token") { + Some(t) => match User::get_by_token(db, t).await { + Some(user) => LoginStatus::LoggedIn(user), + None => LoginStatus::InvalidToken, + }, + None => LoginStatus::NotLoggedIn, + } + } +}