Basic create user account
This commit is contained in:
parent
9912428fd6
commit
0bfdc305b1
@ -33,5 +33,5 @@ diesel migation run
|
|||||||
> Note: You can access the database directly using this command:
|
> Note: You can access the database directly using this command:
|
||||||
>
|
>
|
||||||
> ```bash
|
> ```bash
|
||||||
> PGPASSWORD=pass psql -h localhost -p 5432 -U user -d bioclimsol
|
> PGPASSWORD=pass psql -h localhost -p 5432 -U user -d geneit
|
||||||
> ```
|
> ```
|
32
geneit_backend/Cargo.lock
generated
32
geneit_backend/Cargo.lock
generated
@ -68,6 +68,17 @@ dependencies = [
|
|||||||
"syn 1.0.109",
|
"syn 1.0.109",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "actix-remote-ip"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7629b357d4705cf3f1e31f989f48ecd56027112f7d52dcf06dd96ee197065f8e"
|
||||||
|
dependencies = [
|
||||||
|
"actix-web",
|
||||||
|
"futures-util",
|
||||||
|
"log",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "actix-router"
|
name = "actix-router"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
@ -285,6 +296,12 @@ dependencies = [
|
|||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anyhow"
|
||||||
|
version = "1.0.71"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
@ -584,6 +601,17 @@ version = "0.3.28"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"
|
checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-macro"
|
||||||
|
version = "0.3.28"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.16",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-sink"
|
name = "futures-sink"
|
||||||
version = "0.3.28"
|
version = "0.3.28"
|
||||||
@ -603,16 +631,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533"
|
checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
"futures-macro",
|
||||||
"futures-task",
|
"futures-task",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"pin-utils",
|
"pin-utils",
|
||||||
|
"slab",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "geneit_backend"
|
name = "geneit_backend"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"actix-remote-ip",
|
||||||
"actix-web",
|
"actix-web",
|
||||||
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
"diesel",
|
"diesel",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
|
@ -10,6 +10,8 @@ log = "0.4.17"
|
|||||||
env_logger = "0.10.0"
|
env_logger = "0.10.0"
|
||||||
clap = { version = "4.3.0", features = ["derive", "env"] }
|
clap = { version = "4.3.0", features = ["derive", "env"] }
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
|
anyhow = "1.0.71"
|
||||||
actix-web = "4.3.1"
|
actix-web = "4.3.1"
|
||||||
diesel = { version = "2.0.4", features = ["postgres"] }
|
diesel = { version = "2.0.4", features = ["postgres"] }
|
||||||
serde = { version = "1.0.163", features = ["derive"] }
|
serde = { version = "1.0.163", features = ["derive"] }
|
||||||
|
actix-remote-ip = "0.1.0"
|
@ -16,17 +16,25 @@ pub struct AppConfig {
|
|||||||
#[clap(short, long, env)]
|
#[clap(short, long, env)]
|
||||||
pub proxy_ip: Option<String>,
|
pub proxy_ip: Option<String>,
|
||||||
|
|
||||||
/// PostgreSQL connexion chain
|
/// PostgreSQL database host
|
||||||
#[clap(long, env, default_value = "postgres://localhost/geneit")]
|
#[clap(long, env, default_value = "localhost")]
|
||||||
db_chain: String,
|
db_host: String,
|
||||||
|
|
||||||
|
/// PostgreSQL database port
|
||||||
|
#[clap(long, env, default_value_t = 5432)]
|
||||||
|
db_port: u16,
|
||||||
|
|
||||||
/// PostgreSQL username
|
/// PostgreSQL username
|
||||||
#[clap(long, env, default_value = "user")]
|
#[clap(long, env, default_value = "user")]
|
||||||
db_username: String,
|
db_username: String,
|
||||||
|
|
||||||
/// PostgreSQL password
|
/// PostgreSQL password
|
||||||
#[clap(long, env, default_value = "user")]
|
#[clap(long, env, default_value = "pass")]
|
||||||
db_password: String,
|
db_password: String,
|
||||||
|
|
||||||
|
/// PostgreSQL database name
|
||||||
|
#[clap(long, env, default_value = "geneit")]
|
||||||
|
db_name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
@ -40,4 +48,12 @@ impl AppConfig {
|
|||||||
pub fn get() -> &'static AppConfig {
|
pub fn get() -> &'static AppConfig {
|
||||||
&ARGS
|
&ARGS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get full db connection chain
|
||||||
|
pub fn db_connection_chain(&self) -> String {
|
||||||
|
format!(
|
||||||
|
"postgres://{}:{}@{}:{}/{}",
|
||||||
|
self.db_username, self.db_password, self.db_host, self.db_port, self.db_name
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
@ -8,6 +8,11 @@ impl SizeConstraint {
|
|||||||
pub fn new(min: usize, max: usize) -> Self {
|
pub fn new(min: usize, max: usize) -> Self {
|
||||||
Self { min, max }
|
Self { min, max }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn validate(&self, val: &str) -> bool {
|
||||||
|
let len = val.trim().len();
|
||||||
|
len >= self.min && len <= self.max
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, serde::Serialize)]
|
#[derive(Debug, Clone, serde::Serialize)]
|
||||||
|
@ -1 +1,4 @@
|
|||||||
|
//! # API controller
|
||||||
|
|
||||||
|
pub mod auth_controller;
|
||||||
pub mod config_controller;
|
pub mod config_controller;
|
||||||
|
43
geneit_backend/src/controllers/auth_controller.rs
Normal file
43
geneit_backend/src/controllers/auth_controller.rs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
use crate::constants::StaticConstraints;
|
||||||
|
use crate::services::users_service;
|
||||||
|
use actix_remote_ip::RemoteIP;
|
||||||
|
use actix_web::error::ErrorInternalServerError;
|
||||||
|
use actix_web::{web, HttpResponse};
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
pub struct CreateAccountBody {
|
||||||
|
name: String,
|
||||||
|
email: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new account
|
||||||
|
pub async fn create_account(
|
||||||
|
_remote_ip: RemoteIP,
|
||||||
|
req: web::Json<CreateAccountBody>,
|
||||||
|
) -> actix_web::Result<HttpResponse> {
|
||||||
|
// TODO : rate limiting
|
||||||
|
|
||||||
|
// TODO : check if email is valid
|
||||||
|
|
||||||
|
// Check parameters
|
||||||
|
let constraints = StaticConstraints::default();
|
||||||
|
if !constraints.user_name_len.validate(&req.name) || !constraints.mail_len.validate(&req.email)
|
||||||
|
{
|
||||||
|
return Ok(HttpResponse::BadRequest().json("Size constraints were not respected!"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO : check the mail address
|
||||||
|
|
||||||
|
// Create the account
|
||||||
|
let user_id = users_service::create_account(&req.name, &req.email)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
log::error!("Failed to create user! {e}");
|
||||||
|
ErrorInternalServerError(e)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// TODO : trigger reset password (send mail)
|
||||||
|
|
||||||
|
// Account successfully created
|
||||||
|
Ok(HttpResponse::Created().finish())
|
||||||
|
}
|
25
geneit_backend/src/db_connection.rs
Normal file
25
geneit_backend/src/db_connection.rs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
//! # Database connection management
|
||||||
|
|
||||||
|
use crate::app_config::AppConfig;
|
||||||
|
use diesel::{Connection, PgConnection};
|
||||||
|
use std::cell::RefCell;
|
||||||
|
thread_local! {
|
||||||
|
static POSTGRES_CONNECTION: RefCell<Option<PgConnection>> = RefCell::new(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Execute a request on the database
|
||||||
|
pub fn execute<E, I>(cb: E) -> anyhow::Result<I>
|
||||||
|
where
|
||||||
|
E: FnOnce(&mut PgConnection) -> anyhow::Result<I>,
|
||||||
|
{
|
||||||
|
// Establish connection if required
|
||||||
|
if POSTGRES_CONNECTION.with(|i| i.borrow().is_none()) {
|
||||||
|
let database_url = AppConfig::get().db_connection_chain();
|
||||||
|
let conn = PgConnection::establish(&database_url)
|
||||||
|
.unwrap_or_else(|_| panic!("Error connecting to {}", database_url));
|
||||||
|
|
||||||
|
POSTGRES_CONNECTION.with(|i| *i.borrow_mut() = Some(conn))
|
||||||
|
}
|
||||||
|
|
||||||
|
POSTGRES_CONNECTION.with(|i| cb(i.borrow_mut().as_mut().unwrap()))
|
||||||
|
}
|
@ -1,4 +1,10 @@
|
|||||||
pub mod app_config;
|
pub mod app_config;
|
||||||
pub mod constants;
|
pub mod constants;
|
||||||
pub mod controllers;
|
pub mod controllers;
|
||||||
|
pub mod services;
|
||||||
|
pub mod utils;
|
||||||
|
|
||||||
|
// Diesel specific
|
||||||
|
pub mod db_connection;
|
||||||
|
pub mod models;
|
||||||
pub mod schema;
|
pub mod schema;
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
use actix_remote_ip::RemoteIPConfig;
|
||||||
|
use actix_web::middleware::Logger;
|
||||||
use actix_web::{web, App, HttpServer};
|
use actix_web::{web, App, HttpServer};
|
||||||
use geneit_backend::app_config::AppConfig;
|
use geneit_backend::app_config::AppConfig;
|
||||||
use geneit_backend::controllers::config_controller;
|
use geneit_backend::controllers::{auth_controller, config_controller};
|
||||||
|
|
||||||
#[actix_web::main]
|
#[actix_web::main]
|
||||||
async fn main() -> std::io::Result<()> {
|
async fn main() -> std::io::Result<()> {
|
||||||
@ -10,12 +12,21 @@ async fn main() -> std::io::Result<()> {
|
|||||||
|
|
||||||
HttpServer::new(|| {
|
HttpServer::new(|| {
|
||||||
App::new()
|
App::new()
|
||||||
|
.wrap(Logger::default())
|
||||||
|
.app_data(web::Data::new(RemoteIPConfig {
|
||||||
|
proxy: AppConfig::get().proxy_ip.clone(),
|
||||||
|
}))
|
||||||
// Config controller
|
// Config controller
|
||||||
.route("/", web::get().to(config_controller::home))
|
.route("/", web::get().to(config_controller::home))
|
||||||
.route(
|
.route(
|
||||||
"/config/static",
|
"/config/static",
|
||||||
web::get().to(config_controller::static_config),
|
web::get().to(config_controller::static_config),
|
||||||
)
|
)
|
||||||
|
// Auth controller
|
||||||
|
.route(
|
||||||
|
"/auth/create_account",
|
||||||
|
web::post().to(auth_controller::create_account),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
.bind(AppConfig::get().listen_address.as_str())?
|
.bind(AppConfig::get().listen_address.as_str())?
|
||||||
.run()
|
.run()
|
||||||
|
34
geneit_backend/src/models.rs
Normal file
34
geneit_backend/src/models.rs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
use crate::schema::users;
|
||||||
|
use diesel::prelude::*;
|
||||||
|
|
||||||
|
/// User ID holder
|
||||||
|
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct UserID(i32);
|
||||||
|
|
||||||
|
#[derive(Queryable, Debug)]
|
||||||
|
pub struct User {
|
||||||
|
id: i32,
|
||||||
|
name: String,
|
||||||
|
email: String,
|
||||||
|
password: Option<String>,
|
||||||
|
reset_password_token: Option<String>,
|
||||||
|
time_create: i64,
|
||||||
|
time_gen_reset_token: i64,
|
||||||
|
time_activate: i64,
|
||||||
|
active: bool,
|
||||||
|
admin: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl User {
|
||||||
|
pub fn id(&self) -> UserID {
|
||||||
|
UserID(self.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Insertable)]
|
||||||
|
#[diesel(table_name = users)]
|
||||||
|
pub struct NewUser<'a> {
|
||||||
|
pub name: &'a str,
|
||||||
|
pub email: &'a str,
|
||||||
|
pub time_create: i64,
|
||||||
|
}
|
3
geneit_backend/src/services/mod.rs
Normal file
3
geneit_backend/src/services/mod.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
//! # Backend services
|
||||||
|
|
||||||
|
pub mod users_service;
|
22
geneit_backend/src/services/users_service.rs
Normal file
22
geneit_backend/src/services/users_service.rs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
//! # Users service
|
||||||
|
|
||||||
|
use crate::db_connection;
|
||||||
|
use crate::models::{NewUser, User};
|
||||||
|
use crate::schema::users;
|
||||||
|
use crate::utils::time_utils::time;
|
||||||
|
use diesel::prelude::*;
|
||||||
|
|
||||||
|
/// Create a new account
|
||||||
|
pub async fn create_account(name: &str, email: &str) -> anyhow::Result<User> {
|
||||||
|
db_connection::execute(|conn| {
|
||||||
|
let res = diesel::insert_into(users::table)
|
||||||
|
.values(&NewUser {
|
||||||
|
name: name.trim(),
|
||||||
|
email: email.trim(),
|
||||||
|
time_create: time() as i64,
|
||||||
|
})
|
||||||
|
.get_result(conn)?;
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
})
|
||||||
|
}
|
3
geneit_backend/src/utils.rs
Normal file
3
geneit_backend/src/utils.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
//! # App utilities
|
||||||
|
|
||||||
|
pub mod time_utils;
|
11
geneit_backend/src/utils/time_utils.rs
Normal file
11
geneit_backend/src/utils/time_utils.rs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
//! # Time utilities
|
||||||
|
|
||||||
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
|
/// Get the current time since epoch
|
||||||
|
pub fn time() -> u64 {
|
||||||
|
SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.unwrap()
|
||||||
|
.as_secs()
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user