Implemented database (untested), managed to make actix pass it to routes

This commit is contained in:
2024-03-25 23:26:56 +02:00
parent b18e193173
commit 2cb4acc604
14 changed files with 1125 additions and 19 deletions

View File

@@ -3,11 +3,17 @@ use serde::Deserialize;
#[derive(Debug, Deserialize)]
pub struct Config {
pub debug: bool,
pub webserver: ConfigWebserver
pub webserver: ConfigWebserver,
pub database: ConfigDatabase
}
#[derive(Debug, Deserialize)]
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
View 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
}
}

View File

@@ -0,0 +1 @@
pub mod users;

View 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(())
}
}

View File

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

View File

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

View File

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