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
|
||||
|
|
|
@ -2,4 +2,7 @@ debug=true
|
|||
|
||||
[webserver]
|
||||
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,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
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