Skip to content
This repository was archived by the owner on May 30, 2024. It is now read-only.

Commit 3fa3906

Browse files
author
Noah Lee
authored
Display the Token in the settings page (#357)
* Add extendedUserData to the API * Change into the Descriptions component * Expose the token on the setting page
1 parent 9f2fdce commit 3fa3906

File tree

6 files changed

+105
-29
lines changed

6 files changed

+105
-29
lines changed

internal/server/api/v1/users/get.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,15 @@ import (
1010
"github.com/gitploy-io/gitploy/model/ent"
1111
)
1212

13+
type (
14+
// extendedUserData includes the 'hash' field.
15+
extendedUserData struct {
16+
*ent.User
17+
18+
Hash string `json:"hash"`
19+
}
20+
)
21+
1322
func (u *UserAPI) GetMe(c *gin.Context) {
1423
ctx := c.Request.Context()
1524

@@ -23,5 +32,8 @@ func (u *UserAPI) GetMe(c *gin.Context) {
2332
return
2433
}
2534

26-
gb.Response(c, http.StatusOK, uv)
35+
gb.Response(c, http.StatusOK, extendedUserData{
36+
User: uv,
37+
Hash: uv.Hash,
38+
})
2739
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package users
2+
3+
import (
4+
"encoding/json"
5+
"net/http"
6+
"net/http/httptest"
7+
"testing"
8+
9+
"github.com/gin-gonic/gin"
10+
"github.com/gitploy-io/gitploy/internal/server/api/v1/users/mock"
11+
"github.com/gitploy-io/gitploy/internal/server/global"
12+
"github.com/gitploy-io/gitploy/model/ent"
13+
gm "github.com/golang/mock/gomock"
14+
)
15+
16+
func init() {
17+
gin.SetMode(gin.ReleaseMode)
18+
}
19+
20+
func TestUsers_GetMe(t *testing.T) {
21+
t.Run("Return user's information with the 'hash' field.", func(t *testing.T) {
22+
const hash = "HASH_VALUE"
23+
24+
t.Log("Start mocking:")
25+
ctrl := gm.NewController(t)
26+
i := mock.NewMockInteractor(ctrl)
27+
28+
t.Log("\tFind the user.")
29+
i.EXPECT().
30+
FindUserByID(gm.Any(), gm.AssignableToTypeOf(int64(1))).
31+
Return(&ent.User{Hash: hash}, nil)
32+
33+
api := NewUserAPI(i)
34+
r := gin.New()
35+
r.GET("/user",
36+
func(c *gin.Context) {
37+
c.Set(global.KeyUser, &ent.User{})
38+
},
39+
api.GetMe)
40+
41+
req, _ := http.NewRequest("GET", "/user", nil)
42+
w := httptest.NewRecorder()
43+
r.ServeHTTP(w, req)
44+
45+
t.Log("Evaluate the return value.")
46+
if w.Code != http.StatusOK {
47+
t.Fatalf("Code = %v, wanted %v", w.Code, http.StatusOK)
48+
}
49+
50+
d := extendedUserData{}
51+
if err := json.Unmarshal(w.Body.Bytes(), &d); err != nil {
52+
t.Fatalf("Failed to unmarshal: %v", err)
53+
}
54+
55+
if d.Hash != hash {
56+
t.Fatalf("Hash = %v, wanted %v", d.Hash, hash)
57+
}
58+
})
59+
}

internal/server/api/v1/users/update_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import (
1616
"github.com/golang/mock/gomock"
1717
)
1818

19-
func TestUser_UpdateUser(t *testing.T) {
19+
func TestUserAPI_UpdateUser(t *testing.T) {
2020
input := struct {
2121
ID int64
2222
Payload *userPatchPayload

ui/src/apis/user.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export interface UserData {
99
login: string
1010
avatar: string
1111
admin: boolean
12+
hash?: string
1213
created_at: string
1314
updated_at: string
1415
edges: {
@@ -47,6 +48,7 @@ export const mapDataToUser = (data: UserData): User => {
4748
login: data.login,
4849
avatar: data.avatar,
4950
admin: data.admin,
51+
hash: data.hash,
5052
createdAt: new Date(data.created_at),
5153
updatedAt: new Date(data.updated_at),
5254
chatUser: cu,

ui/src/models/User.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ export default interface User {
33
login: string
44
avatar: string
55
admin: boolean
6+
// It exists only when getting self user.
7+
hash?: string
68
createdAt: Date
79
updatedAt: Date
810
chatUser: ChatUser | null

ui/src/views/Settings.tsx

Lines changed: 28 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,19 @@
1-
import { Avatar, Button, Tag } from "antd"
2-
import moment from "moment"
31
import { useEffect } from "react"
42
import { shallowEqual } from "react-redux"
53
import { Helmet } from "react-helmet"
4+
import { Button, Tag, Descriptions, Input } from "antd"
65

76
import { useAppSelector, useAppDispatch } from "../redux/hooks"
8-
import { fetchMe, fetchRateLimit,checkSlack } from "../redux/settings"
7+
import { fetchMe, checkSlack } from "../redux/settings"
98

109
import Main from "./Main"
1110

1211
export default function Settings(): JSX.Element {
13-
const { user, rateLimit, isSlackEnabled } = useAppSelector(state => state.settings, shallowEqual)
12+
const { user, isSlackEnabled } = useAppSelector(state => state.settings, shallowEqual)
1413
const dispatch = useAppDispatch()
1514

1615
useEffect(() => {
1716
dispatch(fetchMe())
18-
dispatch(fetchRateLimit())
1917
dispatch(checkSlack())
2018
}, [dispatch])
2119

@@ -27,30 +25,33 @@ export default function Settings(): JSX.Element {
2725
<Helmet>
2826
<title>Settings</title>
2927
</Helmet>
30-
<div >
31-
<h1>Settings</h1>
32-
</div>
33-
<div style={{marginTop: "40px"}}>
34-
<h2>User</h2>
35-
<p>
36-
Login: <Avatar src={user?.avatar}/> <b>{user?.login}</b>
37-
</p>
38-
<p>
39-
Role: {(user?.admin)? <Tag color="purple">Admin</Tag> : <Tag color="purple">Member</Tag>}
40-
</p>
41-
</div>
42-
<div style={{marginTop: "40px"}}>
43-
<h2>Rate Limit</h2>
44-
<p>Limit: {rateLimit?.limit}</p>
45-
<p>Remaining: {rateLimit?.remaining}</p>
46-
<p>Reset: {moment(rateLimit?.reset).fromNow()}</p>
47-
</div>
28+
<h1>Settings</h1>
29+
<Descriptions title="User Info" column={2} style={{marginTop: "40px"}} layout="vertical">
30+
<Descriptions.Item label="Login">{user?.login}</Descriptions.Item>
31+
<Descriptions.Item label="Role">
32+
{(user?.admin)?
33+
<Tag color="purple">Admin</Tag>
34+
:
35+
<Tag color="purple">Member</Tag>}
36+
</Descriptions.Item>
37+
<Descriptions.Item label="Token">
38+
<Input.Password
39+
value={user?.hash}
40+
style={{width: 200, padding:0 }}
41+
readOnly
42+
bordered={false}
43+
/>
44+
</Descriptions.Item>
45+
</Descriptions>
4846
{(isSlackEnabled)?
4947
<div style={{marginTop: "40px", marginBottom: "20px"}}>
50-
<h2>Slack</h2>
51-
{(connected)?
52-
<Button href="/slack/signout" type="primary" danger>DISCONNECTED</Button>:
53-
<Button href="/slack/" type="primary">CONNECT</Button>}
48+
<Descriptions title="Slack">
49+
<Descriptions.Item>
50+
{(connected)?
51+
<Button href="/slack/signout" type="primary" danger>DISCONNECTED</Button>:
52+
<Button href="/slack/" type="primary">CONNECT</Button>}
53+
</Descriptions.Item>
54+
</Descriptions>
5455
</div>:
5556
null}
5657
</Main>

0 commit comments

Comments
 (0)