Add database to server endpoints, move modals, add forget pw to db
All checks were successful
Build Crate / build (push) Successful in 1m45s
All checks were successful
Build Crate / build (push) Successful in 1m45s
This commit is contained in:
@@ -9,6 +9,7 @@ pub struct Migration;
|
|||||||
impl MigrationTrait for Migration {
|
impl MigrationTrait for Migration {
|
||||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
// User table
|
// User table
|
||||||
|
// @todo verify all data saved is length-checked
|
||||||
manager
|
manager
|
||||||
.create_table(
|
.create_table(
|
||||||
Table::create()
|
Table::create()
|
||||||
@@ -22,6 +23,7 @@ impl MigrationTrait for Migration {
|
|||||||
.col(boolean(User::IsAdmin))
|
.col(boolean(User::IsAdmin))
|
||||||
.col(string_null(User::Email))
|
.col(string_null(User::Email))
|
||||||
.col(string_null(User::Avatar))
|
.col(string_null(User::Avatar))
|
||||||
|
.col(string_null(User::ForgotPasswordRequest))
|
||||||
.to_owned(),
|
.to_owned(),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@@ -46,4 +48,5 @@ pub enum User {
|
|||||||
IsAdmin,
|
IsAdmin,
|
||||||
Email,
|
Email,
|
||||||
Avatar,
|
Avatar,
|
||||||
|
ForgotPasswordRequest,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,6 +49,8 @@ fn forgot_password_form_capsule<G: Html>(
|
|||||||
{
|
{
|
||||||
spawn_local_scoped(cx, async move {
|
spawn_local_scoped(cx, async move {
|
||||||
let global_state = Reactor::<G>::from_cx(cx).get_global_state::<AppStateRx>(cx);
|
let global_state = Reactor::<G>::from_cx(cx).get_global_state::<AppStateRx>(cx);
|
||||||
|
|
||||||
|
// Close modal
|
||||||
state.reset();
|
state.reset();
|
||||||
global_state
|
global_state
|
||||||
.modals_open
|
.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") {
|
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 {
|
div {
|
||||||
label (class="text-sm font-medium text-gray-900 block mb-2 dark:text-gray-300") {"Username"}
|
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") {}
|
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 {
|
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"){}
|
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"}
|
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"}
|
||||||
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"}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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| {
|
let handle_log_in = move |_event: Event| {
|
||||||
#[cfg(client)]
|
#[cfg(client)]
|
||||||
{
|
{
|
||||||
@@ -155,11 +168,11 @@ fn login_form_capsule<G: Html>(
|
|||||||
}},
|
}},
|
||||||
false => view!{cx, },
|
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"}
|
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"){
|
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"}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
|
|||||||
@@ -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, }
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
use crate::{
|
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},
|
components::header::{Header, HeaderProps},
|
||||||
global_state::AppStateRx,
|
global_state::AppStateRx,
|
||||||
state_enums::{GameState, LoginState},
|
state_enums::{GameState, LoginState, OpenState},
|
||||||
};
|
};
|
||||||
use perseus::prelude::*;
|
use perseus::prelude::*;
|
||||||
use sycamore::prelude::*;
|
use sycamore::prelude::*;
|
||||||
@@ -28,8 +31,6 @@ pub fn Layout<'a, G: Html>(
|
|||||||
) -> View<G> {
|
) -> View<G> {
|
||||||
let children = children.call(cx);
|
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);
|
let global_state = Reactor::<G>::from_cx(cx).get_global_state::<AppStateRx>(cx);
|
||||||
|
|
||||||
// Check if the client is authenticated or not
|
// Check if the client is authenticated or not
|
||||||
@@ -40,6 +41,36 @@ pub fn Layout<'a, G: Html>(
|
|||||||
// Main page header, including login functionality
|
// Main page header, including login functionality
|
||||||
Header(game = game, title = title)
|
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") {
|
main(style = "my-8") {
|
||||||
// Body header
|
// Body header
|
||||||
div {
|
div {
|
||||||
@@ -59,7 +90,7 @@ pub fn Layout<'a, G: Html>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Actual body
|
// Content body
|
||||||
div(class = "container mx-auto px-6") {
|
div(class = "container mx-auto px-6") {
|
||||||
div(class = "md:flex mt-8 md:-mx-4") {
|
div(class = "md:flex mt-8 md:-mx-4") {
|
||||||
div(class = "rounded-md overflow-hidden bg-cover bg-center") {
|
div(class = "rounded-md overflow-hidden bg-cover bg-center") {
|
||||||
|
|||||||
@@ -1,2 +1,4 @@
|
|||||||
|
pub const REGISTER: &str = "/api/login";
|
||||||
pub const LOGIN: &str = "/api/login";
|
pub const LOGIN: &str = "/api/login";
|
||||||
pub const LOGIN_TEST: &str = "/api/login-test";
|
pub const LOGIN_TEST: &str = "/api/login-test";
|
||||||
|
pub const FORGOT_PASSWORD: &str = "/api/forgot-password";
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ pub struct Model {
|
|||||||
pub is_admin: bool,
|
pub is_admin: bool,
|
||||||
pub email: Option<String>,
|
pub email: Option<String>,
|
||||||
pub avatar: Option<String>,
|
pub avatar: Option<String>,
|
||||||
|
pub forgot_password_request: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ impl AuthDataRx {
|
|||||||
#[rx(alias = "ModalOpenDataRx")]
|
#[rx(alias = "ModalOpenDataRx")]
|
||||||
pub struct ModalOpenData {
|
pub struct ModalOpenData {
|
||||||
pub login: OpenState,
|
pub login: OpenState,
|
||||||
|
pub register: OpenState,
|
||||||
pub forgot_password: OpenState,
|
pub forgot_password: OpenState,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,6 +100,7 @@ pub async fn get_build_state() -> AppState {
|
|||||||
},
|
},
|
||||||
modals_open: ModalOpenData {
|
modals_open: ModalOpenData {
|
||||||
login: OpenState::Closed,
|
login: OpenState::Closed,
|
||||||
|
register: OpenState::Closed,
|
||||||
forgot_password: OpenState::Closed,
|
forgot_password: OpenState::Closed,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
25
src/main.rs
25
src/main.rs
@@ -23,9 +23,10 @@ cfg_if::cfg_if! {
|
|||||||
stores::MutableStore,
|
stores::MutableStore,
|
||||||
turbine::Turbine,
|
turbine::Turbine,
|
||||||
};
|
};
|
||||||
|
use crate::server::routes::get_api_router;
|
||||||
|
use crate::server::server_state::ServerState;
|
||||||
use futures::executor::block_on;
|
use futures::executor::block_on;
|
||||||
use sea_orm::{Database};
|
use sea_orm::Database;
|
||||||
use crate::server::routes::register_routes;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,16 +39,24 @@ pub async fn dflt_server<M: MutableStore + 'static, T: TranslationsManager + 'st
|
|||||||
let addr: SocketAddr = format!("{}:{}", host, port)
|
let addr: SocketAddr = format!("{}:{}", host, port)
|
||||||
.parse()
|
.parse()
|
||||||
.expect("Invalid address provided to bind to.");
|
.expect("Invalid address provided to bind to.");
|
||||||
let mut app = perseus_axum::get_router(turbine, opts).await;
|
let app = perseus_axum::get_router(turbine, opts).await;
|
||||||
|
|
||||||
app = register_routes(app);
|
|
||||||
|
|
||||||
// TODO -> Update to use environment variable
|
// TODO -> Update to use environment variable
|
||||||
if let Err(err) = block_on(Database::connect(
|
// TODO -> error handling
|
||||||
"postgres://elo:elo@localhost:5432/elo_app",
|
// 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);
|
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)
|
axum::Server::bind(&addr)
|
||||||
.serve(app.into_make_service())
|
.serve(app.into_make_service())
|
||||||
|
|||||||
@@ -31,3 +31,9 @@ pub struct WebAuthInfo {
|
|||||||
pub username: String,
|
pub username: String,
|
||||||
pub remember_me: bool,
|
pub remember_me: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
|
pub struct ForgotPasswordRequest {
|
||||||
|
pub username: String,
|
||||||
|
pub contact_info: String,
|
||||||
|
}
|
||||||
|
|||||||
13
src/server/auth/forgot_password.rs
Normal file
13
src/server/auth/forgot_password.rs
Normal 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
|
||||||
|
}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
use crate::models::auth::{Claims, LoginInfo, LoginResponse};
|
use crate::{
|
||||||
|
models::auth::{Claims, LoginInfo, LoginResponse},
|
||||||
|
server::server_state::ServerState,
|
||||||
|
};
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::Json,
|
extract::{Json, State},
|
||||||
http::{HeaderMap, StatusCode},
|
http::{HeaderMap, StatusCode},
|
||||||
};
|
};
|
||||||
use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation};
|
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(
|
pub async fn post_login_user(
|
||||||
|
State(state): State<ServerState>,
|
||||||
Json(login_info): Json<LoginInfo>,
|
Json(login_info): Json<LoginInfo>,
|
||||||
) -> Result<Json<LoginResponse>, StatusCode> {
|
) -> Result<Json<LoginResponse>, StatusCode> {
|
||||||
let user_authenticated = is_valid_user(&login_info.username, &login_info.password);
|
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 Some(auth_header) = header_map.get("Authorization") {
|
||||||
if let Ok(auth_header_str) = auth_header.to_str() {
|
if let Ok(auth_header_str) = auth_header.to_str() {
|
||||||
if auth_header_str.starts_with("Bearer ") {
|
if auth_header_str.starts_with("Bearer ") {
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
|
pub mod forgot_password;
|
||||||
pub mod login;
|
pub mod login;
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
pub mod auth;
|
pub mod auth;
|
||||||
pub mod routes;
|
pub mod routes;
|
||||||
|
pub mod server_state;
|
||||||
|
|||||||
@@ -1,11 +1,21 @@
|
|||||||
// (Server only) Routes
|
// (Server only) Routes
|
||||||
use crate::endpoints::{LOGIN, LOGIN_TEST};
|
use crate::endpoints::{FORGOT_PASSWORD, LOGIN, LOGIN_TEST};
|
||||||
use axum::routing::{post, Router};
|
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 {
|
pub fn get_api_router(state: ServerState) -> Router {
|
||||||
let app = app.route(LOGIN, post(post_login_user));
|
Router::new()
|
||||||
let app = app.route(LOGIN_TEST, post(post_test_login));
|
.route(LOGIN, post(post_login_user))
|
||||||
app
|
.route(LOGIN_TEST, post(post_test_login))
|
||||||
|
.route(FORGOT_PASSWORD, post(post_forgot_password))
|
||||||
|
.with_state(state)
|
||||||
}
|
}
|
||||||
|
|||||||
6
src/server/server_state.rs
Normal file
6
src/server/server_state.rs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
use sea_orm::DatabaseConnection;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ServerState {
|
||||||
|
pub db_conn: DatabaseConnection,
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user