clean up tables

This commit is contained in:
SadlyNotSappho 2024-04-05 11:24:41 -07:00
parent 4ea44b3639
commit 7462a36515
11 changed files with 556 additions and 541 deletions

View File

@ -1,3 +1,2 @@
pub mod tables;
pub mod routes;

View File

@ -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<String> {
@ -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])

View File

@ -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;

View File

@ -1,3 +1,4 @@
pub mod images;
pub mod users;
pub mod web;
pub mod posts;

0
src/routes/posts.rs Normal file
View File

View File

@ -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())
}
},
},

View File

@ -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<Db>,
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<Db>, id: i32) -> Post {
let res = db
.fetch_one(sqlx::query("SELECT * FROM posts WHERE id = $1;").bind(id))
.await
.unwrap();
Post {
id: res.get::<i32, _>("id"),
uuid: res.get::<String, _>("uuid"),
text_id: res.get::<String, _>("text_id"),
title: res.get::<String, _>("title"),
body: res.get::<String, _>("body"),
published: res.get::<bool, _>("published"),
}
}
pub async fn get_by_uuid(mut db: Connection<Db>, uuid: String) -> Post {
let res = db
.fetch_one(sqlx::query("SELECT * FROM posts WHERE uuid = $1;").bind(uuid))
.await
.unwrap();
Post {
id: res.get::<i32, _>("id"),
uuid: res.get::<String, _>("uuid"),
text_id: res.get::<String, _>("text_id"),
title: res.get::<String, _>("title"),
body: res.get::<String, _>("body"),
published: res.get::<bool, _>("published"),
}
}
pub async fn get_by_text_id(mut db: Connection<Db>, 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::<i32, _>("id"),
uuid: res.get::<String, _>("uuid"),
text_id: res.get::<String, _>("text_id"),
title: res.get::<String, _>("title"),
body: res.get::<String, _>("body"),
published: res.get::<bool, _>("published"),
}
}
pub async fn edit_body(
mut db: Connection<Db>,
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<Db>,
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<Db>,
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<Db>,
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<Db>,
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<Db>,
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<Db>,
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<Db>,
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<String>,
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<Db>,
username: &String,
password: &String,
) -> Result<String, String> {
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<Db>, id: i32) -> Option<User> {
match db
.fetch_one(sqlx::query("SELECT * FROM users WHERE id = $1;").bind(id))
.await
{
Ok(user) => Some(User {
id: user.get::<i32, _>("id"),
username: user.get::<String, _>("username"),
password: user.get::<String, _>("password"),
token: user.get::<Option<String>, _>("token"),
admin: user.get::<bool, _>("admin"),
make_posts: user.get::<bool, _>("make_posts"),
comment: user.get::<bool, _>("comment"),
}),
Err(_) => None,
}
}
pub async fn get_by_token(db: &mut Connection<Db>, token: Cookie<'static>) -> Option<User> {
let to = token.to_string();
let mut fixed_token = to.split('=').collect::<Vec<&str>>();
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::<i32, _>("id"),
username: user.get::<String, _>("username"),
password: user.get::<String, _>("password"),
token: user.get::<Option<String>, _>("token"),
admin: user.get::<bool, _>("admin"),
make_posts: user.get::<bool, _>("make_posts"),
comment: user.get::<bool, _>("comment"),
}),
Err(_) => None,
}
}
pub async fn get_by_username(db: &mut Connection<Db>, username: &String) -> Option<User> {
match db
.fetch_one(sqlx::query("SELECT * FROM users WHERE username = $1;").bind(username))
.await
{
Ok(user) => Some(User {
id: user.get::<i32, _>("id"),
username: user.get::<String, _>("username"),
password: user.get::<String, _>("password"),
token: user.get::<Option<String>, _>("token"),
admin: user.get::<bool, _>("admin"),
make_posts: user.get::<bool, _>("make_posts"),
comment: user.get::<bool, _>("comment"),
}),
Err(_) => None,
}
}
pub async fn set_new_token(&self, db: &mut Connection<Db>) -> Result<String, String> {
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<Db>,
role: &String,
value: &bool,
) -> Result<String, String> {
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<Db>, 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<Db>, 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<Db>, uuid: &String) -> Result<Image, String> {
match db
.fetch_one(sqlx::query("SELECT * FROM images WHERE uuid = $1;").bind(uuid))
.await
{
Ok(img) => Ok(Image {
id: img.get::<i32, _>("id"),
uuid: img.get::<String, _>("uuid"),
owner_name: img.get::<String, _>("owner_name"),
}),
Err(_) => Err("Couldn't get image.".to_string()),
}
}
pub async fn is_owned_by(
db: &mut Connection<Db>,
uuid: &String,
username: &String,
) -> Result<bool, String> {
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<Db>,
owner_name: &String,
) -> Result<Vec<Image>, 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::<i32, _>("id"),
uuid: img.get::<String, _>("uuid"),
owner_name: img.get::<String, _>("owner_name"),
})
}
Ok(res)
}
Err(_) => Err("Couldn't get image.".to_string()),
}
}
pub async fn delete(db: &mut Connection<Db>, 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(())
}
}
}
}

