feat(admin): create and list users commands

This commit is contained in:
Matthieu Bessat 2024-12-03 13:20:33 +01:00
parent 8d20cab18f
commit a0de3b287b
19 changed files with 314 additions and 30 deletions

View file

@ -5,9 +5,10 @@ edition = "2021"
[dependencies]
utils = { path = "../utils" }
anyhow = { workspace = true }
thiserror = { workspace = true }
log = { workspace = true }
env_logger = { workspace = true }
anyhow = { workspace = true }
fully_pub = { workspace = true }
strum = { workspace = true }
strum_macros = { workspace = true }

View file

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

View file

@ -0,0 +1,44 @@
use crate::{context::KernelContext, models::user::User};
use anyhow::{Context, Result};
use chrono::SecondsFormat;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum CreateUserErr {
#[error("Handle or email for user is already used.")]
HandleOrEmailNotUnique,
#[error("Database error.")]
DatabaseErr(String)
}
pub async fn create_user(ctx: KernelContext, user: User) -> Result<(), CreateUserErr> {
let res = sqlx::query("
INSERT INTO users
(id, handle, email, status, roles, password_hash, reset_password_token, created_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
")
.bind(user.id)
.bind(user.handle)
.bind(user.email)
.bind(user.status.to_string())
.bind(user.roles)
.bind(user.password_hash)
.bind(user.reset_password_token)
.bind(user.created_at.to_rfc3339_opts(SecondsFormat::Millis, true))
.execute(&ctx.storage.0)
.await;
match res {
Err(err) => {
let db_err = &err.as_database_error().unwrap();
if db_err.code().unwrap() == "2067" {
Err(CreateUserErr::HandleOrEmailNotUnique)
} else {
dbg!(&err);
Err(CreateUserErr::DatabaseErr(db_err.to_string()))
}
}
Ok(_) => Ok(())
}
}

View file

@ -29,7 +29,15 @@ struct AppSecrets {
jwt_secret: String
}
pub async fn get_kernel_context(start_config: StartKernelConfig) -> Result<(Config, AppSecrets, Storage)> {
#[derive(Debug, Clone)]
#[fully_pub]
struct KernelContext {
config: Config,
secrets: AppSecrets,
storage: Storage
}
pub async fn get_kernel_context(start_config: StartKernelConfig) -> Result<KernelContext> {
env_logger::init();
let _ = dotenvy::dotenv();
@ -47,5 +55,9 @@ pub async fn get_kernel_context(start_config: StartKernelConfig) -> Result<(Conf
jwt_secret: env::var("APP_JWT_SECRET").context("Expecting APP_JWT_SECRET env var.")?
};
Ok((config, secrets, storage))
Ok(KernelContext {
config,
secrets,
storage
})
}

View file

@ -2,6 +2,8 @@ use fully_pub::fully_pub;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use sqlx::types::Json;
use utils::get_random_human_token;
use uuid::Uuid;
#[derive(sqlx::Type, Clone, Debug, Serialize, Deserialize, PartialEq)]
#[derive(strum_macros::Display)]
@ -30,3 +32,30 @@ struct User {
last_login_at: Option<DateTime<Utc>>,
created_at: DateTime<Utc>
}
impl User {
pub fn new(
handle: String
) -> User {
User {
id: Uuid::new_v4().to_string(),
handle,
full_name: None,
email: None,
website: None,
picture: None,
password_hash: None,
status: UserStatus::Disabled,
roles: Json(Vec::new()),
reset_password_token: None,
last_login_at: None,
created_at: Utc::now()
}
}
pub fn invite(self: &mut Self) {
self.reset_password_token = Some(get_random_human_token());
self.status = UserStatus::Invited;
}
}

View file

@ -5,10 +5,17 @@ use crate::models::user::User;
use super::storage::Storage;
use anyhow::{Result, Context};
async fn get_user_by_id(storage: &Storage, user_id: &str) -> Result<User> {
pub async fn get_user_by_id(storage: &Storage, user_id: &str) -> Result<User> {
sqlx::query_as::<_, User>("SELECT * FROM users WHERE id = $1")
.bind(user_id)
.fetch_one(&storage.0)
.await
.context("To get user from claim")
.context("To get user by id.")
}
pub async fn get_users(storage: &Storage) -> Result<Vec<User>> {
sqlx::query_as::<_, User>("SELECT * FROM users")
.fetch_all(&storage.0)
.await
.context("To get users.")
}