:3
This commit is contained in:
		
							parent
							
								
									cd99fea483
								
							
						
					
					
						commit
						81e68770c6
					
				
							
								
								
									
										1
									
								
								.env
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.env
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1 @@
 | 
				
			||||||
 | 
					DATABASE_URL="postgres://postgres:postgres@127.0.0.1:5432/persmgr""
 | 
				
			||||||
							
								
								
									
										1379
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1379
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
					@ -9,9 +9,13 @@ askama = "0.14.0"
 | 
				
			||||||
axum = "0.8.4"
 | 
					axum = "0.8.4"
 | 
				
			||||||
serde = { version = "1.0.219", features = ["derive"] }
 | 
					serde = { version = "1.0.219", features = ["derive"] }
 | 
				
			||||||
serde_json = "1.0.143"
 | 
					serde_json = "1.0.143"
 | 
				
			||||||
 | 
					sqlx = { version = "0.8.6", features = ["macros", "postgres", "runtime-tokio"] }
 | 
				
			||||||
tokio = { version = "1.47.1", features = ["full"] }
 | 
					tokio = { version = "1.47.1", features = ["full"] }
 | 
				
			||||||
toml = "0.9.5"
 | 
					toml = "0.9.5"
 | 
				
			||||||
tower = { version = "0.5.2", features = ["full"] }
 | 
					tower = { version = "0.5.2", features = ["full"] }
 | 
				
			||||||
 | 
					tower-cookies = "0.11.0"
 | 
				
			||||||
tower-http = { version = "0.6.6", features = ["full"] }
 | 
					tower-http = { version = "0.6.6", features = ["full"] }
 | 
				
			||||||
 | 
					tower-livereload = "0.9.6"
 | 
				
			||||||
 | 
					tower-sessions = "0.14.0"
 | 
				
			||||||
tracing = "0.1.41"
 | 
					tracing = "0.1.41"
 | 
				
			||||||
