clean up tables
This commit is contained in:
parent
4ea44b3639
commit
7462a36515
|
@ -1,3 +1,2 @@
|
|||
pub mod tables;
|
||||
|
||||
pub mod routes;
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
pub mod images;
|
||||
pub mod users;
|
||||
pub mod web;
|
||||
pub mod posts;
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
536
src/tables.rs
536
src/tables.rs
|
@ -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(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue