Implemented database (untested), managed to make actix pass it to routes
This commit is contained in:
		
							parent
							
								
									b18e193173
								
							
						
					
					
						commit
						2cb4acc604
					
				
							
								
								
									
										908
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										908
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										29
									
								
								Cargo.toml
									
									
									
									
									
								
							
							
						
						
									
										29
									
								
								Cargo.toml
									
									
									
									
									
								
							| 
						 | 
				
			
			@ -8,15 +8,28 @@ authors = [
 | 
			
		|||
]
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
#? base
 | 
			
		||||
clap = { version = "4.5.3", features = ["derive"] }
 | 
			
		||||
anyhow = "1.0.81"
 | 
			
		||||
camino = "1.1.6"
 | 
			
		||||
simplelog = { version = "0.12.2", features = ["paris"] }
 | 
			
		||||
toml = "0.8.12"
 | 
			
		||||
serde = { version = "1.0.197", features = ["derive"] }
 | 
			
		||||
log = "0.4.21"
 | 
			
		||||
 | 
			
		||||
#? web
 | 
			
		||||
actix-files = "0.6.5"
 | 
			
		||||
actix-web = "4.5.1"
 | 
			
		||||
actix-web-lab = "0.20.2"
 | 
			
		||||
anyhow = "1.0.81"
 | 
			
		||||
askama = "0.12.1"
 | 
			
		||||
camino = "1.1.6"
 | 
			
		||||
clap = { version = "4.5.3", features = ["derive"] }
 | 
			
		||||
log = "0.4.21"
 | 
			
		||||
serde = { version = "1.0.197", features = ["derive"] }
 | 
			
		||||
simplelog = { version = "0.12.2", features = ["paris"] }
 | 
			
		||||
toml = "0.8.12"
 | 
			
		||||
# time = { version = "0.3.34", features = ["macros"] }
 | 
			
		||||
 | 
			
		||||
#? database
 | 
			
		||||
sqlx = { version = "0.7.4", features = ["tls-rustls", "runtime-tokio", "postgres", "uuid"] }
 | 
			
		||||
uuid = { version = "1.8.0", features = ["v4"] }
 | 
			
		||||
time = { version = "0.3.34", features = ["macros"] }
 | 
			
		||||
crypto = { version = "0.5.1", features = ["digest", "password-hash"] }
 | 
			
		||||
bcrypt = "0.15.1"
 | 
			
		||||
futures = "0.3.30"
 | 
			
		||||
 | 
			
		||||
[profile.dev.package.sqlx-macros]
 | 
			
		||||
opt-level = 3
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,3 +3,6 @@ debug=true
 | 
			
		|||
[webserver]
 | 
			
		||||
host="0.0.0.0"
 | 
			
		||||
port=8080
 | 
			
		||||
 | 
			
		||||
[database]
 | 
			
		||||
url="postgresql://postgres@localhost/mctest"
 | 
			
		||||
							
								
								
									
										1
									
								
								env.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								env.sh
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
export DATABASE_URL="postgresql://postgres@localhost/mctest"
 | 
			
		||||
							
								
								
									
										0
									
								
								migrations/.keep
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								migrations/.keep
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										2
									
								
								migrations/2024-03-25-183335_users/down.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								migrations/2024-03-25-183335_users/down.sql
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,2 @@
 | 
			
		|||
-- This file should undo anything in `up.sql`
 | 
			
		||||
DROP TABLE Users
 | 
			
		||||
							
								
								
									
										9
									
								
								migrations/2024-03-25-183335_users/up.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								migrations/2024-03-25-183335_users/up.sql
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,9 @@
 | 
			
		|||
-- Your SQL goes here
 | 
			
		||||
