Add database to server endpoints, move modals, add forget pw to db
All checks were successful
Build Crate / build (push) Successful in 1m45s

This commit is contained in:
2024-08-27 02:12:57 -04:00
parent 242f9b1218
commit f4f491085d
17 changed files with 137 additions and 61 deletions

View File

@@ -9,6 +9,7 @@ pub struct Migration;
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
// User table
// @todo verify all data saved is length-checked
manager
.create_table(
Table::create()
@@ -22,6 +23,7 @@ impl MigrationTrait for Migration {
.col(boolean(User::IsAdmin))
.col(string_null(User::Email))
.col(string_null(User::Avatar))
.col(string_null(User::ForgotPasswordRequest))
.to_owned(),
)
.await
@@ -46,4 +48,5 @@ pub enum User {
IsAdmin,
Email,
Avatar,
ForgotPasswordRequest,
}

View File

@@ -49,6 +49,8 @@ fn forgot_password_form_capsule<G: Html>(
{
spawn_local_scoped(cx, async move {
let global_state = Reactor::<G>::from_cx(cx).get_global_state::<AppStateRx>(cx);
// Close modal
state.reset();
global_state
.modals_open
@@ -83,20 +85,17 @@ fn forgot_password_form_capsule<G: Html>(
}
}
div (class="space-y-6 px-6 lg:px-8 pb-4 sm:pb-6 xl:pb-8") {
h3 (class="text-xl font-medium text-gray-900 dark:text-white"){"Sign in to our platform"}
h3 (class="text-xl font-medium text-gray-900 dark:text-white"){"Forgot Password"}
div {
label (class="text-sm font-medium text-gray-900 block mb-2 dark:text-gray-300") {"Username"}
input (bind:value = state.username, class="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white") {}
}
div {
label (class="text-sm font-medium text-gray-900 block mb-2 dark:text-gray-300"){"How to contact you with new password"}
label (class="text-sm font-medium text-gray-900 block mb-2 dark:text-gray-300"){"Contact Info"}
input (bind:value = state.how_to_reach, class="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white"){}
}
button (on:click = handle_submit, class="w-full text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"){"Log in"}
div (class="text-sm font-medium text-gray-500 dark:text-gray-300"){
a (class="text-blue-700 hover:underline dark:text-blue-500"){"Submit"}
}
button (on:click = handle_submit, class="w-full text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"){"Submit"}
}
}
}

View File

@@ -76,6 +76,19 @@ fn login_form_capsule<G: Html>(
}
};
let handle_register = move |_event: Event| {
#[cfg(client)]
{
spawn_local_scoped(cx, async move {
let global_state = Reactor::<G>::from_cx(cx).get_global_state::<AppStateRx>(cx);
global_state.modals_open.register.set(OpenState::Open);
// Close modal
state.reset();
global_state.modals_open.login.set(OpenState::Closed);
});
}
};
let handle_log_in = move |_event: Event| {
#[cfg(client)]
{
@@ -155,11 +168,11 @@ fn login_form_capsule<G: Html>(
}},
false => view!{cx, },
})
a (on:click = handle_forgot_password, class="text-sm text-blue-700 hover:underline dark:text-blue-500"){"Lost Password?"}
button (on:click = handle_forgot_password, class="text-sm text-blue-700 hover:underline dark:text-blue-500"){"Lost Password?"}
}
button (on:click = handle_log_in, class="w-full text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"){"Log in"}
div (class="text-sm font-medium text-gray-500 dark:text-gray-300"){
a (class="text-blue-700 hover:underline dark:text-blue-500"){"Create account"}
button (on:click = handle_register, class="text-blue-700 hover:underline dark:text-blue-500"){"Create account"}
}
}
}

View File

@@ -0,0 +1 @@

View File

