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]
|
[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-files = "0.6.5"
|
||||||
actix-web = "4.5.1"
|
actix-web = "4.5.1"
|
||||||
actix-web-lab = "0.20.2"
|
actix-web-lab = "0.20.2"
|
||||||
anyhow = "1.0.81"
|
|
||||||
askama = "0.12.1"
|
askama = "0.12.1"
|
||||||
camino = "1.1.6"
|
|
||||||
clap = { version = "4.5.3", features = ["derive"] }
|
#? database
|
||||||
log = "0.4.21"
|
sqlx = { version = "0.7.4", features = ["tls-rustls", "runtime-tokio", "postgres", "uuid"] }
|
||||||
serde = { version = "1.0.197", features = ["derive"] }
|
uuid = { version = "1.8.0", features = ["v4"] }
|
||||||
simplelog = { version = "0.12.2", features = ["paris"] }
|
time = { version = "0.3.34", features = ["macros"] }
|
||||||
toml = "0.8.12"
|
crypto = { version = "0.5.1", features = ["digest", "password-hash"] }
|
||||||
# time = { version = "0.3.34", features = ["macros"] }
|
bcrypt = "0.15.1"
|
||||||
|
futures = "0.3.30"
|
||||||
|
|
||||||
|
[profile.dev.package.sqlx-macros]
|
||||||
|
opt-level = 3
|
||||||
|
|
|
@ -3,3 +3,6 @@ debug=true
|
||||||
[webserver]
|
[webserver]
|
||||||
host="0.0.0.0"
|
host="0.0.0.0"
|
||||||
port=8080
|
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)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub debug: bool,
|
pub debug: bool,
|
||||||
pub webserver: ConfigWebserver
|
pub webserver: ConfigWebserver,
|
||||||
|
pub database: ConfigDatabase
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
|
@ -11,3 +12,8 @@ pub struct ConfigWebserver {
|
||||||
pub host: String,
|
pub host: String,
|
||||||
pub port: u16,
|
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 logger;
|
||||||
mod cli;
|
mod cli;
|
||||||
mod config;
|
mod config;
|
||||||
|
mod database;
|
||||||
|
|
||||||
#[actix_web::main]
|
#[actix_web::main]
|
||||||
async fn main() -> std::io::Result<()> {
|
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}");
|
log::error!("Actix had an error: {e}");
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -4,15 +4,17 @@ mod templates;
|
||||||
|
|
||||||
use actix_web::{web, App, HttpServer};
|
use actix_web::{web, App, HttpServer};
|
||||||
use actix_files as actix_fs;
|
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);
|
let bindip = format!("{}:{}", config.webserver.host, config.webserver.port);
|
||||||
|
|
||||||
log::info!("Serving an http server at http://{bindip}");
|
log::info!("Serving an http server at http://{bindip}");
|
||||||
HttpServer::new(|| {
|
HttpServer::new(move || {
|
||||||
App::new()
|
App::new()
|
||||||
|
.app_data(actix_web::web::Data::new(database.clone()))
|
||||||
.route("/", web::get().to(routes::index)) // index.html
|
.route("/", web::get().to(routes::index)) // index.html
|
||||||
.service(actix_fs::Files::new("/static", "./static").index_file("index.html")) // static directoryh
|
.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_lab::respond::Html;
|
||||||
use actix_web::{Result, Responder};
|
use actix_web::{web::Data, Responder, Result};
|
||||||
use askama::Template;
|
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 {
|
let html = IndexTemplate {
|
||||||
placeholder: "hewwo world"
|
placeholder: "hewwo world"
|
||||||
}.render().expect("Failed to render index.html");
|
}.render().expect("Failed to render index.html");
|
||||||
|
|
Loading…
Reference in New Issue
Block a user