Set up the user interface state model and set up the admin user onboarding #283
|
@ -99,7 +99,7 @@ impl<A, E, FE> ResultExt<A, E, FE> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert from a normal `Result` type to a `Result` type. The error condition for a `Result` will
|
/// Convert from a normal `Result` type to a `ResultExt` type. The error condition for a `Result` will
|
||||||
/// be treated as `Result::Err`, never `Result::Fatal`.
|
/// be treated as `Result::Err`, never `Result::Fatal`.
|
||||||
impl<A, E, FE> From<std::result::Result<A, E>> for ResultExt<A, E, FE> {
|
impl<A, E, FE> From<std::result::Result<A, E>> for ResultExt<A, E, FE> {
|
||||||
fn from(r: std::result::Result<A, E>) -> Self {
|
fn from(r: std::result::Result<A, E>) -> Self {
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
CREATE TABLE games(
|
|
||||||
uuid TEXT PRIMARY KEY,
|
|
||||||
name TEXT
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE characters(
|
|
||||||
uuid TEXT PRIMARY KEY,
|
|
||||||
game TEXT,
|
|
||||||
data TEXT,
|
|
||||||
|
|
||||||
FOREIGN KEY(game) REFERENCES games(uuid)
|
|
||||||
);
|
|
|
@ -6,6 +6,19 @@ CREATE TABLE users(
|
||||||
enabled BOOLEAN
|
enabled BOOLEAN
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE games(
|
||||||
|
uuid TEXT PRIMARY KEY,
|
||||||
|
name TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE characters(
|
||||||
|
uuid TEXT PRIMARY KEY,
|
||||||
|
game TEXT,
|
||||||
|
data TEXT,
|
||||||
|
|
||||||
|
FOREIGN KEY(game) REFERENCES games(uuid)
|
||||||
|
);
|
||||||
|
|
||||||
CREATE TABLE roles(
|
CREATE TABLE roles(
|
||||||
user_id TEXT,
|
user_id TEXT,
|
||||||
game_id TEXT,
|
game_id TEXT,
|
||||||
|
@ -14,3 +27,6 @@ CREATE TABLE roles(
|
||||||
FOREIGN KEY(user_id) REFERENCES users(uuid),
|
FOREIGN KEY(user_id) REFERENCES users(uuid),
|
||||||
FOREIGN KEY(game_id) REFERENCES games(uuid)
|
FOREIGN KEY(game_id) REFERENCES games(uuid)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
INSERT INTO users VALUES ("admin", "admin", "", true, true);
|
||||||
|
|
|
@ -2,8 +2,10 @@ use std::{collections::HashMap, sync::Arc};
|
||||||
|
|
||||||
use async_std::sync::RwLock;
|
use async_std::sync::RwLock;
|
||||||
use mime::Mime;
|
use mime::Mime;
|
||||||
use result_extended::{fatal, ok, ResultExt};
|
use result_extended::{fatal, ok, return_error, ResultExt};
|
||||||
|
use serde::Serialize;
|
||||||
use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender};
|
use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender};
|
||||||
|
use typeshare::typeshare;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -18,6 +20,12 @@ const DEFAULT_BACKGROUND_COLOR: RGB = RGB {
|
||||||
blue: 0xbb,
|
blue: 0xbb,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize)]
|
||||||
|
#[typeshare]
|
||||||
|
pub struct Status {
|
||||||
|
admin_enabled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct WebsocketClient {
|
struct WebsocketClient {
|
||||||
sender: Option<UnboundedSender<Message>>,
|
sender: Option<UnboundedSender<Message>>,
|
||||||
|
@ -51,6 +59,27 @@ impl Core {
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn status(&self) -> ResultExt<Status, AppError, FatalError> {
|
||||||
|
let mut state = self.0.write().await;
|
||||||
|
let admin_user = match return_error!(state
|
||||||
|
.db
|
||||||
|
.user(UserId::from("admin"))
|
||||||
|
.await
|
||||||
|
.map_err(|_| AppError::Inaccessible("database stopped responding".to_owned())))
|
||||||
|
{
|
||||||
|
Some(admin_user) => admin_user,
|
||||||
|
None => {
|
||||||
|
return ok(Status {
|
||||||
|
admin_enabled: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ok(Status {
|
||||||
|
admin_enabled: !admin_user.password.is_empty(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn register_client(&self) -> String {
|
pub async fn register_client(&self) -> String {
|
||||||
let mut state = self.0.write().await;
|
let mut state = self.0.write().await;
|
||||||
let uuid = Uuid::new_v4().simple().to_string();
|
let uuid = Uuid::new_v4().simple().to_string();
|
||||||
|
|
|
@ -5,7 +5,10 @@ use async_trait::async_trait;
|
||||||
use include_dir::{include_dir, Dir};
|
use include_dir::{include_dir, Dir};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use result_extended::{error, fatal, ok, return_error, ResultExt};
|
use result_extended::{error, fatal, ok, return_error, ResultExt};
|
||||||
use rusqlite::{types::{FromSql, FromSqlResult, ValueRef}, Connection};
|
use rusqlite::{
|
||||||
|
types::{FromSql, FromSqlResult, ValueRef},
|
||||||
|
Connection,
|
||||||
|
};
|
||||||
use rusqlite_migration::Migrations;
|
use rusqlite_migration::Migrations;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
@ -30,6 +33,7 @@ pub enum Error {
|
||||||
enum Request {
|
enum Request {
|
||||||
Charsheet(CharacterId),
|
Charsheet(CharacterId),
|
||||||
Games,
|
Games,
|
||||||
|
User(UserId),
|
||||||
Users,
|
Users,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,6 +47,7 @@ struct DatabaseRequest {
|
||||||
enum DatabaseResponse {
|
enum DatabaseResponse {
|
||||||
Charsheet(Option<CharsheetRow>),
|
Charsheet(Option<CharsheetRow>),
|
||||||
Games(Vec<GameRow>),
|
Games(Vec<GameRow>),
|
||||||
|
User(Option<UserRow>),
|
||||||
Users(Vec<UserRow>),
|
Users(Vec<UserRow>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,6 +184,11 @@ pub struct CharsheetRow {
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait Database: Send + Sync {
|
pub trait Database: Send + Sync {
|
||||||
|
async fn user(
|
||||||
|
&mut self,
|
||||||
|
_: UserId,
|
||||||
|
) -> result_extended::ResultExt<Option<UserRow>, Error, FatalError>;
|
||||||
|
|
||||||
async fn users(&mut self) -> result_extended::ResultExt<Vec<UserRow>, Error, FatalError>;
|
async fn users(&mut self) -> result_extended::ResultExt<Vec<UserRow>, Error, FatalError>;
|
||||||
|
|
||||||
async fn games(&mut self) -> result_extended::ResultExt<Vec<GameRow>, Error, FatalError>;
|
async fn games(&mut self) -> result_extended::ResultExt<Vec<GameRow>, Error, FatalError>;
|
||||||
|
@ -193,6 +203,7 @@ pub struct DiskDb {
|
||||||
conn: Connection,
|
conn: Connection,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
fn setup_test_database(conn: &Connection) -> Result<(), FatalError> {
|
fn setup_test_database(conn: &Connection) -> Result<(), FatalError> {
|
||||||
let mut gamecount_stmt = conn.prepare("SELECT count(*) FROM games").unwrap();
|
let mut gamecount_stmt = conn.prepare("SELECT count(*) FROM games").unwrap();
|
||||||
let mut count = gamecount_stmt.query([]).unwrap();
|
let mut count = gamecount_stmt.query([]).unwrap();
|
||||||
|
@ -236,6 +247,7 @@ fn setup_test_database(conn: &Connection) -> Result<(), FatalError> {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
impl DiskDb {
|
impl DiskDb {
|
||||||
pub fn new<P>(path: Option<P>) -> Result<Self, FatalError>
|
pub fn new<P>(path: Option<P>) -> Result<Self, FatalError>
|
||||||
|
@ -251,15 +263,18 @@ impl DiskDb {
|
||||||
.to_latest(&mut conn)
|
.to_latest(&mut conn)
|
||||||
.map_err(|err| FatalError::DatabaseMigrationFailure(format!("{}", err)))?;
|
.map_err(|err| FatalError::DatabaseMigrationFailure(format!("{}", err)))?;
|
||||||
|
|
||||||
setup_test_database(&conn)?;
|
// setup_test_database(&conn)?;
|
||||||
|
|
||||||
Ok(DiskDb { conn })
|
Ok(DiskDb { conn })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn users(&self) -> Result<Vec<UserRow>, FatalError> {
|
fn users(&self) -> Result<Vec<UserRow>, FatalError> {
|
||||||
let mut stmt = self.conn.prepare("SELECT * FROM USERS")
|
let mut stmt = self
|
||||||
|
.conn
|
||||||
|
.prepare("SELECT * FROM users")
|
||||||
.map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
|
.map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
|
||||||
let items = stmt.query_map([], |row| {
|
let items = stmt
|
||||||
|
.query_map([], |row| {
|
||||||
Ok(UserRow {
|
Ok(UserRow {
|
||||||
id: row.get(0).unwrap(),
|
id: row.get(0).unwrap(),
|
||||||
name: row.get(1).unwrap(),
|
name: row.get(1).unwrap(),
|
||||||
|
@ -267,14 +282,17 @@ impl DiskDb {
|
||||||
admin: row.get(3).unwrap(),
|
admin: row.get(3).unwrap(),
|
||||||
enabled: row.get(4).unwrap(),
|
enabled: row.get(4).unwrap(),
|
||||||
})
|
})
|
||||||
}).unwrap().collect::<Result<Vec<UserRow>, rusqlite::Error>>().unwrap();
|
})
|
||||||
|
.unwrap()
|
||||||
|
.collect::<Result<Vec<UserRow>, rusqlite::Error>>()
|
||||||
|
.unwrap();
|
||||||
Ok(items)
|
Ok(items)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn user(&self, id: UserId) -> Result<Option<UserRow>, FatalError> {
|
fn user(&self, id: UserId) -> Result<Option<UserRow>, FatalError> {
|
||||||
let mut stmt = self
|
let mut stmt = self
|
||||||
.conn
|
.conn
|
||||||
.prepare("SELECT uuid, name, password, admin, enabled WHERE uuid=?")
|
.prepare("SELECT uuid, name, password, admin, enabled FROM users WHERE uuid=?")
|
||||||
.map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
|
.map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
|
||||||
let items: Vec<UserRow> = stmt
|
let items: Vec<UserRow> = stmt
|
||||||
.query_map([id.as_str()], |row| {
|
.query_map([id.as_str()], |row| {
|
||||||
|
@ -329,11 +347,7 @@ impl DiskDb {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save_game(
|
fn save_game(&self, game_id: Option<GameId>, name: &str) -> Result<GameId, FatalError> {
|
||||||
&self,
|
|
||||||
game_id: Option<GameId>,
|
|
||||||
name: &str,
|
|
||||||
) -> Result<GameId, FatalError> {
|
|
||||||
match game_id {
|
match game_id {
|
||||||
None => {
|
None => {
|
||||||
let game_id = GameId::new();
|
let game_id = GameId::new();
|
||||||
|
@ -341,19 +355,15 @@ impl DiskDb {
|
||||||
.conn
|
.conn
|
||||||
.prepare("INSERT INTO games VALUES (?, ?)")
|
.prepare("INSERT INTO games VALUES (?, ?)")
|
||||||
.map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
|
.map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
|
||||||
stmt.execute((game_id.as_str(), name))
|
stmt.execute((game_id.as_str(), name)).unwrap();
|
||||||
.unwrap();
|
|
||||||
Ok(game_id)
|
Ok(game_id)
|
||||||
}
|
}
|
||||||
Some(game_id) => {
|
Some(game_id) => {
|
||||||
let mut stmt = self
|
let mut stmt = self
|
||||||
.conn
|
.conn
|
||||||
.prepare(
|
.prepare("UPDATE games SET name=? WHERE uuid=?")
|
||||||
"UPDATE games SET name=? WHERE uuid=?",
|
|
||||||
)
|
|
||||||
.map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
|
.map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
|
||||||
stmt.execute((name, game_id.as_str()))
|
stmt.execute((name, game_id.as_str())).unwrap();
|
||||||
.unwrap();
|
|
||||||
Ok(game_id)
|
Ok(game_id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -432,6 +442,15 @@ async fn db_handler(db: DiskDb, requestor: Receiver<DatabaseRequest>) {
|
||||||
Request::Games => {
|
Request::Games => {
|
||||||
unimplemented!();
|
unimplemented!();
|
||||||
}
|
}
|
||||||
|
Request::User(uid) => {
|
||||||
|
let user = db.user(uid);
|
||||||
|
match user {
|
||||||
|
Ok(user) => {
|
||||||
|
tx.send(DatabaseResponse::User(user)).await.unwrap();
|
||||||
|
}
|
||||||
|
err => panic!("{:?}", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
Request::Users => {
|
Request::Users => {
|
||||||
let users = db.users();
|
let users = db.users();
|
||||||
match users {
|
match users {
|
||||||
|
@ -469,6 +488,26 @@ impl DbConn {
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Database for DbConn {
|
impl Database for DbConn {
|
||||||
|
async fn user(&mut self, uid: UserId) -> ResultExt<Option<UserRow>, Error, FatalError> {
|
||||||
|
let (tx, rx) = bounded::<DatabaseResponse>(1);
|
||||||
|
|
||||||
|
let request = DatabaseRequest {
|
||||||
|
tx,
|
||||||
|
req: Request::User(uid),
|
||||||
|
};
|
||||||
|
|
||||||
|
match self.conn.send(request).await {
|
||||||
|
Ok(()) => (),
|
||||||
|
Err(_) => return fatal(FatalError::DatabaseConnectionLost),
|
||||||
|
};
|
||||||
|
|
||||||
|
match rx.recv().await {
|
||||||
|
Ok(DatabaseResponse::User(user)) => ok(user),
|
||||||
|
Ok(_) => fatal(FatalError::MessageMismatch),
|
||||||
|
Err(_) => error(Error::NoResponse),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn users(&mut self) -> ResultExt<Vec<UserRow>, Error, FatalError> {
|
async fn users(&mut self) -> ResultExt<Vec<UserRow>, Error, FatalError> {
|
||||||
let (tx, rx) = bounded::<DatabaseResponse>(1);
|
let (tx, rx) = bounded::<DatabaseResponse>(1);
|
||||||
|
|
||||||
|
@ -559,9 +598,7 @@ mod test {
|
||||||
assert_matches!(db.character(CharacterId::from("1")), Ok(None));
|
assert_matches!(db.character(CharacterId::from("1")), Ok(None));
|
||||||
|
|
||||||
let js: serde_json::Value = serde_json::from_str(soren).unwrap();
|
let js: serde_json::Value = serde_json::from_str(soren).unwrap();
|
||||||
let soren_id = db
|
let soren_id = db.save_character(None, game_id, js.clone()).unwrap();
|
||||||
.save_character(None, game_id, js.clone())
|
|
||||||
.unwrap();
|
|
||||||
assert_matches!(db.character(soren_id).unwrap(), Some(CharsheetRow{ data, .. }) => assert_eq!(js, data));
|
assert_matches!(db.character(soren_id).unwrap(), Some(CharsheetRow{ data, .. }) => assert_eq!(js, data));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -56,11 +56,23 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn handle_server_status(core: Core) -> impl Reply {
|
||||||
|
handler(async move {
|
||||||
|
let status = return_error!(core.status().await);
|
||||||
|
ok(Response::builder()
|
||||||
|
.header("Access-Control-Allow-Origin", "*")
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.body(serde_json::to_vec(&status).unwrap())
|
||||||
|
.unwrap())
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn handle_file(core: Core, asset_id: AssetId) -> impl Reply {
|
pub async fn handle_file(core: Core, asset_id: AssetId) -> impl Reply {
|
||||||
handler(async move {
|
handler(async move {
|
||||||
let (mime, bytes) = return_error!(core.get_asset(asset_id).await);
|
let (mime, bytes) = return_error!(core.get_asset(asset_id).await);
|
||||||
ok(Response::builder()
|
ok(Response::builder()
|
||||||
.header("application-type", mime.to_string())
|
.header("content-type", mime.to_string())
|
||||||
.body(bytes)
|
.body(bytes)
|
||||||
.unwrap())
|
.unwrap())
|
||||||
})
|
})
|
||||||
|
|
|
@ -8,7 +8,7 @@ use asset_db::{AssetId, FsAssets};
|
||||||
use authdb::AuthError;
|
use authdb::AuthError;
|
||||||
use database::DbConn;
|
use database::DbConn;
|
||||||
use handlers::{
|
use handlers::{
|
||||||
handle_available_images, handle_connect_websocket, handle_file, handle_get_charsheet, handle_get_users, handle_register_client, handle_set_background_image, handle_unregister_client, RegisterRequest
|
handle_available_images, handle_connect_websocket, handle_file, handle_get_charsheet, handle_get_users, handle_register_client, handle_server_status, handle_set_background_image, handle_unregister_client, RegisterRequest
|
||||||
};
|
};
|
||||||
use warp::{
|
use warp::{
|
||||||
// header,
|
// header,
|
||||||
|
@ -104,6 +104,13 @@ pub async fn main() {
|
||||||
let core = core::Core::new(FsAssets::new(PathBuf::from("/home/savanni/Pictures")), conn);
|
let core = core::Core::new(FsAssets::new(PathBuf::from("/home/savanni/Pictures")), conn);
|
||||||
let log = warp::log("visions::api");
|
let log = warp::log("visions::api");
|
||||||
|
|
||||||
|
let server_status = warp::path!("api" / "v1" / "status")
|
||||||
|
.and(warp::get())
|
||||||
|
.then({
|
||||||
|
let core = core.clone();
|
||||||
|
move || handle_server_status(core.clone())
|
||||||
|
});
|
||||||
|
|
||||||
let route_image = warp::path!("api" / "v1" / "image" / String)
|
let route_image = warp::path!("api" / "v1" / "image" / String)
|
||||||
.and(warp::get())
|
.and(warp::get())
|
||||||
.then({
|
.then({
|
||||||
|
@ -174,7 +181,8 @@ pub async fn main() {
|
||||||
move |charid| handle_get_charsheet(core.clone(), charid)
|
move |charid| handle_get_charsheet(core.clone(), charid)
|
||||||
});
|
});
|
||||||
|
|
||||||
let filter = route_register_client
|
let filter = server_status
|
||||||
|
.or(route_register_client)
|
||||||
.or(route_unregister_client)
|
.or(route_unregister_client)
|
||||||
.or(route_websocket)
|
.or(route_websocket)
|
||||||
.or(route_image)
|
.or(route_image)
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { PlayerView } from './views/PlayerView/PlayerView';
|
||||||
import { Admin } from './views/Admin/Admin';
|
import { Admin } from './views/Admin/Admin';
|
||||||
import Candela from './plugins/Candela';
|
import Candela from './plugins/Candela';
|
||||||
import { Authentication } from './views/Authentication/Authentication';
|
import { Authentication } from './views/Authentication/Authentication';
|
||||||
|
import { StateProvider } from './components/StateProvider';
|
||||||
|
|
||||||
const TEST_CHARSHEET_UUID = "12df9c09-1f2f-4147-8eda-a97bd2a7a803";
|
const TEST_CHARSHEET_UUID = "12df9c09-1f2f-4147-8eda-a97bd2a7a803";
|
||||||
|
|
||||||
|
@ -38,7 +39,8 @@ const App = ({ client }: AppProps) => {
|
||||||
createBrowserRouter([
|
createBrowserRouter([
|
||||||
{
|
{
|
||||||
path: "/",
|
path: "/",
|
||||||
element: websocketUrl ? <WebsocketProvider websocketUrl={websocketUrl}> <Authentication client={client}> <PlayerView client={client} /> </Authentication> </WebsocketProvider> : <div> </div>
|
element: <StateProvider client={client}> <Authentication> <PlayerView client={client} /> </Authentication> </StateProvider>
|
||||||
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/gm",
|
path: "/gm",
|
||||||
|
|
|
@ -9,6 +9,12 @@ export class Client {
|
||||||
this.base = new URL("http://localhost:8001");
|
this.base = new URL("http://localhost:8001");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
status() {
|
||||||
|
const url = new URL(this.base);
|
||||||
|
url.pathname = `/api/v1/status`;
|
||||||
|
return fetch(url).then((response) => response.json());
|
||||||
|
}
|
||||||
|
|
||||||
registerWebsocket() {
|
registerWebsocket() {
|
||||||
const url = new URL(this.base);
|
const url = new URL(this.base);
|
||||||
url.pathname = `api/v1/client`;
|
url.pathname = `api/v1/client`;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import React, { createContext, PropsWithChildren, useEffect, useReducer } from "react";
|
import React, { createContext, PropsWithChildren, useCallback, useEffect, useReducer } from "react";
|
||||||
import { Tabletop } from "visions-types";
|
import { Status, Tabletop } from "visions-types";
|
||||||
|
import { Client } from "../client";
|
||||||
import { assertNever } from "../plugins/Candela";
|
import { assertNever } from "../plugins/Candela";
|
||||||
|
|
||||||
type AuthState = { type: "NoAdmin" } | { type: "Unauthed" } | { type: "Authed", username: string };
|
type AuthState = { type: "NoAdmin" } | { type: "Unauthed" } | { type: "Authed", username: string };
|
||||||
|
@ -21,12 +22,27 @@ const initialState = (): TabletopState => (
|
||||||
|
|
||||||
export const AppContext = createContext<TabletopState>(initialState());
|
export const AppContext = createContext<TabletopState>(initialState());
|
||||||
|
|
||||||
interface StateProviderProps { }
|
interface StateProviderProps { client: Client; }
|
||||||
|
|
||||||
export const StateProvider = ({ children }: PropsWithChildren<StateProviderProps>) => {
|
export const StateProvider = ({ client, children }: PropsWithChildren<StateProviderProps>) => {
|
||||||
|
console.log("StateProvider");
|
||||||
const [state, dispatch] = useReducer(stateReducer, initialState());
|
const [state, dispatch] = useReducer(stateReducer, initialState());
|
||||||
|
|
||||||
return <AppContext.Provider value={initialState()}>
|
useEffect(() => {
|
||||||
|
console.log("useCallback");
|
||||||
|
client.status().then((status: Status) => {
|
||||||
|
console.log("status: ", status);
|
||||||
|
if (status.admin_enabled) {
|
||||||
|
dispatch({ type: "SetAuthState", state: { type: "Unauthed" } });
|
||||||
|
} else {
|
||||||
|
dispatch({ type: "SetAuthState", state: { type: "NoAdmin" } });
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
[client]
|
||||||
|
);
|
||||||
|
|
||||||
|
return <AppContext.Provider value={state}>
|
||||||
{children}
|
{children}
|
||||||
</AppContext.Provider>;
|
</AppContext.Provider>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
import React, { PropsWithChildren, ReactNode, useContext, useEffect, useState } from 'react';
|
import React, { PropsWithChildren, useContext } from 'react';
|
||||||
import { Client } from '../../client';
|
|
||||||
import { AppContext } from '../../components/StateProvider';
|
import { AppContext } from '../../components/StateProvider';
|
||||||
import { assertNever } from '../../plugins/Candela';
|
import { assertNever } from '../../plugins/Candela';
|
||||||
import './Authentication.css';
|
import './Authentication.css';
|
||||||
|
|
||||||
interface AuthenticationProps {
|
interface AuthenticationProps {
|
||||||
client: Client;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Authentication = ({ client, children }: PropsWithChildren<AuthenticationProps>) => {
|
export const Authentication = ({ children }: PropsWithChildren<AuthenticationProps>) => {
|
||||||
// No admin password set: prompt for the admin password
|
// No admin password set: prompt for the admin password
|
||||||
// Password set, nobody logged in: prompt for login
|
// Password set, nobody logged in: prompt for login
|
||||||
// User logged in: show the children
|
// User logged in: show the children
|
||||||
|
|
Loading…
Reference in New Issue