@@ -92,34 +92,5 @@ pub fn Header<'a, G: Html>(cx: Scope<'a>, HeaderProps { game, title }: HeaderPro
}
}
}
section(class = "flex-2") {
(match *global_state.modals_open.login.get() {
OpenState::Open => {
view! { cx,
(LOGIN_FORM.widget(cx, "",
LoginFormProps{
remember_me: true,
}
))
}
}
OpenState::Closed => {
view!{ cx, }
}
})
(match *global_state.modals_open.forgot_password.get() {
OpenState::Open => {
view! { cx,
(FORGOT_PASSWORD_FORM.widget(cx, "",
ForgotPasswordFormProps{}
))
}
}
OpenState::Closed => {
view!{ cx, }
}
})
}
}
}

View File

@@ -1,8 +1,11 @@
use crate::{
capsules::login_form::{LoginFormProps, LOGIN_FORM},
capsules::{
forgot_password_form::{ForgotPasswordFormProps, FORGOT_PASSWORD_FORM},
login_form::{LoginFormProps, LOGIN_FORM},
},
components::header::{Header, HeaderProps},
global_state::AppStateRx,
state_enums::{GameState, LoginState},
state_enums::{GameState, LoginState, OpenState},
};
use perseus::prelude::*;
use sycamore::prelude::*;
@@ -28,8 +31,6 @@ pub fn Layout<'a, G: Html>(
) -> View<G> {
let children = children.call(cx);
// Get global state to get authentication info
#[cfg(client)]
let global_state = Reactor::<G>::from_cx(cx).get_global_state::<AppStateRx>(cx);
// Check if the client is authenticated or not
@@ -40,6 +41,36 @@ pub fn Layout<'a, G: Html>(
// Main page header, including login functionality
Header(game = game, title = title)
// Modals
section(class = "flex-2") {
(match *global_state.modals_open.login.get() {
OpenState::Open => {
view! { cx,
(LOGIN_FORM.widget(cx, "",
LoginFormProps{
remember_me: true,
}
))
}
}
OpenState::Closed => {
view!{ cx, }
}
})
(match *global_state.modals_open.forgot_password.get() {
OpenState::Open => {
view! { cx,
(FORGOT_PASSWORD_FORM.widget(cx, "",
ForgotPasswordFormProps{}
))
}
}
OpenState::Closed => {
view!{ cx, }
}
})
}
main(style = "my-8") {
// Body header
div {
@@ -59,7 +90,7 @@ pub fn Layout<'a, G: Html>(
}
}
}
// Actual body
// Content body
div(class = "container mx-auto px-6") {
div(class = "md:flex mt-8 md:-mx-4") {
div(class = "rounded-md overflow-hidden bg-cover bg-center") {

View File

@@ -1,2 +1,4 @@
pub const REGISTER: &str = "/api/login";
pub const LOGIN: &str = "/api/login";
pub const LOGIN_TEST: &str = "/api/login-test";
pub const FORGOT_PASSWORD: &str = "/api/forgot-password";

View File

@@ -16,6 +16,7 @@ pub struct Model {
pub is_admin: bool,
pub email: Option<String>,
pub avatar: Option<String>,
pub forgot_password_request: Option<String>,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]

View File

@@ -81,6 +81,7 @@ impl AuthDataRx {
#[rx(alias = "ModalOpenDataRx")]
pub struct ModalOpenData {
pub login: OpenState,
pub register: OpenState,
pub forgot_password: OpenState,
}
@@ -99,6 +100,7 @@ pub async fn get_build_state() -> AppState {
},
modals_open: ModalOpenData {
login: OpenState::Closed,
register: OpenState::Closed,
forgot_password: OpenState::Closed,
},
}

View File

@@ -23,9 +23,10 @@ cfg_if::cfg_if! {
stores::MutableStore,
turbine::Turbine,
};
use crate::server::routes::get_api_router;
use crate::server::server_state::ServerState;
use futures::executor::block_on;
use sea_orm::{Database};
use crate::server::routes::register_routes;
use sea_orm::Database;
}
}
@@ -38,16 +39,24 @@ pub async fn dflt_server<M: MutableStore + 'static, T: TranslationsManager + 'st
let addr: SocketAddr = format!("{}:{}", host, port)
.parse()
.expect("Invalid address provided to bind to.");
let mut app = perseus_axum::get_router(turbine, opts).await;
app = register_routes(app);
let app = perseus_axum::get_router(turbine, opts).await;
// TODO -> Update to use environment variable
if let Err(err) = block_on(Database::connect(
"postgres://elo:elo@localhost:5432/elo_app",
)) {
// TODO -> error handling
// Includes making database connection
let db_conn = Database::connect("postgres://elo:elo@localhost:5432/elo_app");
let db_conn = block_on(db_conn);
let db_conn = match db_conn {
Ok(db_conn) => db_conn,
Err(err) => {
panic!("{}", err);
}
};
let state = ServerState { db_conn };
// Get server routes
let api_router = get_api_router(state);
let app = app.merge(api_router);
axum::Server::bind(&addr)
.serve(app.into_make_service())

View File

@@ -31,3 +31,9 @@ pub struct WebAuthInfo {
pub username: String,
pub remember_me: bool,
}
#[derive(Serialize, Deserialize, Clone)]
pub struct ForgotPasswordRequest {
pub username: String,
pub contact_info: String,
}

View File

@@ -0,0 +1,13 @@
use crate::{models::auth::ForgotPasswordRequest, server::server_state::ServerState};
use axum::{
extract::{Json, State},
http::{HeaderMap, StatusCode},
};
use sea_orm::DatabaseConnection;
pub async fn post_forgot_password(
State(state): State<ServerState>,
Json(password_request): Json<ForgotPasswordRequest>,
) -> StatusCode {
StatusCode::OK
}

View File

@@ -1,6 +1,9 @@
use crate::models::auth::{Claims, LoginInfo, LoginResponse};
use crate::{
models::auth::{Claims, LoginInfo, LoginResponse},
server::server_state::ServerState,
};
use axum::{
extract::Json,
extract::{Json, State},
http::{HeaderMap, StatusCode},
};
use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation};
@@ -10,6 +13,7 @@ pub fn is_valid_user(username: &str, password: &str) -> bool {
}
pub async fn post_login_user(
State(state): State<ServerState>,
Json(login_info): Json<LoginInfo>,
) -> Result<Json<LoginResponse>, StatusCode> {
let user_authenticated = is_valid_user(&login_info.username, &login_info.password);
@@ -42,7 +46,10 @@ pub async fn post_login_user(
}
}
pub async fn post_test_login(header_map: HeaderMap) -> Result<Json<String>, StatusCode> {
pub async fn post_test_login(
State(state): State<ServerState>,
header_map: HeaderMap,
) -> Result<Json<String>, StatusCode> {
if let Some(auth_header) = header_map.get("Authorization") {
if let Ok(auth_header_str) = auth_header.to_str() {
if auth_header_str.starts_with("Bearer ") {

View File

@@ -1 +1,2 @@
pub mod forgot_password;
pub mod login;

View File

@@ -1,2 +1,3 @@
pub mod auth;
pub mod routes;
pub mod server_state;

View File

@@ -1,11 +1,21 @@
// (Server only) Routes
use crate::endpoints::{LOGIN, LOGIN_TEST};
use crate::endpoints::{FORGOT_PASSWORD, LOGIN, LOGIN_TEST};
use axum::routing::{post, Router};
use futures::executor::block_on;
use sea_orm::Database;
use super::auth::login::{post_login_user, post_test_login};
use super::{
auth::{
forgot_password::post_forgot_password,
login::{post_login_user, post_test_login},
},
server_state::ServerState,
};
pub fn register_routes(app: Router) -> Router {
let app = app.route(LOGIN, post(post_login_user));
let app = app.route(LOGIN_TEST, post(post_test_login));
app
pub fn get_api_router(state: ServerState) -> Router {
Router::new()
.route(LOGIN, post(post_login_user))
.route(LOGIN_TEST, post(post_test_login))
.route(FORGOT_PASSWORD, post(post_forgot_password))
.with_state(state)
}

View File

@@ -0,0 +1,6 @@
use sea_orm::DatabaseConnection;
#[derive(Clone)]
pub struct ServerState {
pub db_conn: DatabaseConnection,
}