From f9e903da546819f1361bd2e5d8849302d91fd4d7 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Sun, 5 Jan 2025 00:21:01 -0500 Subject: [PATCH] Expand out the profile to start including a list of characters and games --- visions/server/src/core.rs | 1 + .../server/src/handlers/user_management.rs | 19 ++++++++++- visions/server/src/routes.rs | 33 ++++++++++++++++--- visions/server/src/types.rs | 1 + visions/ui/src/client.ts | 5 ++- visions/ui/src/components/Card/Card.css | 8 +++++ visions/ui/src/components/Card/Card.tsx | 16 +++++++++ .../components/GameOverview/GameOverview.css | 0 .../components/GameOverview/GameOverview.tsx | 12 +++++++ visions/ui/src/components/Profile/Profile.tsx | 29 +++++++++++++--- visions/ui/src/components/index.ts | 4 ++- visions/ui/src/design.css | 7 ---- .../providers/StateProvider/StateProvider.tsx | 9 ++++- 13 files changed, 124 insertions(+), 20 deletions(-) create mode 100644 visions/ui/src/components/Card/Card.css create mode 100644 visions/ui/src/components/Card/Card.tsx create mode 100644 visions/ui/src/components/GameOverview/GameOverview.css create mode 100644 visions/ui/src/components/GameOverview/GameOverview.tsx diff --git a/visions/server/src/core.rs b/visions/server/src/core.rs index f0628ad..055556e 100644 --- a/visions/server/src/core.rs +++ b/visions/server/src/core.rs @@ -136,6 +136,7 @@ impl Core { ok(Some(UserProfile { id: user.id, name: user.name, + password: user.password, games: user_games, is_admin: user.admin, })) diff --git a/visions/server/src/handlers/user_management.rs b/visions/server/src/handlers/user_management.rs index 680fce0..d770649 100644 --- a/visions/server/src/handlers/user_management.rs +++ b/visions/server/src/handlers/user_management.rs @@ -3,7 +3,7 @@ use axum::{ Json, }; use futures::Future; -use result_extended::{error, ok, return_error, ResultExt}; +use result_extended::{error, fatal, ok, return_error, ResultExt}; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -33,6 +33,12 @@ pub struct SetPasswordRequest { pub password_2: String, } +#[derive(Deserialize, Serialize)] +#[typeshare] +pub struct SetAdminPasswordRequest { + pub password: String, +} + async fn check_session( core: &Core, headers: HeaderMap, @@ -134,3 +140,14 @@ pub async fn set_password( } }).await } + +pub async fn set_admin_password( + core: Core, + req: String, +) -> ResultExt<(), AppError, FatalError> { + match return_error!(core.user(UserId::from("admin")).await) { + Some(admin) if admin.password.is_empty() => core.set_password(UserId::from("admin"), req).await, + Some(_) => error(AppError::PermissionDenied), + None => fatal(FatalError::DatabaseKeyMissing), + } +} diff --git a/visions/server/src/routes.rs b/visions/server/src/routes.rs index 7b23280..25c3a4c 100644 --- a/visions/server/src/routes.rs +++ b/visions/server/src/routes.rs @@ -1,6 +1,9 @@ use axum::{ extract::Path, - http::{header::{AUTHORIZATION, CONTENT_TYPE}, HeaderMap, Method}, + http::{ + header::{AUTHORIZATION, CONTENT_TYPE}, + HeaderMap, Method, + }, routing::{get, post, put}, Json, Router, }; @@ -10,8 +13,9 @@ use crate::{ core::Core, database::UserId, handlers::{ - check_password, create_game, create_user, get_user, healthcheck, set_password, - wrap_handler, AuthRequest, CreateGameRequest, CreateUserRequest, SetPasswordRequest, + check_password, create_game, create_user, get_user, healthcheck, set_admin_password, + set_password, wrap_handler, AuthRequest, CreateGameRequest, CreateUserRequest, + SetAdminPasswordRequest, SetPasswordRequest, }, }; @@ -21,7 +25,11 @@ pub fn routes(core: Core) -> Router { "/api/v1/health", get({ let core = core.clone(); - move || healthcheck(core) + move || async { + let result = healthcheck(core).await; + println!("status: {:?}", String::from_utf8(result.to_owned())); + result + } }) .layer( CorsLayer::new() @@ -29,6 +37,23 @@ pub fn routes(core: Core) -> Router { .allow_origin(Any), ), ) + .route( + "/api/v1/admin_password", + put({ + let core = core.clone(); + move |req: Json| { + let Json(req) = req; + println!("set admin password: {:?}", req); + wrap_handler(|| set_admin_password(core, req)) + } + }) + .layer( + CorsLayer::new() + .allow_methods([Method::PUT]) + .allow_headers([CONTENT_TYPE]) + .allow_origin(Any), + ), + ) .route( "/api/v1/auth", post({ diff --git a/visions/server/src/types.rs b/visions/server/src/types.rs index 2e892b2..4a3e8c0 100644 --- a/visions/server/src/types.rs +++ b/visions/server/src/types.rs @@ -138,6 +138,7 @@ pub enum Message { pub struct UserProfile { pub id: UserId, pub name: String, + pub password: String, pub games: Vec, pub is_admin: bool, } diff --git a/visions/ui/src/client.ts b/visions/ui/src/client.ts index af8815c..f2a30f9 100644 --- a/visions/ui/src/client.ts +++ b/visions/ui/src/client.ts @@ -96,7 +96,10 @@ export class Client { async health() { const url = new URL(this.base); url.pathname = `/api/v1/health`; - return fetch(url).then((response) => response.json()); + return fetch(url).then((response) => response.json()).then((response) => { + console.log("health response: ", response); + return response; + }); } } diff --git a/visions/ui/src/components/Card/Card.css b/visions/ui/src/components/Card/Card.css new file mode 100644 index 0000000..68a21d2 --- /dev/null +++ b/visions/ui/src/components/Card/Card.css @@ -0,0 +1,8 @@ +.card { + border: var(--border-standard); + border-radius: var(--border-radius-standard); + box-shadow: var(--border-shadow-shallow); + padding: var(--padding-m); +} + + diff --git a/visions/ui/src/components/Card/Card.tsx b/visions/ui/src/components/Card/Card.tsx new file mode 100644 index 0000000..798ca1b --- /dev/null +++ b/visions/ui/src/components/Card/Card.tsx @@ -0,0 +1,16 @@ +import { PropsWithChildren } from 'react'; +import './Card.css'; + +interface CardElementProps { + name?: string, +} + +export const CardElement = ({ name, children }: PropsWithChildren) => ( +
+ {name &&

{name}

} +
+ {children} +
+
+) + diff --git a/visions/ui/src/components/GameOverview/GameOverview.css b/visions/ui/src/components/GameOverview/GameOverview.css new file mode 100644 index 0000000..e69de29 diff --git a/visions/ui/src/components/GameOverview/GameOverview.tsx b/visions/ui/src/components/GameOverview/GameOverview.tsx new file mode 100644 index 0000000..976f2aa --- /dev/null +++ b/visions/ui/src/components/GameOverview/GameOverview.tsx @@ -0,0 +1,12 @@ +import { GameOverview } from "visions-types" +import { CardElement } from '../Card/Card'; + + +export const GameOverviewElement = ({ game_type, game_name, gm, players }: GameOverview) => { + return ( +

GM {gm}

+
    + {players.map((player) => player)} +
+
) +} diff --git a/visions/ui/src/components/Profile/Profile.tsx b/visions/ui/src/components/Profile/Profile.tsx index dde4545..edbe93b 100644 --- a/visions/ui/src/components/Profile/Profile.tsx +++ b/visions/ui/src/components/Profile/Profile.tsx @@ -1,12 +1,31 @@ import { UserProfile } from 'visions-types'; +import { CardElement } from '../Card/Card'; +import { GameOverviewElement } from '../GameOverview/GameOverview'; export const ProfileElement = ({ name, games, is_admin }: UserProfile) => { const adminNote = is_admin ?
Note: this user is an admin
: <>; - return ( -
-

{name}

-
Games: {games.map((game) => <>{game.game_name} ({game.game_type})).join(', ')}
+ return (
+ +
Games: {games.map((game) => { + return {game.game_name} ({game.game_type}); + }) }
{adminNote} -
) + + +
+ +
    +
  • Savanni
  • +
  • Shephard
  • +
  • Vakarian
  • +
  • vas Normandy
  • +
+
+ +
+ {games.map((game) => )} +
+
+
) } diff --git a/visions/ui/src/components/index.ts b/visions/ui/src/components/index.ts index 95a33e5..f777d4c 100644 --- a/visions/ui/src/components/index.ts +++ b/visions/ui/src/components/index.ts @@ -1,6 +1,8 @@ +import { CardElement } from './Card/Card' +import { GameOverviewElement } from './GameOverview/GameOverview' import { ProfileElement } from './Profile/Profile' import { SimpleGuage } from './Guages/SimpleGuage' import { ThumbnailElement } from './Thumbnail/Thumbnail' import { TabletopElement } from './Tabletop/Tabletop' -export { ProfileElement, ThumbnailElement, TabletopElement, SimpleGuage } +export { CardElement, ProfileElement, ThumbnailElement, TabletopElement, SimpleGuage } diff --git a/visions/ui/src/design.css b/visions/ui/src/design.css index 7e227a4..9c71d9f 100644 --- a/visions/ui/src/design.css +++ b/visions/ui/src/design.css @@ -6,10 +6,3 @@ --margin-s: 4px; } -.card { - border: var(--border-standard); - border-radius: var(--border-radius-standard); - box-shadow: var(--border-shadow-shallow); - padding: var(--padding-m); -} - diff --git a/visions/ui/src/providers/StateProvider/StateProvider.tsx b/visions/ui/src/providers/StateProvider/StateProvider.tsx index 6f928fa..82669dc 100644 --- a/visions/ui/src/providers/StateProvider/StateProvider.tsx +++ b/visions/ui/src/providers/StateProvider/StateProvider.tsx @@ -47,6 +47,8 @@ const stateReducer = (state: AppState, action: Action): AppState => { } } +export const authState = (state: AppState): AuthState => state.auth + export const getSessionId = (state: AppState): SessionId | undefined => { switch (state.auth.type) { case "NoAdmin": return undefined @@ -75,13 +77,18 @@ class StateManager { if (!admin_enabled) { this.dispatch({ type: "SetAuthState", content: { type: "NoAdmin" } }); } + + return admin_enabled; } async setAdminPassword(password: string) { if (!this.client || !this.dispatch) return; await this.client.setAdminPassword(password); - await this.status(); + let admin_enabled = await this.status(); + if (admin_enabled) { + this.dispatch({ type: "SetAuthState", content: { type: "Unauthed" } }); + } } async auth(username: string, password: string) {