Skip to content

Commit e32359e

Browse files
GianOrtizjmattheis
authored andcommitted
Add update client api and dialog (#164)
1 parent efcf4ad commit e32359e

File tree

11 files changed

+318
-5
lines changed

11 files changed

+318
-5
lines changed

api/client.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ type ClientDatabase interface {
1515
GetClientByID(id uint) *model.Client
1616
GetClientsByUser(userID uint) []*model.Client
1717
DeleteClientByID(id uint) error
18+
UpdateClient(client *model.Client) error
1819
}
1920

2021
// The ClientAPI provides handlers for managing clients and applications.
@@ -24,6 +25,65 @@ type ClientAPI struct {
2425
NotifyDeleted func(uint, string)
2526
}
2627

28+
// UpdateClient updates a client by its id.
29+
// swagger:operation PUT /client/{id} client updateClient
30+
//
31+
// Update a client.
32+
//
33+
// ---
34+
// consumes: [application/json]
35+
// produces: [application/json]
36+
// security: [clientTokenHeader: [], clientTokenQuery: [], basicAuth: []]
37+
// parameters:
38+
// - name: body
39+
// in: body
40+
// description: the client to update
41+
// required: true
42+
// schema:
43+
// $ref: "#/definitions/Client"
44+
// - name: id
45+
// in: path
46+
// description: the client id
47+
// required: true
48+
// type: integer
49+
// responses:
50+
// 200:
51+
// description: Ok
52+
// schema:
53+
// $ref: "#/definitions/Client"
54+
// 400:
55+
// description: Bad Request
56+
// schema:
57+
// $ref: "#/definitions/Error"
58+
// 401:
59+
// description: Unauthorized
60+
// schema:
61+
// $ref: "#/definitions/Error"
62+
// 403:
63+
// description: Forbidden
64+
// schema:
65+
// $ref: "#/definitions/Error"
66+
// 404:
67+
// description: Not Found
68+
// schema:
69+
// $ref: "#/definitions/Error"
70+
func (a *ClientAPI) UpdateClient(ctx *gin.Context) {
71+
withID(ctx, "id", func(id uint) {
72+
if client := a.DB.GetClientByID(id); client != nil && client.UserID == auth.GetUserID(ctx) {
73+
newValues := &model.Client{}
74+
if err := ctx.Bind(newValues); err == nil {
75+
client.Name = newValues.Name
76+
77+
a.DB.UpdateClient(client)
78+
79+
ctx.JSON(200, client)
80+
}
81+
} else {
82+
ctx.AbortWithError(404, fmt.Errorf("client with id %d doesn't exists", id))
83+
}
84+
})
85+
}
86+
2787
// CreateClient creates a client and returns the access token.
2888
// swagger:operation POST /client client createClient
2989
//

api/client_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,40 @@ func (s *ClientSuite) Test_DeleteClient() {
168168
assert.True(s.T(), s.notified)
169169
}
170170

171+
func (s *ClientSuite) Test_UpdateClient_expectSuccess() {
172+
s.db.User(5).NewClientWithToken(1, firstClientToken)
173+
174+
test.WithUser(s.ctx, 5)
175+
s.withFormData("name=firefox")
176+
s.ctx.Params = gin.Params{{Key: "id", Value: "1"}}
177+
s.a.UpdateClient(s.ctx)
178+
179+
expected := &model.Client{
180+
ID: 1,
181+
Token: firstClientToken,
182+
UserID: 5,
183+
Name: "firefox",
184+
}
185+
186+
assert.Equal(s.T(), 200, s.recorder.Code)
187+
assert.Equal(s.T(), expected, s.db.GetClientByID(1))
188+
}
189+
190+
func (s *ClientSuite) Test_UpdateClient_expectNotFound() {
191+
test.WithUser(s.ctx, 5)
192+
s.ctx.Params = gin.Params{{Key: "id", Value: "2"}}
193+
s.a.UpdateClient(s.ctx)
194+
195+
assert.Equal(s.T(), 404, s.recorder.Code)
196+
}
197+
198+
func (s *ClientSuite) Test_UpdateClient_WithMissingAttributes_expectBadRequest() {
199+
test.WithUser(s.ctx, 5)
200+
s.a.UpdateClient(s.ctx)
201+
202+
assert.Equal(s.T(), 400, s.recorder.Code)
203+
}
204+
171205
func (s *ClientSuite) withFormData(formData string) {
172206
s.ctx.Request = httptest.NewRequest("POST", "/token", strings.NewReader(formData))
173207
s.ctx.Request.Header.Set("Content-Type", "application/x-www-form-urlencoded")

database/client.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,8 @@ func (d *GormDatabase) GetClientsByUser(userID uint) []*model.Client {
3838
func (d *GormDatabase) DeleteClientByID(id uint) error {
3939
return d.DB.Where("id = ?", id).Delete(&model.Client{}).Error
4040
}
41+
42+
// UpdateClient updates a client.
43+
func (d *GormDatabase) UpdateClient(client *model.Client) error {
44+
return d.DB.Save(client).Error
45+
}

database/client_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ func (s *DatabaseSuite) TestClient() {
2929
newClient = s.db.GetClientByToken(client.Token)
3030
assert.Equal(s.T(), client, newClient)
3131

32+
updateClient := &model.Client{ID: client.ID, UserID: user.ID, Token: "C0000000000", Name: "new_name"}
33+
s.db.UpdateClient(updateClient)
34+
updatedClient := s.db.GetClientByID(client.ID)
35+
assert.Equal(s.T(), updateClient, updatedClient)
36+
3237
s.db.DeleteClientByID(client.ID)
3338

3439
clients = s.db.GetClientsByUser(user.ID)

docs/package.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
//
1717
// Schemes: http, https
1818
// Host: localhost
19-
// Version: 2.0.0
19+
// Version: 2.0.1
2020
// License: MIT https://github.com/gotify/server/blob/master/LICENSE
2121
//
2222
// Consumes:

docs/spec.json

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"name": "MIT",
1818
"url": "https://github.com/gotify/server/blob/master/LICENSE"
1919
},
20-
"version": "2.0.0"
20+
"version": "2.0.1"
2121
},
2222
"host": "localhost",
2323
"paths": {
@@ -599,6 +599,80 @@
599599
}
600600
},
601601
"/client/{id}": {
602+
"put": {
603+
"security": [
604+
{
605+
"clientTokenHeader": []
606+
},
607+
{
608+
"clientTokenQuery": []
609+
},
610+
{
611+
"basicAuth": []
612+
}
613+
],
614+
"consumes": [
615+
"application/json"
616+
],
617+
"produces": [
618+
"application/json"
619+
],
620+
"tags": [
621+
"client"
622+
],
623+
"summary": "Update a client.",
624+
"operationId": "updateClient",
625+
"parameters": [
626+
{
627+
"description": "the client to update",
628+
"name": "body",
629+
"in": "body",
630+
"required": true,
631+
"schema": {
632+
"$ref": "#/definitions/Client"
633+
}
634+
},
635+
{
636+
"type": "integer",
637+
"description": "the client id",
638+
"name": "id",
639+
"in": "path",
640+
"required": true
641+
}
642+
],
643+
"responses": {
644+
"200": {
645+
"description": "Ok",
646+
"schema": {
647+
"$ref": "#/definitions/Client"
648+
}
649+
},
650+
"400": {
651+
"description": "Bad Request",
652+
"schema": {
653+
"$ref": "#/definitions/Error"
654+
}
655+
},
656+
"401": {
657+
"description": "Unauthorized",
658+
"schema": {
659+
"$ref": "#/definitions/Error"
660+
}
661+
},
662+
"403": {
663+
"description": "Forbidden",
664+
"schema": {
665+
"$ref": "#/definitions/Error"
666+
}
667+
},
668+
"404": {
669+
"description": "Not Found",
670+
"schema": {
671+
"$ref": "#/definitions/Error"
672+
}
673+
}
674+
}
675+
},
602676
"delete": {
603677
"security": [
604678
{

router/router.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,8 @@ func Create(db *database.GormDatabase, vInfo *model.VersionInfo, conf *config.Co
139139
client.POST("", clientHandler.CreateClient)
140140

141141
client.DELETE("/:id", clientHandler.DeleteClient)
142+
143+
client.PUT("/:id", clientHandler.UpdateClient)
142144
}
143145

144146
message := clientAuth.Group("/message")

ui/src/client/ClientStore.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@ export class ClientStore extends BaseStore<IClient> {
1919
.then(() => this.snack('Client deleted'));
2020
}
2121

22+
@action
23+
public update = async (id: number, name: string): Promise<void> => {
24+
await axios.put(`${config.get('url')}client/${id}`, {name});
25+
await this.refresh();
26+
this.snack('Client updated');
27+
};
28+
2229
@action
2330
public createNoNotifcation = async (name: string): Promise<IClient> => {
2431
const client = await axios.post(`${config.get('url')}client`, {name});

ui/src/client/Clients.tsx

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ import TableCell from '@material-ui/core/TableCell';
77
import TableHead from '@material-ui/core/TableHead';
88
import TableRow from '@material-ui/core/TableRow';
99
import Delete from '@material-ui/icons/Delete';
10+
import Edit from '@material-ui/icons/Edit';
1011
import React, {Component, SFC} from 'react';
1112
import ConfirmDialog from '../common/ConfirmDialog';
1213
import DefaultPage from '../common/DefaultPage';
1314
import ToggleVisibility from '../common/ToggleVisibility';
1415
import AddClientDialog from './AddClientDialog';
16+
import UpdateDialog from './UpdateClientDialog';
1517
import {observer} from 'mobx-react';
1618
import {observable} from 'mobx';
1719
import {inject, Stores} from '../inject';
@@ -22,12 +24,15 @@ class Clients extends Component<Stores<'clientStore'>> {
2224
private showDialog = false;
2325
@observable
2426
private deleteId: false | number = false;
27+
@observable
28+
private updateId: false | number = false;
2529

2630
public componentDidMount = () => this.props.clientStore.refresh();
2731

2832
public render() {
2933
const {
3034
deleteId,
35+
updateId,
3136
showDialog,
3237
props: {clientStore},
3338
} = this;
@@ -47,6 +52,7 @@ class Clients extends Component<Stores<'clientStore'>> {
4752
<TableCell>Name</TableCell>
4853
<TableCell style={{width: 200}}>token</TableCell>
4954
<TableCell />
55+
<TableCell />
5056
</TableRow>
5157
</TableHead>
5258
<TableBody>
@@ -56,6 +62,7 @@ class Clients extends Component<Stores<'clientStore'>> {
5662
key={client.id}
5763
name={client.name}
5864
value={client.token}
65+
fEdit={() => (this.updateId = client.id)}
5966
fDelete={() => (this.deleteId = client.id)}
6067
/>
6168
);
@@ -70,6 +77,13 @@ class Clients extends Component<Stores<'clientStore'>> {
7077
fOnSubmit={clientStore.create}
7178
/>
7279
)}
80+
{updateId !== false && (
81+
<UpdateDialog
82+
fClose={() => (this.updateId = false)}
83+
fOnSubmit={(name) => clientStore.update(updateId, name)}
84+
initialName={clientStore.getByID(updateId).name}
85+
/>
86+
)}
7387
{deleteId !== false && (
7488
<ConfirmDialog
7589
title="Confirm Delete"
@@ -86,10 +100,11 @@ class Clients extends Component<Stores<'clientStore'>> {
86100
interface IRowProps {
87101
name: string;
88102
value: string;
103+
fEdit: VoidFunction;
89104
fDelete: VoidFunction;
90105
}
91106

92-
const Row: SFC<IRowProps> = ({name, value, fDelete}) => (
107+
const Row: SFC<IRowProps> = ({name, value, fEdit, fDelete}) => (
93108
<TableRow>
94109
<TableCell>{name}</TableCell>
95110
<TableCell>
@@ -99,6 +114,11 @@ const Row: SFC<IRowProps> = ({name, value, fDelete}) => (
99114
/>
100115
</TableCell>
101116
<TableCell numeric padding="none">
117+
<IconButton onClick={fEdit} className="edit">
118+
<Edit />
119+
</IconButton>
120+
</TableCell>
121+
<TableCell>
102122
<IconButton onClick={fDelete} className="delete">
103123
<Delete />
104124
</IconButton>

0 commit comments

Comments
 (0)