tracing-subscriber = "0.3.20"
 | 
					tracing-subscriber = "0.3.20"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										4
									
								
								build.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								build.rs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,4 @@
 | 
				
			||||||
 | 
					fn main() {
 | 
				
			||||||
 | 
					    println!("cargo:rerun-if-changed=res");
 | 
				
			||||||
 | 
					    println!("cargo:rerun-if-changed=templates");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										3
									
								
								migrations/20250906104612_users.down.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								migrations/20250906104612_users.down.sql
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,3 @@
 | 
				
			||||||
 | 
					-- Add down migration script here
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					DROP TABLE IF EXISTS users;
 | 
				
			||||||
							
								
								
									
										12
									
								
								migrations/20250906104612_users.up.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								migrations/20250906104612_users.up.sql
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,12 @@
 | 
				
			||||||
 | 
					-- Add up migration script here
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CREATE TABLE IF NOT EXISTS users (
 | 
				
			||||||
 | 
					    id BIGSERIAL PRIMARY KEY,
 | 
				
			||||||
 | 
					    email TEXT NOT NULL UNIQUE,
 | 
				
			||||||
 | 
					    verified_email BOOLEAN NOT NULL,
 | 
				
			||||||
 | 
					    username TEXT NOT NULL UNIQUE,
 | 
				
			||||||
 | 
					    pw_hash TEXT NOT NULL,
 | 
				
			||||||
 | 
					    pw_salt TEXT NOT NULL,
 | 
				
			||||||
 | 
					    pfp_id BIGINT NOT NULL
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -39,6 +39,9 @@ body {
 | 
				
			||||||
    font-size: 18px;
 | 
					    font-size: 18px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#topnav_login {
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.topnav_button:hover {
 | 
					.topnav_button:hover {
 | 
				
			||||||
    background: var(--bg-color)
 | 
					    background: var(--bg-color)
 | 
				
			||||||
| 
						 | 
					@ -47,7 +50,7 @@ body {
 | 
				
			||||||
#topnav_profile {
 | 
					#topnav_profile {
 | 
				
			||||||
    margin-right: 0.5rem;
 | 
					    margin-right: 0.5rem;
 | 
				
			||||||
    position: relative;
 | 
					    position: relative;
 | 
				
			||||||
    display: flex;
 | 
					    display: none;
 | 
				
			||||||
    align-items: center;
 | 
					    align-items: center;
 | 
				
			||||||
    justify-content: right;
 | 
					    justify-content: right;
 | 
				
			||||||
    height: 3rem;
 | 
					    height: 3rem;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,9 +1,20 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					document.addEventListener("DOMContentLoaded", () => {
 | 
				
			||||||
 | 
					    const is_logged_in = false;
 | 
				
			||||||
 | 
					    if (is_logged_in) {
 | 
				
			||||||
 | 
					        const prof = document.getElementById("topnav_profile");
 | 
				
			||||||
 | 
					        const login = document.getElementById("topnav_login");
 | 
				
			||||||
 | 
					        prof.style.display = "flex";
 | 
				
			||||||
 | 
					        login.style.display = "none";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
document.addEventListener("keydown", (event) => {
 | 
					document.addEventListener("keydown", (event) => {
 | 
				
			||||||
    if (event.key === "Escape") on_pfp_click(true);
 | 
					    if (event.key === "Escape") on_pfp_click(true);
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// :3 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function on_pfp_click(close = false) {
 | 
					function on_pfp_click(close = false) {
 | 
				
			||||||
    const el = document.getElementById("topnav_profile_dropdown");
 | 
					    const el = document.getElementById("topnav_profile_dropdown");
 | 
				
			||||||
| 
						 | 
					@ -13,3 +24,5 @@ function on_pfp_click(close = false) {
 | 
				
			||||||
        el.style.display = "none";
 | 
					        el.style.display = "none";
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,11 +1,16 @@
 | 
				
			||||||
use axum::{Router, http::StatusCode, routing::get};
 | 
					use crate::db::Database;
 | 
				
			||||||
 | 
					use axum::{Router, extract::State, http::StatusCode, routing::get};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async fn root() -> (StatusCode, &'static str) {
 | 
					pub mod user;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async fn root(State(state): State<Database>) -> (StatusCode, &'static str) {
 | 
				
			||||||
    (StatusCode::OK, "We Good twin :3c")
 | 
					    (StatusCode::OK, "We Good twin :3c")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn register_routes() -> Router {
 | 
					pub fn register_routes() -> Router<Database> {
 | 
				
			||||||
    let router = Router::new().route("/", get(root));
 | 
					    let router = Router::new()
 | 
				
			||||||
 | 
					        .route("/", get(root))
 | 
				
			||||||
 | 
					        .nest("/user", user::register_routes());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Router::new().nest("/api", router)
 | 
					    Router::new().nest("/api", router)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										17
									
								
								src/api/user/login.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/api/user/login.rs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,17 @@
 | 
				
			||||||
 | 
					use axum::{
 | 
				
			||||||
 | 
					    body::Body,
 | 
				
			||||||
 | 
					    extract::State,
 | 
				
			||||||
 | 
					    http::{HeaderMap, HeaderValue, StatusCode},
 | 
				
			||||||
 | 
					    response::{IntoResponse, Response},
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::db::Database;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub async fn route(State(db): State<Database>) -> Response {
 | 
				
			||||||
 | 
					    Response::builder()
 | 
				
			||||||
 | 
					        .header("Location", "/")
 | 
				
			||||||
 | 
					        .header("Set-Cookie", &format!("session=meowmeowmeow"))
 | 
				
			||||||
 | 
					        .status(StatusCode::SEE_OTHER)
 | 
				
			||||||
 | 
					        .body(Body::empty())
 | 
				
			||||||
 | 
					        .unwrap()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										15
									
								
								src/api/user/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/api/user/mod.rs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,15 @@
 | 
				
			||||||
 | 
					use axum::{
 | 
				
			||||||
 | 
					    Router,
 | 
				
			||||||
 | 
					    routing::{get, post},
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::db::Database;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub mod login;
 | 
				
			||||||
 | 
					pub mod register;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn register_routes() -> Router<Database> {
 | 
				
			||||||
 | 
					    Router::new()
 | 
				
			||||||
 | 
					        .route("/register", post(register::route))
 | 
				
			||||||
 | 
					        .route("/login", post(login::route))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										17
									
								
								src/api/user/register.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/api/user/register.rs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,17 @@
 | 
				
			||||||
 | 
					use axum::{
 | 
				
			||||||
 | 
					    body::Body,
 | 
				
			||||||
 | 
					    extract::State,
 | 
				
			||||||
 | 
					    http::{HeaderMap, HeaderValue, StatusCode},
 | 
				
			||||||
 | 
					    response::{IntoResponse, Response},
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::db::Database;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub async fn route(State(db): State<Database>) -> Response {
 | 
				
			||||||
 | 
					    Response::builder()
 | 
				
			||||||
 | 
					        .header("Location", "/")
 | 
				
			||||||
 | 
					        .header("Set-Cookie", &format!("session=meowmeowmeow"))
 | 
				
			||||||
 | 
					        .status(StatusCode::SEE_OTHER)
 | 
				
			||||||
 | 
					        .body(Body::empty())
 | 
				
			||||||
 | 
					        .unwrap()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										23
									
								
								src/db/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/db/mod.rs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,23 @@
 | 
				
			||||||
 | 
					use anyhow::Result;
 | 
				
			||||||
 | 
					use sqlx::{Pool, Postgres, postgres::PgPoolOptions};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub mod tables;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub type CurrPool = Pool<Postgres>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone)]
 | 
				
			||||||
 | 
					pub struct Database {
 | 
				
			||||||
 | 
					    pool: CurrPool,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Database {
 | 
				
			||||||
 | 
					    pub async fn connect() -> Result<Self> {
 | 
				
			||||||
 | 
					        let url = "postgres://postgres:postgres@127.0.0.1:5432/persmgr";
 | 
				
			||||||
 | 
					        let pool = PgPoolOptions::new().connect(url).await?;
 | 
				
			||||||
 | 
					        Ok(Self { pool })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn pool(&self) -> &CurrPool {
 | 
				
			||||||
 | 
					        &self.pool
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										1
									
								
								src/db/tables/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/db/tables/mod.rs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1 @@
 | 
				
			||||||
 | 
					pub mod user;
 | 
				
			||||||
							
								
								
									
										135
									
								
								src/db/tables/user.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								src/db/tables/user.rs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,135 @@
 | 
				
			||||||
 | 
					use anyhow::bail;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::db::CurrPool;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Default, Clone)]
 | 
				
			||||||
 | 
					pub struct User {
 | 
				
			||||||
 | 
					    pub id: i64,
 | 
				
			||||||
 | 
					    pub email: String,
 | 
				
			||||||
 | 
					    pub verified_email: bool,
 | 
				
			||||||
 | 
					    pub username: String,
 | 
				
			||||||
 | 
					    pub pw_hash: String,
 | 
				
			||||||
 | 
					    pub pw_salt: String,
 | 
				
			||||||
 | 
					    pub pfp_id: i64,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl User {
 | 
				
			||||||
 | 
					    pub async fn get_by_id(pool: &CurrPool, id: i64) -> anyhow::Result<Self> {
 | 
				
			||||||
 | 
					        let user = sqlx::query_as!(User, "SELECT * FROM users WHERE id = $1", id)
 | 
				
			||||||
 | 
					            .fetch_one(pool)
 | 
				
			||||||
 | 
					            .await?;
 | 
				
			||||||
 | 
					        Ok(user)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    pub async fn get_by_email(pool: &CurrPool, email: String) -> anyhow::Result<Self> {
 | 
				
			||||||
 | 
					        let user = sqlx::query_as!(User, "SELECT * FROM users WHERE email = $1", email)
 | 
				
			||||||
 | 
					            .fetch_one(pool)
 | 
				
			||||||
 | 
					            .await?;
 | 
				
			||||||
 | 
					        Ok(user)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    pub async fn get_by_username(pool: &CurrPool, username: String) -> anyhow::Result<Self> {
 | 
				
			||||||
 | 
					        let user = sqlx::query_as!(User, "SELECT * FROM users WHERE username = $1", username)
 | 
				
			||||||
 | 
					            .fetch_one(pool)
 | 
				
			||||||
 | 
					            .await?;
 | 
				
			||||||
 | 
					        Ok(user)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    pub async fn insert_new(&self, pool: &CurrPool) -> anyhow::Result<()> {
 | 
				
			||||||
 | 
					        sqlx::query!(
 | 
				
			||||||
 | 
					            r#"
 | 
				
			||||||
 | 
					            INSERT INTO users (email, username, pw_hash, pw_salt)
 | 
				
			||||||
 | 
					            VALUES ($1, $2, $3, $4)
 | 
				
			||||||
 | 
					            "#,
 | 
				
			||||||
 | 
					            self.email,
 | 
				
			||||||
 | 
					            self.username,
 | 
				
			||||||
 | 
					            self.pw_hash,
 | 
				
			||||||
 | 
					            self.pw_salt
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        .execute(pool)
 | 
				
			||||||
 | 
					        .await?;
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub async fn set_username(
 | 
				
			||||||
 | 
					        &mut self,
 | 
				
			||||||
 | 
					        pool: &CurrPool,
 | 
				
			||||||
 | 
					        username: String,
 | 
				
			||||||
 | 
					    ) -> anyhow::Result<&mut Self> {
 | 
				
			||||||
 | 
					        let new_user = sqlx::query_as!(
 | 
				
			||||||
 | 
					            User,
 | 
				
			||||||
 | 
					            "UPDATE users SET username = $1 WHERE id = $2 RETURNING *",
 | 
				
			||||||
 | 
					            username,
 | 
				
			||||||
 | 
					            self.id
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        .fetch_one(pool)
 | 
				
			||||||
 | 
					        .await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        *self = new_user;
 | 
				
			||||||
 | 
					        Ok(self)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub async fn set_email(&mut self, pool: &CurrPool, email: String) -> anyhow::Result<&mut Self> {
 | 
				
			||||||
 | 
					        let new_user = sqlx::query_as!(
 | 
				
			||||||
 | 
					            User,
 | 
				
			||||||
 | 
					            "UPDATE users SET email = $1 WHERE id = $2 RETURNING *",
 | 
				
			||||||
 | 
					            email,
 | 
				
			||||||
 | 
					            self.id
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        .fetch_one(pool)
 | 
				
			||||||
 | 
					        .await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        *self = new_user;
 | 
				
			||||||
 | 
					        Ok(self)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub async fn set_pw_hash(
 | 
				
			||||||
 | 
					        &mut self,
 | 
				
			||||||
 | 
					        pool: &CurrPool,
 | 
				
			||||||
 | 
					        pw_hash: String,
 | 
				
			||||||
 | 
					    ) -> anyhow::Result<&mut Self> {
 | 
				
			||||||
 | 
					        let new_user = sqlx::query_as!(
 | 
				
			||||||
 | 
					            User,
 | 
				
			||||||
 | 
					            "UPDATE users SET pw_hash = $1 WHERE id = $2 RETURNING *",
 | 
				
			||||||
 | 
					            pw_hash,
 | 
				
			||||||
 | 
					            self.id
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        .fetch_one(pool)
 | 
				
			||||||
 | 
					        .await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        *self = new_user;
 | 
				
			||||||
 | 
					        Ok(self)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    pub async fn set_pw_salt(
 | 
				
			||||||
 | 
					        &mut self,
 | 
				
			||||||
 | 
					        pool: &CurrPool,
 | 
				
			||||||
 | 
					        pw_salt: String,
 | 
				
			||||||
 | 
					    ) -> anyhow::Result<&mut Self> {
 | 
				
			||||||
 | 
					        let new_user = sqlx::query_as!(
 | 
				
			||||||
 | 
					            User,
 | 
				
			||||||
 | 
					            "UPDATE users SET pw_salt = $1 WHERE id = $2 RETURNING *",
 | 
				
			||||||
 | 
					            pw_salt,
 | 
				
			||||||
 | 
					            self.id
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        .fetch_one(pool)
 | 
				
			||||||
 | 
					        .await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        *self = new_user;
 | 
				
			||||||
 | 
					        Ok(self)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub async fn set_email_verification_status(
 | 
				
			||||||
 | 
					        &mut self,
 | 
				
			||||||
 | 
					        pool: &CurrPool,
 | 
				
			||||||
 | 
					        status: bool,
 | 
				
			||||||
 | 
					    ) -> anyhow::Result<&mut Self> {
 | 
				
			||||||
 | 
					        let new_user = sqlx::query_as!(
 | 
				
			||||||
 | 
					            User,
 | 
				
			||||||
 | 
					            "UPDATE users SET verified_email = $1 WHERE id = $2 RETURNING *",
 | 
				
			||||||
 | 
					            status,
 | 
				
			||||||
 | 
					            self.id
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        .fetch_one(pool)
 | 
				
			||||||
 | 
					        .await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        *self = new_user;
 | 
				
			||||||
 | 
					        Ok(self)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										22
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										22
									
								
								src/main.rs
									
									
									
									
									
								
							| 
						 | 
					@ -1,10 +1,13 @@
 | 
				
			||||||
use axum::Router;
 | 
					use axum::Router;
 | 
				
			||||||
use tower::ServiceBuilder;
 | 
					use tower::ServiceBuilder;
 | 
				
			||||||
 | 
					use tower_cookies::cookie::time::Duration;
 | 
				
			||||||
use tower_http::{services::ServeDir, trace::TraceLayer};
 | 
					use tower_http::{services::ServeDir, trace::TraceLayer};
 | 
				
			||||||
 | 
					use tower_sessions::{MemoryStore, SessionManagerLayer};
 | 
				
			||||||
use tracing::{info, level_filters::LevelFilter};
 | 
					use tracing::{info, level_filters::LevelFilter};
 | 
				
			||||||
use tracing_subscriber::FmtSubscriber;
 | 
					use tracing_subscriber::FmtSubscriber;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
mod api;
 | 
					mod api;
 | 
				
			||||||
 | 
					mod db;
 | 
				
			||||||
mod pages;
 | 
					mod pages;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[tokio::main]
 | 
					#[tokio::main]
 | 
				
			||||||
| 
						 | 
					@ -14,13 +17,28 @@ async fn main() {
 | 
				
			||||||
        .finish();
 | 
					        .finish();
 | 
				
			||||||
    tracing::subscriber::set_global_default(sub).unwrap();
 | 
					    tracing::subscriber::set_global_default(sub).unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    info!("Connecting to DB");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let pool = db::Database::connect().await.unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    info!("Server starting");
 | 
					    info!("Server starting");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let session_store = MemoryStore::default();
 | 
				
			||||||
 | 
					    let session_layer = SessionManagerLayer::new(session_store)
 | 
				
			||||||
 | 
					        .with_secure(false)
 | 
				
			||||||
 | 
					        .with_expiry(tower_sessions::Expiry::OnInactivity(Duration::days(30)));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let app = Router::new()
 | 
					    let app = Router::new()
 | 
				
			||||||
        .merge(pages::register_routes())
 | 
					 | 
				
			||||||
        .merge(api::register_routes())
 | 
					        .merge(api::register_routes())
 | 
				
			||||||
 | 
					        .merge(pages::register_routes())
 | 
				
			||||||
 | 
					        .with_state(pool)
 | 
				
			||||||
        .fallback_service(ServiceBuilder::new().service(ServeDir::new("res")))
 | 
					        .fallback_service(ServiceBuilder::new().service(ServeDir::new("res")))
 | 
				
			||||||
        .layer(ServiceBuilder::new().layer(TraceLayer::new_for_http()));
 | 
					        .layer(
 | 
				
			||||||
 | 
					            ServiceBuilder::new()
 | 
				
			||||||
 | 
					                .layer(TraceLayer::new_for_http())
 | 
				
			||||||
 | 
					                .layer(tower_livereload::LiveReloadLayer::new())
 | 
				
			||||||
 | 
					                .layer(session_layer),
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let laddr = "0.0.0.0:8080";
 | 
					    let laddr = "0.0.0.0:8080";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										12
									
								
								src/pages/login.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/pages/login.rs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,12 @@
 | 
				
			||||||
 | 
					use askama::Template;
 | 
				
			||||||
 | 
					use axum::{http::StatusCode, response::Html};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Template, Clone)]
 | 
				
			||||||
 | 
					#[template(path = "login.html")]
 | 
				
			||||||
 | 
					pub struct PageTemplate {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub async fn page() -> (StatusCode, Html<String>) {
 | 
				
			||||||
 | 
					    let page = PageTemplate {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    (StatusCode::OK, Html(page.render().unwrap()))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,14 +1,20 @@
 | 
				
			||||||
use axum::{Router, routing::get};
 | 
					use axum::{Router, routing::get};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::db;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub mod documents;
 | 
					pub mod documents;
 | 
				
			||||||
pub mod events;
 | 
					pub mod events;
 | 
				
			||||||
pub mod index;
 | 
					pub mod index;
 | 
				
			||||||
 | 
					pub mod login;
 | 
				
			||||||
 | 
					pub mod register;
 | 
				
			||||||
pub mod roster;
 | 
					pub mod roster;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn register_routes() -> Router {
 | 
					pub fn register_routes() -> Router<db::Database> {
 | 
				
			||||||
    Router::new()
 | 
					    Router::new()
 | 
				
			||||||
        .route("/", get(index::page))
 | 
					        .route("/", get(index::page))
 | 
				
			||||||
        .route("/roster", get(roster::page))
 | 
					        .route("/roster", get(roster::page))
 | 
				
			||||||
        .route("/events", get(events::page))
 | 
					        .route("/events", get(events::page))
 | 
				
			||||||
        .route("/documents", get(documents::page))
 | 
					        .route("/documents", get(documents::page))
 | 
				
			||||||
 | 
					        .route("/login", get(login::page))
 | 
				
			||||||
 | 
					        .route("/register", get(register::page))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										12
									
								
								src/pages/register.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/pages/register.rs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,12 @@
 | 
				
			||||||
 | 
					use askama::Template;
 | 
				
			||||||
 | 
					use axum::{http::StatusCode, response::Html};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Template, Clone)]
 | 
				
			||||||
 | 
					#[template(path = "register.html")]
 | 
				
			||||||
 | 
					pub struct PageTemplate {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub async fn page() -> (StatusCode, Html<String>) {
 | 
				
			||||||
 | 
					    let page = PageTemplate {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    (StatusCode::OK, Html(page.render().unwrap()))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -5,7 +5,7 @@
 | 
				
			||||||
        <meta name="viewport" content="width=device-width, initial-scale=1">
 | 
					        <meta name="viewport" content="width=device-width, initial-scale=1">
 | 
				
			||||||
        <title>{% block title %}{% endblock %}</title>
 | 
					        <title>{% block title %}{% endblock %}</title>
 | 
				
			||||||
        <link href="css/global.css" rel="stylesheet">
 | 
					        <link href="css/global.css" rel="stylesheet">
 | 
				
			||||||
        <script src="/js/global.js"></script>
 | 
					        <script src="/js/global.js" defer></script>
 | 
				
			||||||
        {% block headers %}{% endblock %}
 | 
					        {% block headers %}{% endblock %}
 | 
				
			||||||
    </head>
 | 
					    </head>
 | 
				
			||||||
    <body>
 | 
					    <body>
 | 
				
			||||||
| 
						 | 
					@ -25,6 +25,9 @@
 | 
				
			||||||
                </button>
 | 
					                </button>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <div>
 | 
					            <div>
 | 
				
			||||||
 | 
					                <button id="topnav_login" class="topnav_button" onclick="location.href='/login'">Login</button>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <div id="topnav_profile">
 | 
					                <div id="topnav_profile">
 | 
				
			||||||
                    <span>MCorange</span>
 | 
					                    <span>MCorange</span>
 | 
				
			||||||
                    <img src="/img/default_pfp.png" alt="Profile Picture" onclick="on_pfp_click()">
 | 
					                    <img src="/img/default_pfp.png" alt="Profile Picture" onclick="on_pfp_click()">
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										26
									
								
								templates/login.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								templates/login.html
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,26 @@
 | 
				
			||||||
 | 
					{% extends "base.html" %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block title %}Login{% endblock %}
 | 
				
			||||||
 | 
					{% block headers %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block content %}
 | 
				
			||||||
 | 
					    <h1>Login</h1>
 | 
				
			||||||
 | 
					    <form action="/api/user/login" method="POST">
 | 
				
			||||||
 | 
					        <div>
 | 
				
			||||||
 | 
					            <label for="username">Username:</label>
 | 
				
			||||||
 | 
					            <input type="text" id="username" name="username" required>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div>
 | 
				
			||||||
 | 
					            <label for="password">Password:</label>
 | 
				
			||||||
 | 
					            <input type="password" id="password" name="password" required>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <button type="submit">Login</button>
 | 
				
			||||||
 | 
					    </form>
 | 
				
			||||||
 | 
					    <div> 
 | 
				
			||||||
 | 
					        No account yet? 
 | 
				
			||||||
 | 
					        <a href="/register">Register here!</a>
 | 
				
			||||||
 | 
					     </div> 
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
							
								
								
									
										71
									
								
								templates/register.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								templates/register.html
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,71 @@
 | 
				
			||||||
 | 
					{% extends "base.html" %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block title %}Register{% endblock %}
 | 
				
			||||||
 | 
					{% block headers %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block content %}
 | 
				
			||||||
 | 
					    <h1>Login</h1>
 | 
				
			||||||
 | 
					    <form action="/api/user/register" method="POST">
 | 
				
			||||||
 | 
					        <div>
 | 
				
			||||||
 | 
					            <label for="email">Email:</label>
 | 
				
			||||||
 | 
					            <input type="text" id="email" name="email">
 | 
				
			||||||
 | 
					            <span id="email_error" style="color:red; margin-left:5px;"></span>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div>
 | 
				
			||||||
 | 
					            <label for="username">Username:</label>
 | 
				
			||||||
 | 
					            <input type="text" id="username" name="username">
 | 
				
			||||||
 | 
					            <span id="username_error" style="color:red; margin-left:5px;"></span>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div>
 | 
				
			||||||
 | 
					            <label for="username">Password:</label>
 | 
				
			||||||
 | 
					            <input type="password" id="password" name="password">
 | 
				
			||||||
 | 
					            <span id="password_error" style="color:red; margin-left:5px;"></span>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div>
 | 
				
			||||||
 | 
					            <label for="password">Password:</label>
 | 
				
			||||||
 | 
					            <input type="password" id="password2">
 | 
				
			||||||
 | 
					            <span id="confirm_error" style="color:red; margin-left:5px;"></span>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <button type="submit" id="submit_btn">Register</button>
 | 
				
			||||||
 | 
					    </form>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    <script>
 | 
				
			||||||
 | 
					        const password = document.getElementById('password');
 | 
				
			||||||
 | 
					        const password2 = document.getElementById('password2');
 | 
				
			||||||
 | 
					        const submit_btn = document.getElementById('submit_btn');
 | 
				
			||||||
 | 
					        const username = document.getElementById('username');
 | 
				
			||||||
 | 
					        const email = document.getElementById('email');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // const password_error = document.getElementById('password_error');
 | 
				
			||||||
 | 
					        // const password2_error = document.getElementById('confirm_error');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        function validate() {
 | 
				
			||||||
 | 
					            submit_btn.disabled = false;
 | 
				
			||||||
 | 
					            if (!(password.value && password2.value) || password.value !== password2.value) {
 | 
				
			||||||
 | 
					                submit_btn.disabled = true;
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (password.length < 8) {
 | 
				
			||||||
 | 
					                submit_btn.disabled = true;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (username.length < 3) {
 | 
				
			||||||
 | 
					                submit_btn.disabled = true;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (email.length < 5) {
 | 
				
			||||||
 | 
					                submit_btn.disabled = true;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // check on every key press
 | 
				
			||||||
 | 
					        password.addEventListener('input', validate);
 | 
				
			||||||
 | 
					        confirmPassword.addEventListener('input', validate);
 | 
				
			||||||
 | 
					        username.addEventListener("input", validate);
 | 
				
			||||||
 | 
					        email.addEventListener("input", validate);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    </script>
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user