Basic create user account

This commit is contained in:
2023-05-24 16:19:46 +02:00
parent 9912428fd6
commit 0bfdc305b1
15 changed files with 224 additions and 8 deletions

View File

@ -16,17 +16,25 @@ pub struct AppConfig {
#[clap(short, long, env)]
pub proxy_ip: Option<String>,
/// PostgreSQL connexion chain
#[clap(long, env, default_value = "postgres://localhost/geneit")]
db_chain: String,
/// PostgreSQL database host
#[clap(long, env, default_value = "localhost")]
db_host: String,
/// PostgreSQL database port
#[clap(long, env, default_value_t = 5432)]
db_port: u16,
/// PostgreSQL username
#[clap(long, env, default_value = "user")]
db_username: String,
/// PostgreSQL password
#[clap(long, env, default_value = "user")]
#[clap(long, env, default_value = "pass")]
db_password: String,
/// PostgreSQL database name
#[clap(long, env, default_value = "geneit")]
db_name: String,
}
lazy_static::lazy_static! {
@ -40,4 +48,12 @@ impl AppConfig {
pub fn get() -> &'static AppConfig {
&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
)
}
}

View File

@ -8,6 +8,11 @@ impl SizeConstraint {
pub fn new(min: usize, max: usize) -> Self {
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)]

View File

@ -1 +1,4 @@
//! # API controller
pub mod auth_controller;
pub mod config_controller;

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

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

View File

@ -1,4 +1,10 @@
pub mod app_config;
pub mod constants;
pub mod controllers;
pub mod services;
pub mod utils;
// Diesel specific
pub mod db_connection;
pub mod models;
pub mod schema;

View File

@ -1,6 +1,8 @@
use actix_remote_ip::RemoteIPConfig;
use actix_web::middleware::Logger;
use actix_web::{web, App, HttpServer};
use geneit_backend::app_config::AppConfig;
use geneit_backend::controllers::config_controller;
use geneit_backend::controllers::{auth_controller, config_controller};
#[actix_web::main]
async fn main() -> std::io::Result<()> {
@ -10,12 +12,21 @@ async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.wrap(Logger::default())
.app_data(web::Data::new(RemoteIPConfig {
proxy: AppConfig::get().proxy_ip.clone(),
}))
// Config controller
.route("/", web::get().to(config_controller::home))
.route(
"/config/static",
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())?
.run()

View 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,
}

View File

@ -0,0 +1,3 @@
//! # Backend services
pub mod users_service;

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

View File

@ -0,0 +1,3 @@
//! # App utilities
pub mod time_utils;

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