CREATE TABLE IF NOT EXISTS Users (
 | 
			
		||||
    id UUID NOT NULL UNIQUE,
 | 
			
		||||
    email TEXT NOT NULL,
 | 
			
		||||
    username TEXT NOT NULL,
 | 
			
		||||
    pw_hash TEXT NOT NULL,
 | 
			
		||||
    permissions BIGINT NOT NULL,
 | 
			
		||||
    PRIMARY KEY (id)
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			@ -3,7 +3,8 @@ use serde::Deserialize;
 | 
			
		|||
#[derive(Debug, Deserialize)]
 | 
			
		||||
pub struct Config {
 | 
			
		||||
    pub debug: bool,
 | 
			
		||||
    pub webserver: ConfigWebserver
 | 
			
		||||
    pub webserver: ConfigWebserver,
 | 
			
		||||
    pub database: ConfigDatabase
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
| 
						 | 
				
			
			@ -11,3 +12,8 @@ pub struct ConfigWebserver {
 | 
			
		|||
    pub host: String,
 | 
			
		||||
    pub port: u16,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
pub struct ConfigDatabase {
 | 
			
		||||
    pub url: String,
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										28
									
								
								src/database/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								src/database/mod.rs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,28 @@
 | 
			
		|||
use sqlx::{postgres::PgPoolOptions, Postgres};
 | 
			
		||||
 | 
			
		||||
use crate::config::definition::Config;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
pub mod models;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
pub struct Database {
 | 
			
		||||
    connection: sqlx::Pool<Postgres>
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
impl Database {
 | 
			
		||||
    pub async fn new(config: &Config) -> anyhow::Result<Self> {
 | 
			
		||||
        let conn = PgPoolOptions::new()
 | 
			
		||||
            .max_connections(5)
 | 
			
		||||
            .connect(&config.database.url).await?;
 | 
			
		||||
        Ok(Self {
 | 
			
		||||
            connection: conn
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn connection(&self) -> &sqlx::Pool<Postgres> {
 | 
			
		||||
        &self.connection
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1
									
								
								src/database/models/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/database/models/mod.rs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
pub mod users;
 | 
			
		||||
							
								
								
									
										131
									
								
								src/database/models/users.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								src/database/models/users.rs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,131 @@
 | 
			
		|||
use uuid::Uuid;
 | 
			
		||||
use sqlx::Row;
 | 
			
		||||
use crate::database::Database;
 | 
			
		||||
use futures::TryStreamExt;
 | 
			
		||||
 | 
			
		||||
#[derive(sqlx::FromRow)]
 | 
			
		||||
pub struct Users {
 | 
			
		||||
    pub id: uuid::Uuid,
 | 
			
		||||
    pub email: String,
 | 
			
		||||
    pub username: String,
 | 
			
		||||
    pub pw_hash: String,
 | 
			
		||||
    pub permissions: i64,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[allow(dead_code)]
 | 
			
		||||
impl Users {
 | 
			
		||||
    pub async fn create_new(db: &mut Database, email: String, username: String, password: String) -> anyhow::Result<Uuid> {
 | 
			
		||||
        let id = Uuid::new_v4();
 | 
			
		||||
        let hash = bcrypt::hash(password, 15)?;
 | 
			
		||||
 | 
			
		||||
        sqlx::query(r#"
 | 
			
		||||
                INSERT INTO users ( id, email, username, pw_hash, permissions )
 | 
			
		||||
                VALUES ( $1, $2, $3, $4, 0 )
 | 
			
		||||
                RETURNING id
 | 
			
		||||
            "#)
 | 
			
		||||
            .bind(id)
 | 
			
		||||
            .bind(email)
 | 
			
		||||
            .bind(username)
 | 
			
		||||
            .bind(hash)
 | 
			
		||||
            .execute(db.connection())
 | 
			
		||||
            .await?;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        Ok(id)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub async fn get_by_id(db: &mut Database, id: Uuid) -> anyhow::Result<Option<Self>>{
 | 
			
		||||
        let mut rows = sqlx::query("SELECT * FROM users WHERE id = $1")
 | 
			
		||||
            .bind(id)
 | 
			
		||||
            .fetch(db.connection());
 | 
			
		||||
 | 
			
		||||
        while let Some(row) = rows.try_next().await? {
 | 
			
		||||
            return Ok(Some(Self {
 | 
			
		||||
                id: row.try_get("id")?,
 | 
			
		||||
                email: row.try_get("email")?,
 | 
			
		||||
                username: row.try_get("username")?,
 | 
			
		||||
                pw_hash: row.try_get("pw_hash")?,
 | 
			
		||||
                permissions: row.try_get("permissions")?,
 | 
			
		||||
            }));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Ok(None)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub async fn get_by_email(db: &mut Database, email: String) -> anyhow::Result<Option<Self>>{
 | 
			
		||||
        let mut rows = sqlx::query("SELECT * FROM users WHERE email = $1")
 | 
			
		||||
            .bind(email)
 | 
			
		||||
            .fetch(db.connection());
 | 
			
		||||
 | 
			
		||||
        while let Some(row) = rows.try_next().await? {
 | 
			
		||||
            return Ok(Some(Self {
 | 
			
		||||
                id: row.try_get("id")?,
 | 
			
		||||
                email: row.try_get("email")?,
 | 
			
		||||
                username: row.try_get("username")?,
 | 
			
		||||
                pw_hash: row.try_get("pw_hash")?,
 | 
			
		||||
                permissions: row.try_get("permissions")?,
 | 
			
		||||
            }));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Ok(None)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub async fn get_by_username(db: &mut Database, username: String) -> anyhow::Result<Option<Self>>{
 | 
			
		||||
        let mut rows = sqlx::query("SELECT * FROM users WHERE username = $1")
 | 
			
		||||
            .bind(username)
 | 
			
		||||
            .fetch(db.connection());
 | 
			
		||||
 | 
			
		||||
        while let Some(row) = rows.try_next().await? {
 | 
			
		||||
            return Ok(Some(Self {
 | 
			
		||||
                id: row.try_get("id")?,
 | 
			
		||||
                email: row.try_get("email")?,
 | 
			
		||||
                username: row.try_get("username")?,
 | 
			
		||||
                pw_hash: row.try_get("pw_hash")?,
 | 
			
		||||
                permissions: row.try_get("permissions")?,
 | 
			
		||||
            }));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Ok(None)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub async fn check_login(db: &mut Database, email: String, password: String) -> anyhow::Result<Option<Self>>{
 | 
			
		||||
        let Some(user) = Self::get_by_email(db, email).await? else {return Ok(None)};
 | 
			
		||||
 | 
			
		||||
        if bcrypt::verify(password, &user.pw_hash)? {
 | 
			
		||||
            Ok(Some(user))
 | 
			
		||||
        } else {
 | 
			
		||||
            Ok(None)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub async fn save(&self, db: &mut Database) -> anyhow::Result<()> {
 | 
			
		||||
        let _ = sqlx::query(r#"
 | 
			
		||||
                UPDATE users
 | 
			
		||||
                SET id = $1
 | 
			
		||||
                SET email = $2
 | 
			
		||||
                SET username = $3
 | 
			
		||||
                SET pw_hash = $4
 | 
			
		||||
                SET permissions = $5
 | 
			
		||||
            "#)
 | 
			
		||||
            .bind(&self.id)
 | 
			
		||||
            .bind(&self.email)
 | 
			
		||||
            .bind(&self.username)
 | 
			
		||||
            .bind(&self.pw_hash)
 | 
			
		||||
            .bind(&self.permissions)
 | 
			
		||||
            .execute(db.connection()).await?;
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub async fn remove_user(&self, db: &mut Database) -> anyhow::Result<()> {
 | 
			
		||||
        sqlx::query(r#"
 | 
			
		||||
                DELETE FROM users
 | 
			
		||||
                WHERE id = $1
 | 
			
		||||
            "#)
 | 
			
		||||
            .bind(self.id)
 | 
			
		||||
            .execute(db.connection())
 | 
			
		||||
            .await?;
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -4,6 +4,7 @@ mod public;
 | 
			
		|||
mod logger;
 | 
			
		||||
mod cli;
 | 
			
		||||
mod config;
 | 
			
		||||
mod database;
 | 
			
		||||
 | 
			
		||||
#[actix_web::main]
 | 
			
		||||
async fn main() -> std::io::Result<()> {
 | 
			
		||||
| 
						 | 
				
			
			@ -18,8 +19,9 @@ async fn main() -> std::io::Result<()> {
 | 
			
		|||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let Ok(database) = database::Database::new(config.get_ref()).await else {return Ok(())};
 | 
			
		||||
 | 
			
		||||
    if let Err(e) = public::start_actix(config.get_ref()).await {
 | 
			
		||||
    if let Err(e) = public::start_actix(config.get_ref(), database).await {
 | 
			
		||||
        log::error!("Actix had an error: {e}");
 | 
			
		||||
    }
 | 
			
		||||
    Ok(())
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,15 +4,17 @@ mod templates;
 | 
			
		|||
 | 
			
		||||
use actix_web::{web, App, HttpServer};
 | 
			
		||||
use actix_files as actix_fs;
 | 
			
		||||
use sqlx::database;
 | 
			
		||||
 | 
			
		||||
use crate::config::definition::Config;
 | 
			
		||||
use crate::{config::definition::Config, database::Database};
 | 
			
		||||
 | 
			
		||||
pub(crate) async fn start_actix(config: &Config) -> anyhow::Result<()> {
 | 
			
		||||
pub(crate) async fn start_actix(config: &Config, database: Database) -> anyhow::Result<()> {
 | 
			
		||||
    let bindip = format!("{}:{}", config.webserver.host, config.webserver.port);
 | 
			
		||||
 | 
			
		||||
    log::info!("Serving an http server at http://{bindip}");
 | 
			
		||||
    HttpServer::new(|| {
 | 
			
		||||
    HttpServer::new(move || {
 | 
			
		||||
        App::new()
 | 
			
		||||
            .app_data(actix_web::web::Data::new(database.clone()))
 | 
			
		||||
            .route("/", web::get().to(routes::index)) // index.html
 | 
			
		||||
            .service(actix_fs::Files::new("/static", "./static").index_file("index.html")) // static directoryh
 | 
			
		||||
    })
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,10 +1,14 @@
 | 
			
		|||
use std::sync::Mutex;
 | 
			
		||||
 | 
			
		||||
use actix_web_lab::respond::Html;
 | 
			
		||||
use actix_web::{Result, Responder};
 | 
			
		||||
use actix_web::{web::Data, Responder, Result};
 | 
			
		||||
use askama::Template;
 | 
			
		||||
 | 
			
		||||
use crate::public::templates::IndexTemplate;
 | 
			
		||||
use crate::{database::Database, public::templates::IndexTemplate};
 | 
			
		||||
 | 
			
		||||
pub async fn index() -> Result<impl Responder> {
 | 
			
		||||
 | 
			
		||||
// NOTE: Not usefull to have database here but just so u know how
 | 
			
		||||
pub async fn index(_: Data<Mutex<Database>>) -> Result<impl Responder> {
 | 
			
		||||
    let html = IndexTemplate {
 | 
			
		||||
        placeholder: "hewwo world"
 | 
			
		||||
    }.render().expect("Failed to render index.html");
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue
	
	Block a user