97
src/tables/images.rs Normal file
View File

@ -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<Db>, 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<Db>, uuid: &String) -> Result<Image, String> {
match db
.fetch_one(sqlx::query("SELECT * FROM images WHERE uuid = $1;").bind(uuid))
.await
{
Ok(img) => Ok(Image {
id: img.get::<i32, _>("id"),
uuid: img.get::<String, _>("uuid"),
owner_name: img.get::<String, _>("owner_name"),
}),
Err(_) => Err("Couldn't get image.".to_string()),
}
}
pub async fn is_owned_by(
db: &mut Connection<Db>,
uuid: &String,
username: &String,
) -> Result<bool, String> {
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<Db>,
owner_name: &String,
) -> Result<Vec<Image>, 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::<i32, _>("id"),
uuid: img.get::<String, _>("uuid"),
owner_name: img.get::<String, _>("owner_name"),
})
}
Ok(res)
}
Err(_) => Err("Couldn't get image.".to_string()),
}
}
pub async fn delete(db: &mut Connection<Db>, 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(())
}
}
}
}

9
src/tables/mod.rs Normal file
View File

@ -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);

251
src/tables/posts.rs Normal file
View File

@ -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<Db>,
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<Db>, id: i32) -> Post {
let res = db
.fetch_one(sqlx::query("SELECT * FROM posts WHERE id = $1;").bind(id))
.await
.unwrap();
Post {
id: res.get::<i32, _>("id"),
uuid: res.get::<String, _>("uuid"),
text_id: res.get::<String, _>("text_id"),
title: res.get::<String, _>("title"),
body: res.get::<String, _>("body"),
published: res.get::<bool, _>("published"),
}
}
pub async fn get_by_uuid(mut db: Connection<Db>, uuid: String) -> Post {
let res = db
.fetch_one(sqlx::query("SELECT * FROM posts WHERE uuid = $1;").bind(uuid))
.await
.unwrap();
Post {
id: res.get::<i32, _>("id"),
uuid: res.get::<String, _>("uuid"),
text_id: res.get::<String, _>("text_id"),
title: res.get::<String, _>("title"),
body: res.get::<String, _>("body"),
published: res.get::<bool, _>("published"),
}
}
pub async fn get_by_text_id(mut db: Connection<Db>, 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::<i32, _>("id"),
uuid: res.get::<String, _>("uuid"),
text_id: res.get::<String, _>("text_id"),
title: res.get::<String, _>("title"),
body: res.get::<String, _>("body"),
published: res.get::<bool, _>("published"),
}
}
pub async fn edit_body(
mut db: Connection<Db>,
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<Db>,
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<Db>,
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<Db>,
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<Db>,
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<Db>,
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<Db>,
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<Db>,
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())
}
}
}
}

193
src/tables/users.rs Normal file
View File

@ -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<String>,
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<Db>,
username: &String,
password: &String,
) -> Result<String, String> {
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<Db>, id: i32) -> Option<User> {
match db
.fetch_one(sqlx::query("SELECT * FROM users WHERE id = $1;").bind(id))
.await
{
Ok(user) => Some(User {
id: user.get::<i32, _>("id"),
username: user.get::<String, _>("username"),
password: user.get::<String, _>("password"),
token: user.get::<Option<String>, _>("token"),
admin: user.get::<bool, _>("admin"),
make_posts: user.get::<bool, _>("make_posts"),
comment: user.get::<bool, _>("comment"),
}),
Err(_) => None,
}
}
pub async fn get_by_token(db: &mut Connection<Db>, token: Cookie<'static>) -> Option<User> {
let to = token.to_string();
let mut fixed_token = to.split('=').collect::<Vec<&str>>();
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::<i32, _>("id"),
username: user.get::<String, _>("username"),
password: user.get::<String, _>("password"),
token: user.get::<Option<String>, _>("token"),
admin: user.get::<bool, _>("admin"),
make_posts: user.get::<bool, _>("make_posts"),
comment: user.get::<bool, _>("comment"),
}),
Err(_) => None,
}
}
pub async fn get_by_username(db: &mut Connection<Db>, username: &String) -> Option<User> {
match db
.fetch_one(sqlx::query("SELECT * FROM users WHERE username = $1;").bind(username))
.await
{
Ok(user) => Some(User {
id: user.get::<i32, _>("id"),
username: user.get::<String, _>("username"),
password: user.get::<String, _>("password"),
token: user.get::<Option<String>, _>("token"),
admin: user.get::<bool, _>("admin"),
make_posts: user.get::<bool, _>("make_posts"),
comment: user.get::<bool, _>("comment"),
}),
Err(_) => None,
}
}
pub async fn set_new_token(&self, db: &mut Connection<Db>) -> Result<String, String> {
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<Db>,
role: &String,
value: &bool,
) -> Result<String, String> {
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<Db>, 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,
}
}
}