feat(ui, webserver): support list users in team management (#1005)

* feat(webserver): support list users

* feat(ui, webserver): support list users in team management

* fix lint
r0.7
Meng Zhang 2023-12-10 15:14:17 +08:00 committed by GitHub
parent ae4dc5f8d0
commit 58787e707b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 92 additions and 2 deletions

View File

@ -3,10 +3,11 @@
import { CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import InvitationTable from './invitation-table'
import UsersTable from './user-table'
export default function Team() {
return (
<div>
<div className="xl:max-w-[750px]">
<div>
<CardHeader>
<CardTitle>Invites</CardTitle>
@ -19,7 +20,9 @@ export default function Team() {
<CardHeader>
<CardTitle>Users</CardTitle>
</CardHeader>
<CardContent className="p-4"></CardContent>
<CardContent className="p-4">
<UsersTable />
</CardContent>
</div>
</div>
)

View File

@ -0,0 +1,53 @@
'use client'
import React from 'react'
import moment from 'moment'
import { graphql } from '@/lib/gql/generates'
import { useAuthenticatedGraphQLQuery } from '@/lib/tabby/gql'
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow
} from '@/components/ui/table'
const listUsers = graphql(/* GraphQL */ `
query ListUsers {
users {
email
isAdmin
createdAt
}
}
`)
export default function UsersTable() {
const { data, mutate } = useAuthenticatedGraphQLQuery(listUsers)
const users = data?.users
return (
users && (
<Table>
<TableHeader>
<TableRow>
<TableHead>Email</TableHead>
<TableHead>Joined</TableHead>
<TableHead>Role</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{users.map((x, i) => (
<TableRow key={i}>
<TableCell className="w-[300px] font-medium">{x.email}</TableCell>
<TableCell>{moment.utc(x.createdAt).fromNow()}</TableCell>
<TableCell>{x.isAdmin ? 'Admin' : 'Member'}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
)
)
}

View File

@ -43,6 +43,7 @@ type Query {
isAdminInitialized: Boolean!
invitations: [Invitation!]!
me: User!
users: [User!]!
}
type Invitation {
@ -56,6 +57,7 @@ type User {
email: String!
isAdmin: Boolean!
authToken: String!
createdAt: DateTimeUtc!
}
type Worker {

View File

@ -276,6 +276,8 @@ pub trait AuthenticationService: Send + Sync {
async fn delete_invitation(&self, id: i32) -> Result<i32>;
async fn reset_user_auth_token(&self, email: &str) -> Result<()>;
async fn list_users(&self) -> Result<Vec<User>>;
}
#[cfg(test)]

View File

@ -4,6 +4,7 @@ pub mod worker;
use std::sync::Arc;
use auth::AuthenticationService;
use chrono::{DateTime, Utc};
use juniper::{
graphql_object, graphql_value, EmptySubscription, FieldError, GraphQLObject, IntoFieldError,
Object, RootNode, ScalarValue, Value,
@ -118,6 +119,15 @@ impl Query {
Err(CoreError::Unauthorized("Not logged in"))
}
}
async fn users(ctx: &Context) -> Result<Vec<User>> {
if let Some(claims) = &ctx.claims {
if claims.is_admin {
return Ok(ctx.locator.auth().list_users().await?);
}
}
Err(CoreError::Unauthorized("Only admin is able to query users"))
}
}
#[derive(Debug, GraphQLObject)]
@ -125,6 +135,7 @@ pub struct User {
pub email: String,
pub is_admin: bool,
pub auth_token: String,
pub created_at: DateTime<Utc>,
}
#[derive(Default)]

View File

@ -296,6 +296,11 @@ impl AuthenticationService for DbConn {
async fn reset_user_auth_token(&self, email: &str) -> Result<()> {
self.reset_user_auth_token_by_email(email).await
}
async fn list_users(&self) -> Result<Vec<User>> {
let users = self.list_users().await?;
Ok(users.into_iter().map(|x| x.into()).collect())
}
}
fn password_hash(raw: &str) -> password_hash::Result<String> {

View File

@ -48,6 +48,7 @@ impl From<User> for schema::User {
email: val.email,
is_admin: val.is_admin,
auth_token: val.auth_token,
created_at: val.created_at,
}
}
}
@ -147,6 +148,19 @@ impl DbConn {
Ok(users)
}
pub async fn list_users(&self) -> Result<Vec<User>> {
let users = self
.conn
.call(move |c| {
let mut stmt = c.prepare(&User::select("true"))?;
let user_iter = stmt.query_map([], User::from_row)?;
Ok(user_iter.filter_map(|x| x.ok()).collect::<Vec<_>>())
})
.await?;
Ok(users)
}
pub async fn verify_auth_token(&self, token: &str) -> bool {
let token = token.to_owned();
let id: Result<i32, _> = self