basic db schema and rough retrieval
commit
5b231bdb78
@ -0,0 +1,2 @@
|
|||||||
|
/target
|
||||||
|
.env
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,15 @@
|
|||||||
|
[package]
|
||||||
|
name = "rust-forum-test"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
dotenv = "0.15.0"
|
||||||
|
rocket = { version = "0.5.0", features = ["serde_json"] }
|
||||||
|
sqlx = { version = "0.7.3", features = ["postgres", "runtime-tokio", "macros", "uuid", "time"] }
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
time = { version = "0.3.34" }
|
||||||
|
uuid = { version = "1.7.0", features = ["v4", "serde"] }
|
||||||
|
serde_json = "1.0.114"
|
@ -0,0 +1,41 @@
|
|||||||
|
create extension if not exists "uuid-ossp";
|
||||||
|
create extension if not exists "citext";
|
||||||
|
|
||||||
|
create schema forum;
|
||||||
|
|
||||||
|
create table forum.user (
|
||||||
|
user_uuid uuid primary key default uuid_generate_v4(),
|
||||||
|
user_email text not null unique,
|
||||||
|
user_username text not null unique,
|
||||||
|
user_password_hash text not null,
|
||||||
|
user_is_admin boolean default false
|
||||||
|
);
|
||||||
|
|
||||||
|
create table forum.site (
|
||||||
|
site_uuid uuid primary key default uuid_generate_v4(),
|
||||||
|
site_title text not null,
|
||||||
|
site_base_url text not null,
|
||||||
|
site_theme text not null default 'internal',
|
||||||
|
site_singleton boolean default true,
|
||||||
|
constraint only_one_site check (site_singleton)
|
||||||
|
);
|
||||||
|
|
||||||
|
create table forum.board (
|
||||||
|
board_uuid uuid primary key default uuid_generate_v4(),
|
||||||
|
board_site uuid not null references forum.site (site_uuid),
|
||||||
|
board_title text not null,
|
||||||
|
board_description text not null
|
||||||
|
);
|
||||||
|
|
||||||
|
create table forum.thread (
|
||||||
|
thread_uuid uuid primary key default uuid_generate_v4(),
|
||||||
|
thread_board uuid not null references forum.board (board_uuid),
|
||||||
|
thread_title text not null
|
||||||
|
);
|
||||||
|
|
||||||
|
create table forum.post (
|
||||||
|
post_uuid uuid primary key default uuid_generate_v4(),
|
||||||
|
post_thread uuid not null references forum.thread (thread_uuid),
|
||||||
|
post_contents text not null,
|
||||||
|
post_author uuid not null references forum.user (user_uuid)
|
||||||
|
);
|
@ -0,0 +1,168 @@
|
|||||||
|
use serde::Serialize;
|
||||||
|
use sqlx::{Pool, Postgres};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct DB {
|
||||||
|
connection_pool: Pool::<Postgres>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct Site {
|
||||||
|
uuid: Uuid,
|
||||||
|
title: String,
|
||||||
|
base_url: String,
|
||||||
|
theme: String,
|
||||||
|
boards: Vec<Board>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Clone)]
|
||||||
|
pub struct Board {
|
||||||
|
uuid: Uuid,
|
||||||
|
title: String,
|
||||||
|
description: String,
|
||||||
|
threads: Vec<Thread>
|
||||||
|
}
|
||||||
|
impl Board {
|
||||||
|
fn clone(&self) -> Board {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Clone)]
|
||||||
|
pub struct Thread {
|
||||||
|
uuid: Uuid,
|
||||||
|
title: String,
|
||||||
|
posts: Vec<Post>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Clone)]
|
||||||
|
pub struct Post {
|
||||||
|
uuid: Uuid,
|
||||||
|
contents: String,
|
||||||
|
author: User
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Clone)]
|
||||||
|
pub struct User {
|
||||||
|
uuid: Uuid,
|
||||||
|
email: String,
|
||||||
|
username: String,
|
||||||
|
password_hash: String,
|
||||||
|
is_admin: bool
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DB {
|
||||||
|
pub async fn init() -> Self {
|
||||||
|
let db_url = std::env::var("DATABASE_URL").expect("Please provide DATABASE_URL in environment");
|
||||||
|
let pool = Pool::<Postgres>::connect(db_url.as_str()).await.expect("Could not connect to database");
|
||||||
|
|
||||||
|
sqlx::migrate!()
|
||||||
|
.run(&pool)
|
||||||
|
.await
|
||||||
|
.expect("Could not run database migrations");
|
||||||
|
|
||||||
|
DB {
|
||||||
|
connection_pool: pool
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_site_data(&self) -> Result<Site, String> {
|
||||||
|
let rows = sqlx::query!(r#"
|
||||||
|
select
|
||||||
|
site_uuid,
|
||||||
|
site_title,
|
||||||
|
site_base_url,
|
||||||
|
site_theme,
|
||||||
|
board_uuid,
|
||||||
|
board_title,
|
||||||
|
board_description,
|
||||||
|
thread_uuid,
|
||||||
|
thread_title,
|
||||||
|
post_uuid as "post_uuid?",
|
||||||
|
post_contents as "post_contents?",
|
||||||
|
user_uuid,
|
||||||
|
user_email,
|
||||||
|
user_username,
|
||||||
|
user_password_hash,
|
||||||
|
user_is_admin
|
||||||
|
from forum.site
|
||||||
|
left join forum.board on board_site = site_uuid
|
||||||
|
left join forum.thread on thread_board = board_uuid
|
||||||
|
left join forum.post on post_thread = thread_uuid
|
||||||
|
left join forum.user on post_author = user_uuid
|
||||||
|
"#).fetch_all(&self.connection_pool).await.expect("Could not connect to database");
|
||||||
|
|
||||||
|
let mut site : Option<Site> = None;
|
||||||
|
let mut last_board : Option<Board> = None;
|
||||||
|
let mut last_thread : Option<Thread> = None;
|
||||||
|
let mut last_post : Option<Post> = None;
|
||||||
|
|
||||||
|
for row in rows {
|
||||||
|
if site.is_none() || row.site_uuid.is_some() && row.site_uuid.unwrap() != site.as_ref().unwrap().uuid {
|
||||||
|
site = Some(Site {
|
||||||
|
uuid: row.site_uuid.unwrap(),
|
||||||
|
title: row.site_title.unwrap_or(String::new()),
|
||||||
|
base_url: row.site_base_url.unwrap_or(String::new()),
|
||||||
|
theme: row.site_theme.unwrap_or(String::new()),
|
||||||
|
boards: Vec::new()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if last_board.is_none() || row.board_uuid.is_some() && row.board_uuid.unwrap() != last_board.as_ref().unwrap().uuid {
|
||||||
|
if let Some(ref board) = last_board {
|
||||||
|
site.as_mut().unwrap().boards.push(board.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
if row.board_uuid.is_some() {
|
||||||
|
last_board = Some(Board {
|
||||||
|
uuid: row.board_uuid.unwrap(),
|
||||||
|
title: row.board_title.unwrap_or(String::new()),
|
||||||
|
description: row.board_description.unwrap_or(String::new()),
|
||||||
|
threads: Vec::new()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if last_thread.is_none() || row.thread_uuid.is_some() && row.thread_uuid.unwrap() != last_thread.as_mut().unwrap().uuid {
|
||||||
|
if let Some(ref thread) = last_thread {
|
||||||
|
last_board.as_mut().unwrap().threads.push(thread.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
if row.thread_uuid.is_some() {
|
||||||
|
last_thread = Some(Thread {
|
||||||
|
uuid: row.thread_uuid.unwrap(),
|
||||||
|
title: row.thread_title.unwrap_or(String::new()),
|
||||||
|
posts: Vec::new()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if last_post.is_none() || row.post_uuid != last_post.as_ref().unwrap().uuid {
|
||||||
|
// if let Some(post) = last_post {
|
||||||
|
// last_thread.as_mut().unwrap().posts.push(post)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if row.post_uuid.is_some() {
|
||||||
|
// last_post = Some(Post {
|
||||||
|
// uuid: row.post_uuid,
|
||||||
|
// contents: row.post_contents,
|
||||||
|
// author: User {
|
||||||
|
// uuid: row.user_uuid.unwrap(),
|
||||||
|
// email: row.user_email.unwrap_or(String::new()),
|
||||||
|
// username: row.user_username.unwrap_or(String::new()),
|
||||||
|
// password_hash: row.user_password_hash.unwrap_or(String::new()),
|
||||||
|
// is_admin: row.user_is_admin.unwrap_or(false)
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(site) = site {
|
||||||
|
Ok(site)
|
||||||
|
} else {
|
||||||
|
Err("Could not find site in DB".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
#[macro_use]
|
||||||
|
extern crate rocket;
|
||||||
|
|
||||||
|
use db::DB;
|
||||||
|
use dotenv::dotenv;
|
||||||
|
|
||||||
|
mod db;
|
||||||
|
mod routes;
|
||||||
|
|
||||||
|
#[rocket::main]
|
||||||
|
async fn main() {
|
||||||
|
dotenv().ok();
|
||||||
|
|
||||||
|
let db = DB::init().await;
|
||||||
|
|
||||||
|
let launch_result = rocket::build()
|
||||||
|
.manage(db)
|
||||||
|
.mount("/", routes![
|
||||||
|
routes::site_index
|
||||||
|
])
|
||||||
|
.launch();
|
||||||
|
|
||||||
|
launch_result.await.expect("Could not start Rocket");
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
use rocket::{http::Status, State};
|
||||||
|
|
||||||
|
use crate::db::DB;
|
||||||
|
|
||||||
|
#[get("/")]
|
||||||
|
pub async fn site_index(db : &State<DB>) -> Result<String, (Status, String)> {
|
||||||
|
let site = db.get_site_data().await.unwrap();
|
||||||
|
let response = serde_json::to_string(&site).unwrap();
|
||||||
|
|
||||||
|
Ok(response)
|
||||||
|
}
|
Loading…
Reference in New Issue