Set up the user interface state model and set up the admin user onboarding #283
|
@ -8,7 +8,7 @@ use uuid::Uuid;
|
|||
|
||||
use crate::{
|
||||
asset_db::{self, AssetId, Assets},
|
||||
database::{CharacterId, Database, Error},
|
||||
database::{CharacterId, Database, Error, UserId},
|
||||
types::{AppError, FatalError, Message, Tabletop, RGB},
|
||||
};
|
||||
|
||||
|
@ -81,6 +81,18 @@ impl Core {
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn list_users(&self) -> ResultExt<Vec<(UserId, String)>, AppError, FatalError> {
|
||||
let users = self.0.write().await.db.users().await;
|
||||
match users {
|
||||
ResultExt::Ok(users) => ResultExt::Ok(users),
|
||||
ResultExt::Err(err) => {
|
||||
println!("Database error: {:?}", err);
|
||||
ResultExt::Ok(vec![])
|
||||
}
|
||||
ResultExt::Fatal(users) => ResultExt::Fatal(users),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn tabletop(&self) -> Tabletop {
|
||||
self.0.read().await.tabletop.clone()
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ pub enum Error {
|
|||
#[derive(Debug)]
|
||||
enum Request {
|
||||
Charsheet(CharacterId),
|
||||
Users,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -39,6 +40,7 @@ struct DatabaseRequest {
|
|||
|
||||
#[derive(Debug)]
|
||||
enum DatabaseResponse {
|
||||
Users(Vec<(UserId, String)>),
|
||||
Charsheet(Option<CharsheetRow>),
|
||||
}
|
||||
|
||||
|
@ -142,6 +144,8 @@ pub struct CharsheetRow {
|
|||
|
||||
#[async_trait]
|
||||
pub trait Database: Send + Sync {
|
||||
async fn users(&mut self) -> result_extended::ResultExt<Vec<(UserId, String)>, Error, FatalError>;
|
||||
|
||||
async fn character(
|
||||
&mut self,
|
||||
id: CharacterId,
|
||||
|
@ -240,6 +244,17 @@ impl DiskDb {
|
|||
}
|
||||
}
|
||||
|
||||
fn users(&self) -> Result<Vec<(UserId, String)>, FatalError> {
|
||||
let mut stmt = self.conn.prepare("SELECT * FROM USERS")
|
||||
.map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
|
||||
let items = stmt.query_map([], |row| {
|
||||
let userid: String = row.get(0).unwrap();
|
||||
let username = row.get(1).unwrap();
|
||||
Ok((UserId::from(userid), username))
|
||||
}).unwrap().collect::<Result<Vec<(UserId, String)>, rusqlite::Error>>().unwrap();
|
||||
Ok(items)
|
||||
}
|
||||
|
||||
fn save_user(
|
||||
&self,
|
||||
user_id: Option<UserId>,
|
||||
|
@ -374,6 +389,15 @@ async fn db_handler(db: DiskDb, requestor: Receiver<DatabaseRequest>) {
|
|||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
Request::Users => {
|
||||
let users = db.users();
|
||||
match users {
|
||||
Ok(users) => {
|
||||
tx.send(DatabaseResponse::Users(users)).await.unwrap();
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
println!("ending db_handler");
|
||||
|
@ -402,6 +426,26 @@ impl DbConn {
|
|||
|
||||
#[async_trait]
|
||||
impl Database for DbConn {
|
||||
async fn users(&mut self) -> ResultExt<Vec<(UserId, String)>, Error, FatalError> {
|
||||
let (tx, rx) = bounded::<DatabaseResponse>(1);
|
||||
|
||||
let request = DatabaseRequest {
|
||||
tx,
|
||||
req: Request::Users,
|
||||
};
|
||||
|
||||
match self.conn.send(request).await {
|
||||
Ok(()) => (),
|
||||
Err(_) => return fatal(FatalError::DatabaseConnectionLost),
|
||||
};
|
||||
|
||||
match rx.recv().await {
|
||||
Ok(DatabaseResponse::Users(lst)) => ok(lst),
|
||||
Ok(_) => fatal(FatalError::MessageMismatch),
|
||||
Err(_) => error(Error::NoResponse),
|
||||
}
|
||||
}
|
||||
|
||||
async fn character(
|
||||
&mut self,
|
||||
id: CharacterId,
|
||||
|
@ -420,7 +464,7 @@ impl Database for DbConn {
|
|||
|
||||
match rx.recv().await {
|
||||
Ok(DatabaseResponse::Charsheet(row)) => ok(row),
|
||||
// Ok(_) => fatal(FatalError::MessageMismatch),
|
||||
Ok(_) => fatal(FatalError::MessageMismatch),
|
||||
Err(_err) => error(Error::NoResponse),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,12 @@ use result_extended::{ok, return_error, ResultExt};
|
|||
use serde::{Deserialize, Serialize};
|
||||
use warp::{http::Response, http::StatusCode, reply::Reply, ws::Message};
|
||||
|
||||
use crate::{asset_db::AssetId, core::Core, database::CharacterId, types::{AppError, FatalError}};
|
||||
use crate::{
|
||||
asset_db::AssetId,
|
||||
core::Core,
|
||||
database::CharacterId,
|
||||
types::{AppError, FatalError},
|
||||
};
|
||||
|
||||
/*
|
||||
pub async fn handle_auth(
|
||||
|
@ -164,6 +169,23 @@ pub async fn handle_set_background_image(core: Core, image_name: String) -> impl
|
|||
.await
|
||||
}
|
||||
|
||||
pub async fn handle_get_users(core: Core) -> impl Reply {
|
||||
handler(async move {
|
||||
let users = match core.list_users().await {
|
||||
ResultExt::Ok(users) => users,
|
||||
ResultExt::Err(err) => return ResultExt::Err(err),
|
||||
ResultExt::Fatal(err) => return ResultExt::Fatal(err),
|
||||
};
|
||||
|
||||
ok(Response::builder()
|
||||
.header("Access-Control-Allow-Origin", "*")
|
||||
.header("Content-Type", "application/json")
|
||||
.body(serde_json::to_vec(&users).unwrap())
|
||||
.unwrap())
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn handle_get_charsheet(core: Core, charid: String) -> impl Reply {
|
||||
handler(async move {
|
||||
let sheet = match core.get_charsheet(CharacterId::from(charid)).await {
|
||||
|
|
|
@ -8,7 +8,7 @@ use asset_db::{AssetId, FsAssets};
|
|||
use authdb::AuthError;
|
||||
use database::DbConn;
|
||||
use handlers::{
|
||||
handle_available_images, handle_connect_websocket, handle_file, handle_get_charsheet, 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_set_background_image, handle_unregister_client, RegisterRequest
|
||||
};
|
||||
use warp::{
|
||||
// header,
|
||||
|
@ -160,6 +160,13 @@ pub async fn main() {
|
|||
})
|
||||
.with(log);
|
||||
|
||||
let route_get_users = warp::path!("api" / "v1" / "users")
|
||||
.and(warp::get())
|
||||
.then({
|
||||
let core = core.clone();
|
||||
move || handle_get_users(core.clone())
|
||||
});
|
||||
|
||||
let route_get_charsheet = warp::path!("api" / "v1" / "charsheet" / String)
|
||||
.and(warp::get())
|
||||
.then({
|
||||
|
@ -174,6 +181,7 @@ pub async fn main() {
|
|||
.or(route_available_images)
|
||||
.or(route_set_bg_image_options)
|
||||
.or(route_set_bg_image)
|
||||
.or(route_get_users)
|
||||
.or(route_get_charsheet)
|
||||
.recover(handle_rejection);
|
||||
|
||||
|
|
|
@ -6,8 +6,11 @@ import { DesignPage } from './views/Design/Design';
|
|||
import { GmView } from './views/GmView/GmView';
|
||||
import { WebsocketProvider } from './components/WebsocketProvider';
|
||||
import { PlayerView } from './views/PlayerView/PlayerView';
|
||||
import { Admin } from './views/Admin/Admin';
|
||||
import Candela from './plugins/Candela';
|
||||
|
||||
const TEST_CHARSHEET_UUID = "12df9c09-1f2f-4147-8eda-a97bd2a7a803";
|
||||
|
||||
interface AppProps {
|
||||
client: Client;
|
||||
}
|
||||
|
@ -15,7 +18,7 @@ interface AppProps {
|
|||
const CandelaCharsheet = ({ client }: { client: Client }) => {
|
||||
let [sheet, setSheet] = useState(undefined);
|
||||
useEffect(
|
||||
() => { client.charsheet("db7a2585-5dcf-4909-8743-2741111f8b9a").then((c) => setSheet(c)); },
|
||||
() => { client.charsheet(TEST_CHARSHEET_UUID).then((c) => setSheet(c)); },
|
||||
[client, setSheet]
|
||||
);
|
||||
|
||||
|
@ -36,6 +39,10 @@ const App = ({ client }: AppProps) => {
|
|||
path: "/gm",
|
||||
element: websocketUrl ? <WebsocketProvider websocketUrl={websocketUrl}> <GmView client={client} /> </WebsocketProvider> : <div> </div>
|
||||
},
|
||||
{
|
||||
path: "/admin",
|
||||
element: <Admin client={client} />
|
||||
},
|
||||
{
|
||||
path: "/",
|
||||
element: websocketUrl ? <WebsocketProvider websocketUrl={websocketUrl}> <PlayerView client={client} /> </WebsocketProvider> : <div> </div>
|
||||
|
|
|
@ -45,6 +45,12 @@ export class Client {
|
|||
return fetch(url, { method: 'PUT', headers: [['Content-Type', 'application/json']], body: JSON.stringify(name) });
|
||||
}
|
||||
|
||||
async users() {
|
||||
const url = new URL(this.base);
|
||||
url.pathname = '/api/v1/users/';
|
||||
return fetch(url).then((response) => response.json());
|
||||
}
|
||||
|
||||
async charsheet(id: string) {
|
||||
const url = new URL(this.base);
|
||||
url.pathname = `/api/v1/charsheet/${id}`;
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { Client } from '../../client';
|
||||
|
||||
interface AdminProps {
|
||||
client: Client,
|
||||
}
|
||||
|
||||
export const Admin = ({ client }: AdminProps) => {
|
||||
const [users, setUsers] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
client.users().then(setUsers);
|
||||
}, [client, setUsers]);
|
||||
return <ul>
|
||||
{users.map(([uuid, username]) => <li> {username} </li>) }
|
||||
</ul>;
|
||||
}
|
|
@ -5,6 +5,8 @@ import { Client } from '../../client';
|
|||
import { TabletopElement } from '../../components/Tabletop/Tabletop';
|
||||
import Candela from '../../plugins/Candela';
|
||||
|
||||
const TEST_CHARSHEET_UUID = "12df9c09-1f2f-4147-8eda-a97bd2a7a803";
|
||||
|
||||
interface PlayerViewProps {
|
||||
client: Client;
|
||||
}
|
||||
|
@ -16,7 +18,7 @@ export const PlayerView = ({ client }: PlayerViewProps) => {
|
|||
|
||||
useEffect(
|
||||
() => {
|
||||
client.charsheet("db7a2585-5dcf-4909-8743-2741111f8b9a").then((c) => {
|
||||
client.charsheet(TEST_CHARSHEET_UUID).then((c) => {
|
||||
setCharsheet(c)
|
||||
});
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue