Expand out the profile to start including a list of characters and games
This commit is contained in:
parent
a2cdaef689
commit
f9e903da54
@ -136,6 +136,7 @@ impl Core {
|
|||||||
ok(Some(UserProfile {
|
ok(Some(UserProfile {
|
||||||
id: user.id,
|
id: user.id,
|
||||||
name: user.name,
|
name: user.name,
|
||||||
|
password: user.password,
|
||||||
games: user_games,
|
games: user_games,
|
||||||
is_admin: user.admin,
|
is_admin: user.admin,
|
||||||
}))
|
}))
|
||||||
|
@ -3,7 +3,7 @@ use axum::{
|
|||||||
Json,
|
Json,
|
||||||
};
|
};
|
||||||
use futures::Future;
|
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 serde::{Deserialize, Serialize};
|
||||||
use typeshare::typeshare;
|
use typeshare::typeshare;
|
||||||
|
|
||||||
@ -33,6 +33,12 @@ pub struct SetPasswordRequest {
|
|||||||
pub password_2: String,
|
pub password_2: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize)]
|
||||||
|
#[typeshare]
|
||||||
|
pub struct SetAdminPasswordRequest {
|
||||||
|
pub password: String,
|
||||||
|
}
|
||||||
|
|
||||||
async fn check_session(
|
async fn check_session(
|
||||||
core: &Core,
|
core: &Core,
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
@ -134,3 +140,14 @@ pub async fn set_password(
|
|||||||
}
|
}
|
||||||
}).await
|
}).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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
use axum::{
|
use axum::{
|
||||||
extract::Path,
|
extract::Path,
|
||||||
http::{header::{AUTHORIZATION, CONTENT_TYPE}, HeaderMap, Method},
|
http::{
|
||||||
|
header::{AUTHORIZATION, CONTENT_TYPE},
|
||||||
|
HeaderMap, Method,
|
||||||
|
},
|
||||||
routing::{get, post, put},
|
routing::{get, post, put},
|
||||||
Json, Router,
|
Json, Router,
|
||||||
};
|
};
|
||||||
@ -10,8 +13,9 @@ use crate::{
|
|||||||
core::Core,
|
core::Core,
|
||||||
database::UserId,
|
database::UserId,
|
||||||
handlers::{
|
handlers::{
|
||||||
check_password, create_game, create_user, get_user, healthcheck, set_password,
|
check_password, create_game, create_user, get_user, healthcheck, set_admin_password,
|
||||||
wrap_handler, AuthRequest, CreateGameRequest, CreateUserRequest, SetPasswordRequest,
|
set_password, wrap_handler, AuthRequest, CreateGameRequest, CreateUserRequest,
|
||||||
|
SetAdminPasswordRequest, SetPasswordRequest,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -21,7 +25,11 @@ pub fn routes(core: Core) -> Router {
|
|||||||
"/api/v1/health",
|
"/api/v1/health",
|
||||||
get({
|
get({
|
||||||
let core = core.clone();
|
let core = core.clone();
|
||||||
move || healthcheck(core)
|
move || async {
|
||||||
|
let result = healthcheck(core).await;
|
||||||
|
println!("status: {:?}", String::from_utf8(result.to_owned()));
|
||||||
|
result
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.layer(
|
.layer(
|
||||||
CorsLayer::new()
|
CorsLayer::new()
|
||||||
@ -29,6 +37,23 @@ pub fn routes(core: Core) -> Router {
|
|||||||
.allow_origin(Any),
|
.allow_origin(Any),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
.route(
|
||||||
|
"/api/v1/admin_password",
|
||||||
|
put({
|
||||||
|
let core = core.clone();
|
||||||
|
move |req: Json<String>| {
|
||||||
|
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(
|
.route(
|
||||||
"/api/v1/auth",
|
"/api/v1/auth",
|
||||||
post({
|
post({
|
||||||
|
@ -138,6 +138,7 @@ pub enum Message {
|
|||||||
pub struct UserProfile {
|
pub struct UserProfile {
|
||||||
pub id: UserId,
|
pub id: UserId,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
pub password: String,
|
||||||
pub games: Vec<GameOverview>,
|
pub games: Vec<GameOverview>,
|
||||||
pub is_admin: bool,
|
pub is_admin: bool,
|
||||||
}
|
}
|
||||||
|
@ -96,7 +96,10 @@ export class Client {
|
|||||||
async health() {
|
async health() {
|
||||||
const url = new URL(this.base);
|
const url = new URL(this.base);
|
||||||
url.pathname = `/api/v1/health`;
|
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;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
8
visions/ui/src/components/Card/Card.css
Normal file
8
visions/ui/src/components/Card/Card.css
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
16
visions/ui/src/components/Card/Card.tsx
Normal file
16
visions/ui/src/components/Card/Card.tsx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { PropsWithChildren } from 'react';
|
||||||
|
import './Card.css';
|
||||||
|
|
||||||
|
interface CardElementProps {
|
||||||
|
name?: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CardElement = ({ name, children }: PropsWithChildren<CardElementProps>) => (
|
||||||
|
<div className="card">
|
||||||
|
{name && <h1 className="card__title">{name}</h1> }
|
||||||
|
<div className="card__body">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
12
visions/ui/src/components/GameOverview/GameOverview.tsx
Normal file
12
visions/ui/src/components/GameOverview/GameOverview.tsx
Normal file
@ -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 (<CardElement name={game_name}>
|
||||||
|
<p><i>GM</i> {gm}</p>
|
||||||
|
<ul>
|
||||||
|
{players.map((player) => player)}
|
||||||
|
</ul>
|
||||||
|
</CardElement>)
|
||||||
|
}
|
@ -1,12 +1,31 @@
|
|||||||
import { UserProfile } from 'visions-types';
|
import { UserProfile } from 'visions-types';
|
||||||
|
import { CardElement } from '../Card/Card';
|
||||||
|
import { GameOverviewElement } from '../GameOverview/GameOverview';
|
||||||
|
|
||||||
export const ProfileElement = ({ name, games, is_admin }: UserProfile) => {
|
export const ProfileElement = ({ name, games, is_admin }: UserProfile) => {
|
||||||
const adminNote = is_admin ? <div> <i>Note: this user is an admin</i> </div> : <></>;
|
const adminNote = is_admin ? <div> <i>Note: this user is an admin</i> </div> : <></>;
|
||||||
|
|
||||||
return (
|
return (<div>
|
||||||
<div className="card">
|
<CardElement name={name}>
|
||||||
<h1>{name}</h1>
|
<div>Games: {games.map((game) => {
|
||||||
<div>Games: {games.map((game) => <>{game.game_name} ({game.game_type})</>).join(', ')}</div>
|
return <span key={game.id}>{game.game_name} ({game.game_type})</span>;
|
||||||
|
}) }</div>
|
||||||
{adminNote}
|
{adminNote}
|
||||||
</div>)
|
</CardElement>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<CardElement>
|
||||||
|
<ul>
|
||||||
|
<li> Savanni </li>
|
||||||
|
<li> Shephard </li>
|
||||||
|
<li> Vakarian </li>
|
||||||
|
<li> vas Normandy </li>
|
||||||
|
</ul>
|
||||||
|
</CardElement>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{games.map((game) => <GameOverviewElement {...game} />)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>)
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
import { CardElement } from './Card/Card'
|
||||||
|
import { GameOverviewElement } from './GameOverview/GameOverview'
|
||||||
import { ProfileElement } from './Profile/Profile'
|
import { ProfileElement } from './Profile/Profile'
|
||||||
import { SimpleGuage } from './Guages/SimpleGuage'
|
import { SimpleGuage } from './Guages/SimpleGuage'
|
||||||
import { ThumbnailElement } from './Thumbnail/Thumbnail'
|
import { ThumbnailElement } from './Thumbnail/Thumbnail'
|
||||||
import { TabletopElement } from './Tabletop/Tabletop'
|
import { TabletopElement } from './Tabletop/Tabletop'
|
||||||
|
|
||||||
export { ProfileElement, ThumbnailElement, TabletopElement, SimpleGuage }
|
export { CardElement, ProfileElement, ThumbnailElement, TabletopElement, SimpleGuage }
|
||||||
|
@ -6,10 +6,3 @@
|
|||||||
--margin-s: 4px;
|
--margin-s: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card {
|
|
||||||
border: var(--border-standard);
|
|
||||||
border-radius: var(--border-radius-standard);
|
|
||||||
box-shadow: var(--border-shadow-shallow);
|
|
||||||
padding: var(--padding-m);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@ -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 => {
|
export const getSessionId = (state: AppState): SessionId | undefined => {
|
||||||
switch (state.auth.type) {
|
switch (state.auth.type) {
|
||||||
case "NoAdmin": return undefined
|
case "NoAdmin": return undefined
|
||||||
@ -75,13 +77,18 @@ class StateManager {
|
|||||||
if (!admin_enabled) {
|
if (!admin_enabled) {
|
||||||
this.dispatch({ type: "SetAuthState", content: { type: "NoAdmin" } });
|
this.dispatch({ type: "SetAuthState", content: { type: "NoAdmin" } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return admin_enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
async setAdminPassword(password: string) {
|
async setAdminPassword(password: string) {
|
||||||
if (!this.client || !this.dispatch) return;
|
if (!this.client || !this.dispatch) return;
|
||||||
|
|
||||||
await this.client.setAdminPassword(password);
|
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) {
|
async auth(username: string, password: string) {
|
||||||
|
Loading…
Reference in New Issue
Block a user