Skip to content

Commit

Permalink
Add default message priority for applications
Browse files Browse the repository at this point in the history
Co-authored-by: Jannis Mattheis <contact@jmattheis.de>
  • Loading branch information
chrispruitt and jmattheis committed Jul 19, 2023
1 parent aedc3e2 commit 72bd8c8
Show file tree
Hide file tree
Showing 18 changed files with 210 additions and 35 deletions.
16 changes: 11 additions & 5 deletions api/application.go
Expand Up @@ -44,6 +44,10 @@ type ApplicationParams struct {
//
// example: Backup server for the interwebs
Description string `form:"description" query:"description" json:"description"`
// The default priority of messages sent by this application. Defaults to 0.
//
// example: 5
DefaultPriority int `form:"defaultPriority" query:"defaultPriority" json:"defaultPriority"`
}

// CreateApplication creates an application and returns the access token.
Expand Down Expand Up @@ -83,11 +87,12 @@ func (a *ApplicationAPI) CreateApplication(ctx *gin.Context) {
applicationParams := ApplicationParams{}
if err := ctx.Bind(&applicationParams); err == nil {
app := model.Application{
Name: applicationParams.Name,
Description: applicationParams.Description,
Token: auth.GenerateNotExistingToken(generateApplicationToken, a.applicationExists),
UserID: auth.GetUserID(ctx),
Internal: false,
Name: applicationParams.Name,
Description: applicationParams.Description,
DefaultPriority: applicationParams.DefaultPriority,
Token: auth.GenerateNotExistingToken(generateApplicationToken, a.applicationExists),
UserID: auth.GetUserID(ctx),
Internal: false,
}

if success := successOrAbort(ctx, 500, a.DB.CreateApplication(&app)); !success {
Expand Down Expand Up @@ -245,6 +250,7 @@ func (a *ApplicationAPI) UpdateApplication(ctx *gin.Context) {
if err := ctx.Bind(&applicationParams); err == nil {
app.Description = applicationParams.Description
app.Name = applicationParams.Name
app.DefaultPriority = applicationParams.DefaultPriority

if success := successOrAbort(ctx, 500, a.DB.UpdateApplication(app)); !success {
return
Expand Down
25 changes: 24 additions & 1 deletion api/application_test.go
Expand Up @@ -92,7 +92,7 @@ func (s *ApplicationSuite) Test_ensureApplicationHasCorrectJsonRepresentation()
Image: "asd",
Internal: true,
}
test.JSONEquals(s.T(), actual, `{"id":1,"token":"Aasdasfgeeg","name":"myapp","description":"mydesc", "image": "asd", "internal":true}`)
test.JSONEquals(s.T(), actual, `{"id":1,"token":"Aasdasfgeeg","name":"myapp","description":"mydesc", "image": "asd", "internal":true, "defaultPriority":0}`)
}

func (s *ApplicationSuite) Test_CreateApplication_expectBadRequestOnEmptyName() {
Expand Down Expand Up @@ -527,6 +527,29 @@ func (s *ApplicationSuite) Test_UpdateApplicationName_expectSuccess() {
}
}

func (s *ApplicationSuite) Test_UpdateApplicationDefaultPriority_expectSuccess() {
s.db.User(5).NewAppWithToken(2, "app-2")

test.WithUser(s.ctx, 5)
s.withFormData("name=name&description=&defaultPriority=4")
s.ctx.Params = gin.Params{{Key: "id", Value: "2"}}
s.a.UpdateApplication(s.ctx)

expected := &model.Application{
ID: 2,
Token: "app-2",
UserID: 5,
Name: "name",
Description: "",
DefaultPriority: 4,
}

assert.Equal(s.T(), 200, s.recorder.Code)
if app, err := s.db.GetApplicationByID(2); assert.NoError(s.T(), err) {
assert.Equal(s.T(), expected, app)
}
}

func (s *ApplicationSuite) Test_UpdateApplication_preservesImage() {
app := s.db.User(5).NewAppWithToken(2, "app-2")
app.Image = "existing.png"
Expand Down
12 changes: 10 additions & 2 deletions api/message.go
Expand Up @@ -371,6 +371,11 @@ func (a *MessageAPI) CreateMessage(ctx *gin.Context) {
if strings.TrimSpace(message.Title) == "" {
message.Title = application.Name
}

if message.Priority == nil {
message.Priority = &application.DefaultPriority
}

message.Date = timeNow()
message.ID = 0
msgInternal := toInternalMessage(&message)
Expand All @@ -388,9 +393,12 @@ func toInternalMessage(msg *model.MessageExternal) *model.Message {
ApplicationID: msg.ApplicationID,
Message: msg.Message,
Title: msg.Title,
Priority: msg.Priority,
Date: msg.Date,
}
if msg.Priority != nil {
res.Priority = *msg.Priority
}

if msg.Extras != nil {
res.Extras, _ = json.Marshal(msg.Extras)
}
Expand All @@ -403,7 +411,7 @@ func toExternalMessage(msg *model.Message) *model.MessageExternal {
ApplicationID: msg.ApplicationID,
Message: msg.Message,
Title: msg.Title,
Priority: msg.Priority,
Priority: &msg.Priority,
Date: msg.Date,
}
if len(msg.Extras) != 0 {
Expand Down
37 changes: 32 additions & 5 deletions api/message_test.go
Expand Up @@ -53,7 +53,7 @@ func (s *MessageSuite) Test_ensureCorrectJsonRepresentation() {

actual := &model.PagedMessages{
Paging: model.Paging{Limit: 5, Since: 122, Size: 5, Next: "http://example.com/message?limit=5&since=122"},
Messages: []*model.MessageExternal{{ID: 55, ApplicationID: 2, Message: "hi", Title: "hi", Date: t, Priority: 4, Extras: map[string]interface{}{
Messages: []*model.MessageExternal{{ID: 55, ApplicationID: 2, Message: "hi", Title: "hi", Date: t, Priority: intPtr(4), Extras: map[string]interface{}{
"test::string": "string",
"test::array": []interface{}{1, 2, 3},
"test::int": 1,
Expand Down Expand Up @@ -331,7 +331,29 @@ func (s *MessageSuite) Test_CreateMessage_onJson_allParams() {

msgs, err := s.db.GetMessagesByApplication(7)
assert.NoError(s.T(), err)
expected := &model.MessageExternal{ID: 1, ApplicationID: 7, Title: "mytitle", Message: "mymessage", Priority: 1, Date: t}
expected := &model.MessageExternal{ID: 1, ApplicationID: 7, Title: "mytitle", Message: "mymessage", Priority: intPtr(1), Date: t}
assert.Len(s.T(), msgs, 1)
assert.Equal(s.T(), expected, toExternalMessage(msgs[0]))
assert.Equal(s.T(), 200, s.recorder.Code)
assert.Equal(s.T(), expected, s.notifiedMessage)
}

func (s *MessageSuite) Test_CreateMessage_WithDefaultPriority() {
t, _ := time.Parse("2006/01/02", "2017/01/02")

timeNow = func() time.Time { return t }
defer func() { timeNow = time.Now }()

auth.RegisterAuthentication(s.ctx, nil, 4, "app-token")
s.db.User(4).AppWithTokenAndDefaultPriority(8, "app-token", 5)
s.ctx.Request = httptest.NewRequest("POST", "/message", strings.NewReader(`{"title": "mytitle", "message": "mymessage"}`))
s.ctx.Request.Header.Set("Content-Type", "application/json")

s.a.CreateMessage(s.ctx)

msgs, err := s.db.GetMessagesByApplication(8)
assert.NoError(s.T(), err)
expected := &model.MessageExternal{ID: 1, ApplicationID: 8, Title: "mytitle", Message: "mymessage", Priority: intPtr(5), Date: t}
assert.Len(s.T(), msgs, 1)
assert.Equal(s.T(), expected, toExternalMessage(msgs[0]))
assert.Equal(s.T(), 200, s.recorder.Code)
Expand All @@ -352,7 +374,7 @@ func (s *MessageSuite) Test_CreateMessage_WithTitle() {

msgs, err := s.db.GetMessagesByApplication(5)
assert.NoError(s.T(), err)
expected := &model.MessageExternal{ID: 1, ApplicationID: 5, Title: "mytitle", Message: "mymessage", Date: t}
expected := &model.MessageExternal{ID: 1, ApplicationID: 5, Title: "mytitle", Message: "mymessage", Date: t, Priority: intPtr(0)}
assert.Len(s.T(), msgs, 1)
assert.Equal(s.T(), expected, toExternalMessage(msgs[0]))
assert.Equal(s.T(), 200, s.recorder.Code)
Expand Down Expand Up @@ -446,6 +468,7 @@ func (s *MessageSuite) Test_CreateMessage_WithExtras() {
Message: "mymessage",
Title: "msg with extras",
Date: t,
Priority: intPtr(0),
Extras: map[string]interface{}{
"gotify::test": map[string]interface{}{
"string": "test",
Expand Down Expand Up @@ -492,7 +515,7 @@ func (s *MessageSuite) Test_CreateMessage_onQueryData() {

s.a.CreateMessage(s.ctx)

expected := &model.MessageExternal{ID: 1, ApplicationID: 2, Title: "mytitle", Message: "mymessage", Priority: 1, Date: t}
expected := &model.MessageExternal{ID: 1, ApplicationID: 2, Title: "mytitle", Message: "mymessage", Priority: intPtr(1), Date: t}

msgs, err := s.db.GetMessagesByApplication(2)
assert.NoError(s.T(), err)
Expand All @@ -515,7 +538,7 @@ func (s *MessageSuite) Test_CreateMessage_onFormData() {

s.a.CreateMessage(s.ctx)

expected := &model.MessageExternal{ID: 1, ApplicationID: 99, Title: "mytitle", Message: "mymessage", Priority: 1, Date: t}
expected := &model.MessageExternal{ID: 1, ApplicationID: 99, Title: "mytitle", Message: "mymessage", Priority: intPtr(1), Date: t}
msgs, err := s.db.GetMessagesByApplication(99)
assert.NoError(s.T(), err)
assert.Len(s.T(), msgs, 1)
Expand All @@ -528,3 +551,7 @@ func (s *MessageSuite) withURL(scheme, host, path, query string) {
s.ctx.Request.URL = &url.URL{Path: path, RawQuery: query}
s.ctx.Set("location", &url.URL{Scheme: scheme, Host: host})
}

func intPtr(x int) *int {
return &x
}
16 changes: 15 additions & 1 deletion docs/spec.json
Expand Up @@ -2063,6 +2063,13 @@
"image"
],
"properties": {
"defaultPriority": {
"description": "The default priority of messages sent by this application. Defaults to 0.",
"type": "integer",
"format": "int64",
"x-go-name": "DefaultPriority",
"example": 4
},
"description": {
"description": "The description of the application.",
"type": "string",
Expand Down Expand Up @@ -2115,6 +2122,13 @@
"name"
],
"properties": {
"defaultPriority": {
"description": "The default priority of messages sent by this application. Defaults to 0.",
"type": "integer",
"format": "int64",
"x-go-name": "DefaultPriority",
"example": 5
},
"description": {
"description": "The description of the application.",
"type": "string",
Expand Down Expand Up @@ -2326,7 +2340,7 @@
"example": "**Backup** was successfully finished."
},
"priority": {
"description": "The priority of the message.",
"description": "The priority of the message. If unset, then the default priority of the\napplication will be used.",
"type": "integer",
"format": "int64",
"x-go-name": "Priority",
Expand Down
5 changes: 5 additions & 0 deletions model/application.go
Expand Up @@ -42,4 +42,9 @@ type Application struct {
// example: image/image.jpeg
Image string `gorm:"type:text" json:"image"`
Messages []MessageExternal `json:"-"`
// The default priority of messages sent by this application. Defaults to 0.
//
// required: false
// example: 4
DefaultPriority int `form:"defaultPriority" query:"defaultPriority" json:"defaultPriority"`
}
5 changes: 3 additions & 2 deletions model/message.go
Expand Up @@ -42,10 +42,11 @@ type MessageExternal struct {
//
// example: Backup
Title string `form:"title" query:"title" json:"title"`
// The priority of the message.
// The priority of the message. If unset, then the default priority of the
// application will be used.
//
// example: 2
Priority int `form:"priority" query:"priority" json:"priority"`
Priority *int `form:"priority" query:"priority" json:"priority"`
// The extra data sent along the message.
//
// The extra fields are stored in a key-value scheme. Only accepted in CreateMessage requests with application/json content-type.
Expand Down
2 changes: 1 addition & 1 deletion plugin/manager.go
Expand Up @@ -70,7 +70,7 @@ func NewManager(db Database, directory string, mux *gin.RouterGroup, notifier No
internalMsg := &model.Message{
ApplicationID: message.Message.ApplicationID,
Title: message.Message.Title,
Priority: message.Message.Priority,
Priority: *message.Message.Priority,
Date: message.Message.Date,
Message: message.Message.Message,
}
Expand Down
2 changes: 1 addition & 1 deletion plugin/messagehandler.go
Expand Up @@ -26,7 +26,7 @@ func (c redirectToChannel) SendMessage(msg compat.Message) error {
ApplicationID: c.ApplicationID,
Message: msg.Message,
Title: msg.Title,
Priority: msg.Priority,
Priority: &msg.Priority,
Date: time.Now(),
Extras: msg.Extras,
},
Expand Down
7 changes: 7 additions & 0 deletions test/testdb/database.go
Expand Up @@ -138,6 +138,13 @@ func (ab *AppClientBuilder) newAppWithTokenAndName(id uint, token, name string,
return application
}

// AppWithTokenAndDefaultPriority creates an application with a token and defaultPriority and returns a message builder.
func (ab *AppClientBuilder) AppWithTokenAndDefaultPriority(id uint, token string, defaultPriority int) *MessageBuilder {
application := &model.Application{ID: id, UserID: ab.userID, Token: token, DefaultPriority: defaultPriority}
ab.db.CreateApplication(application)
return &MessageBuilder{db: ab.db, appID: id}
}

// Client creates a client and returns itself.
func (ab *AppClientBuilder) Client(id uint) *AppClientBuilder {
return ab.ClientWithToken(id, "client"+fmt.Sprint(id))
Expand Down
2 changes: 2 additions & 0 deletions test/testdb/database_test.go
Expand Up @@ -127,6 +127,7 @@ func (s *DatabaseSuite) Test_Apps() {
userBuilder.InternalAppWithTokenAndName(10, "test-tokeni-2", "app name")
userBuilder.AppWithToken(11, "test-token-3")
userBuilder.InternalAppWithToken(12, "test-tokeni-3")
userBuilder.AppWithTokenAndDefaultPriority(13, "test-tokeni-4", 4)

s.db.AssertAppExist(1)
s.db.AssertAppExist(2)
Expand All @@ -140,6 +141,7 @@ func (s *DatabaseSuite) Test_Apps() {
s.db.AssertAppExist(10)
s.db.AssertAppExist(11)
s.db.AssertAppExist(12)
s.db.AssertAppExist(13)

s.db.DeleteApplicationByID(2)

Expand Down
18 changes: 14 additions & 4 deletions ui/src/application/AddApplicationDialog.tsx
Expand Up @@ -6,27 +6,29 @@ import DialogContentText from '@material-ui/core/DialogContentText';
import DialogTitle from '@material-ui/core/DialogTitle';
import TextField from '@material-ui/core/TextField';
import Tooltip from '@material-ui/core/Tooltip';
import {NumberField} from '../common/NumberField';
import React, {Component} from 'react';

interface IProps {
fClose: VoidFunction;
fOnSubmit: (name: string, description: string) => void;
fOnSubmit: (name: string, description: string, defaultPriority: number) => void;
}

interface IState {
name: string;
description: string;
defaultPriority: number;
}

export default class AddDialog extends Component<IProps, IState> {
public state = {name: '', description: ''};
public state = {name: '', description: '', defaultPriority: 0};

public render() {
const {fClose, fOnSubmit} = this.props;
const {name, description} = this.state;
const {name, description, defaultPriority} = this.state;
const submitEnabled = this.state.name.length !== 0;
const submitAndClose = () => {
fOnSubmit(name, description);
fOnSubmit(name, description, defaultPriority);
fClose();
};
return (
Expand Down Expand Up @@ -59,6 +61,14 @@ export default class AddDialog extends Component<IProps, IState> {
fullWidth
multiline
/>
<NumberField
margin="dense"
className="priority"
label="Default Priority"
value={defaultPriority}
onChange={(value) => this.setState({defaultPriority: value})}
fullWidth
/>
</DialogContent>
<DialogActions>
<Button onClick={fClose}>Cancel</Button>
Expand Down
25 changes: 21 additions & 4 deletions ui/src/application/AppStore.ts
Expand Up @@ -35,15 +35,32 @@ export class AppStore extends BaseStore<IApplication> {
};

@action
public update = async (id: number, name: string, description: string): Promise<void> => {
await axios.put(`${config.get('url')}application/${id}`, {name, description});
public update = async (
id: number,
name: string,
description: string,
defaultPriority: number
): Promise<void> => {
await axios.put(`${config.get('url')}application/${id}`, {
name,
description,
defaultPriority,
});
await this.refresh();
this.snack('Application updated');
};

@action
public create = async (name: string, description: string): Promise<void> => {
await axios.post(`${config.get('url')}application`, {name, description});
public create = async (
name: string,
description: string,
defaultPriority: number
): Promise<void> => {
await axios.post(`${config.get('url')}application`, {
name,
description,
defaultPriority,
});
await this.refresh();
this.snack('Application created');
};
Expand Down

0 comments on commit 72bd8c8

Please sign in to comment.