From dcd5514433fa7e027affbf9ad630dec6afbe56a9 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Sun, 26 Jan 2025 19:58:59 -0500 Subject: [PATCH] Let the admin see a list of users and the state of each one --- visions/server/src/core.rs | 12 +++----- visions/server/src/handlers/types.rs | 6 ++-- .../server/src/handlers/user_management.rs | 12 +++++--- visions/server/src/types.rs | 7 ++--- visions/ui/src/client.ts | 2 +- visions/ui/src/components/Profile/Profile.tsx | 9 ++++-- .../UserManagement/UserManagement.tsx | 28 +++++++++++++++---- visions/ui/src/views/Main/Main.tsx | 7 +++-- 8 files changed, 53 insertions(+), 30 deletions(-) diff --git a/visions/server/src/core.rs b/visions/server/src/core.rs index 24e863c..90ecdd7 100644 --- a/visions/server/src/core.rs +++ b/visions/server/src/core.rs @@ -140,6 +140,7 @@ impl Core { .map(|user| UserOverview { id: user.id, name: user.name, + state: user.state, is_admin: user.admin, }) .collect()), @@ -152,15 +153,10 @@ impl Core { user_id: UserId, ) -> ResultExt, AppError, FatalError> { let users = return_error!(self.list_users().await); - let user = match users.into_iter().find(|user| user.id == user_id) { - Some(user) => user, + match users.into_iter().find(|user| user.id == user_id) { + Some(user) => ok(Some(user)), None => return ok(None), - }; - ok(Some(UserOverview { - id: user.id.clone(), - name: user.name, - is_admin: user.is_admin, - })) + } } pub async fn create_user(&self, username: &str) -> ResultExt { diff --git a/visions/server/src/handlers/types.rs b/visions/server/src/handlers/types.rs index e485167..30a12ae 100644 --- a/visions/server/src/handlers/types.rs +++ b/visions/server/src/handlers/types.rs @@ -52,14 +52,14 @@ impl From for User { } } -/* -#[derive(Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] #[typeshare] pub struct UserOverview { pub id: UserId, pub name: String, pub is_admin: bool, + pub state: AccountState, pub games: Vec, } @@ -76,8 +76,8 @@ impl From for UserOverview { id: input.id, name: input.name, is_admin: input.is_admin, + state: AccountState::from(input.state), games: vec![], } } } -*/ diff --git a/visions/server/src/handlers/user_management.rs b/visions/server/src/handlers/user_management.rs index abd1006..0461538 100644 --- a/visions/server/src/handlers/user_management.rs +++ b/visions/server/src/handlers/user_management.rs @@ -10,6 +10,8 @@ use crate::{ types::{AppError, FatalError, User}, }; +use super::UserOverview; + #[derive(Clone, Debug, Deserialize, Serialize)] #[typeshare] pub struct AuthRequest { @@ -106,21 +108,23 @@ pub async fn get_user( core: Core, headers: HeaderMap, user_id: Option, -) -> ResultExt, AppError, FatalError> { +) -> ResultExt, AppError, FatalError> { auth_required(core.clone(), headers, |user| async move { match user_id { Some(user_id) => core.user(user_id).await, None => core.user(user.id).await, } - }).await + .map(|maybe_user| maybe_user.map(|user| UserOverview::from(user))) + }) + .await } pub async fn get_users( core: Core, headers: HeaderMap, -) -> ResultExt, AppError, FatalError> { +) -> ResultExt, AppError, FatalError> { auth_required(core.clone(), headers, |_user| async move { - core.list_users().await + core.list_users().await.map(|users| users.into_iter().map(|user| UserOverview::from(user)).collect::>()) }) .await } diff --git a/visions/server/src/types.rs b/visions/server/src/types.rs index 6c99e31..47ff775 100644 --- a/visions/server/src/types.rs +++ b/visions/server/src/types.rs @@ -167,16 +167,15 @@ pub enum Message { UpdateTabletop(Tabletop), } -#[derive(Clone, Debug, Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] -#[typeshare] +#[derive(Clone, Debug)] pub struct UserOverview { pub id: UserId, pub name: String, pub is_admin: bool, + pub state: AccountState, } -#[derive(Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] #[typeshare] pub struct GameOverview { diff --git a/visions/ui/src/client.ts b/visions/ui/src/client.ts index 397c077..96e56a2 100644 --- a/visions/ui/src/client.ts +++ b/visions/ui/src/client.ts @@ -50,7 +50,7 @@ export class Client { async users(sessionId: string) { const url = new URL(this.base); - url.pathname = '/api/v1/users/'; + url.pathname = '/api/v1/users'; return fetch(url, { method: 'GET', headers: [['Authorization', `Bearer ${sessionId}`]] diff --git a/visions/ui/src/components/Profile/Profile.tsx b/visions/ui/src/components/Profile/Profile.tsx index b531bfe..a6262cc 100644 --- a/visions/ui/src/components/Profile/Profile.tsx +++ b/visions/ui/src/components/Profile/Profile.tsx @@ -4,18 +4,23 @@ import './Profile.css'; interface ProfileProps { profile: UserOverview, + users: UserOverview[], games: GameOverview[], } -export const ProfileElement = ({ profile, games }: ProfileProps) => { +export const ProfileElement = ({ profile, users, games }: ProfileProps) => { const adminNote = profile.isAdmin ?
Note: this user is an admin
: <>; - return (
+ const userList = profile.isAdmin && ; + + return (
Games: {games.map((game) => { return {game.name} ({game.type}); }) }
{adminNote}
+ + {userList}
) } diff --git a/visions/ui/src/components/UserManagement/UserManagement.tsx b/visions/ui/src/components/UserManagement/UserManagement.tsx index 2b3d7b7..b81e02d 100644 --- a/visions/ui/src/components/UserManagement/UserManagement.tsx +++ b/visions/ui/src/components/UserManagement/UserManagement.tsx @@ -1,16 +1,32 @@ -import { UserOverview } from "visions-types" +import { AccountState, UserOverview } from "visions-types" import { CardElement } from ".." interface UserManagementProps { users: UserOverview[] } -export const UserManagementElement = ({ users }: UserManagementProps ) => { +export const UserManagementElement = ({ users }: UserManagementProps) => { return ( - -
    - {users.map((user) =>
  • {user.name}
  • )} -
+ + + + + + {users.map((user) => + + + )} + +
{user.name} {formatAccountState(user.state)}
+
) } + +const formatAccountState = (state: AccountState): string => { + switch (state.type) { + case "Normal": return ""; + case "PasswordReset": return "password reset"; + case "Locked": return "locked"; + } +} diff --git a/visions/ui/src/views/Main/Main.tsx b/visions/ui/src/views/Main/Main.tsx index 4c7c921..fb05a60 100644 --- a/visions/ui/src/views/Main/Main.tsx +++ b/visions/ui/src/views/Main/Main.tsx @@ -17,13 +17,16 @@ export const MainView = ({ client }: MainProps) => { useEffect(() => { if (sessionId) { client.profile(sessionId, undefined).then((profile) => setProfile(profile)) - client.users(sessionId).then((users) => setUsers(users)) + client.users(sessionId).then((users) => { + console.log(users); + setUsers(users); + }) } }, [sessionId, client]) return (
- {profile && } + {profile && }
) }