add extras to message model
This commit is contained in:
parent
98710db507
commit
de09aae987
|
|
@ -1,6 +1,7 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
|
@ -30,7 +31,7 @@ var timeNow = time.Now
|
|||
|
||||
// Notifier notifies when a new message was created.
|
||||
type Notifier interface {
|
||||
Notify(userID uint, message *model.Message)
|
||||
Notify(userID uint, message *model.MessageExternal)
|
||||
}
|
||||
|
||||
// The MessageAPI provides handlers for managing messages.
|
||||
|
|
@ -110,7 +111,7 @@ func buildWithPaging(ctx *gin.Context, paging *pagingParams, messages []*model.M
|
|||
}
|
||||
return &model.PagedMessages{
|
||||
Paging: model.Paging{Size: len(useMessages), Limit: paging.Limit, Next: next, Since: since},
|
||||
Messages: useMessages,
|
||||
Messages: toExternalMessages(useMessages),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -329,7 +330,7 @@ func (a *MessageAPI) DeleteMessage(ctx *gin.Context) {
|
|||
// schema:
|
||||
// $ref: "#/definitions/Error"
|
||||
func (a *MessageAPI) CreateMessage(ctx *gin.Context) {
|
||||
message := model.Message{}
|
||||
message := model.MessageExternal{}
|
||||
if err := ctx.Bind(&message); err == nil {
|
||||
application := a.DB.GetApplicationByToken(auth.GetTokenID(ctx))
|
||||
message.ApplicationID = application.ID
|
||||
|
|
@ -337,8 +338,48 @@ func (a *MessageAPI) CreateMessage(ctx *gin.Context) {
|
|||
message.Title = application.Name
|
||||
}
|
||||
message.Date = timeNow()
|
||||
a.DB.CreateMessage(&message)
|
||||
a.Notifier.Notify(auth.GetUserID(ctx), &message)
|
||||
ctx.JSON(200, message)
|
||||
msgInternal := toInternalMessage(&message)
|
||||
a.DB.CreateMessage(msgInternal)
|
||||
a.Notifier.Notify(auth.GetUserID(ctx), toExternalMessage(msgInternal))
|
||||
ctx.JSON(200, toExternalMessage(msgInternal))
|
||||
}
|
||||
}
|
||||
|
||||
func toInternalMessage(msg *model.MessageExternal) *model.Message {
|
||||
res := &model.Message{
|
||||
ID: msg.ID,
|
||||
ApplicationID: msg.ApplicationID,
|
||||
Message: msg.Message,
|
||||
Title: msg.Title,
|
||||
Priority: msg.Priority,
|
||||
Date: msg.Date,
|
||||
}
|
||||
if msg.Extras != nil {
|
||||
res.Extras, _ = json.Marshal(msg.Extras)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func toExternalMessage(msg *model.Message) *model.MessageExternal {
|
||||
res := &model.MessageExternal{
|
||||
ID: msg.ID,
|
||||
ApplicationID: msg.ApplicationID,
|
||||
Message: msg.Message,
|
||||
Title: msg.Title,
|
||||
Priority: msg.Priority,
|
||||
Date: msg.Date,
|
||||
}
|
||||
if len(msg.Extras) != 0 {
|
||||
res.Extras = make(map[string]interface{})
|
||||
json.Unmarshal(msg.Extras, &res.Extras)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func toExternalMessages(msg []*model.Message) []*model.MessageExternal {
|
||||
res := make([]*model.MessageExternal, len(msg))
|
||||
for i := range msg {
|
||||
res[i] = toExternalMessage(msg[i])
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ type MessageSuite struct {
|
|||
a *MessageAPI
|
||||
ctx *gin.Context
|
||||
recorder *httptest.ResponseRecorder
|
||||
notified bool
|
||||
notifiedMessage *model.MessageExternal
|
||||
}
|
||||
|
||||
func (s *MessageSuite) BeforeTest(suiteName, testName string) {
|
||||
|
|
@ -35,7 +35,7 @@ func (s *MessageSuite) BeforeTest(suiteName, testName string) {
|
|||
s.ctx, _ = gin.CreateTestContext(s.recorder)
|
||||
s.ctx.Request = httptest.NewRequest("GET", "/irrelevant", nil)
|
||||
s.db = test.NewDB(s.T())
|
||||
s.notified = false
|
||||
s.notifiedMessage = nil
|
||||
s.a = &MessageAPI{DB: s.db, Notifier: s}
|
||||
}
|
||||
|
||||
|
|
@ -43,8 +43,8 @@ func (s *MessageSuite) AfterTest(string, string) {
|
|||
s.db.Close()
|
||||
}
|
||||
|
||||
func (s *MessageSuite) Notify(userID uint, msg *model.Message) {
|
||||
s.notified = true
|
||||
func (s *MessageSuite) Notify(userID uint, msg *model.MessageExternal) {
|
||||
s.notifiedMessage = msg
|
||||
}
|
||||
|
||||
func (s *MessageSuite) Test_ensureCorrectJsonRepresentation() {
|
||||
|
|
@ -52,23 +52,30 @@ 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.Message{{ID: 55, ApplicationID: 2, Message: "hi", Title: "hi", Date: t, Priority: 4}},
|
||||
Messages: []*model.MessageExternal{{ID: 55, ApplicationID: 2, Message: "hi", Title: "hi", Date: t, Priority: 4, Extras: map[string]interface{}{
|
||||
"test::string": "string",
|
||||
"test::array": []interface{}{1, 2, 3},
|
||||
"test::int": 1,
|
||||
"test::float": 0.5,
|
||||
}}},
|
||||
}
|
||||
test.JSONEquals(s.T(), actual, `{"paging": {"limit":5, "since": 122, "size": 5, "next": "http://example.com/message?limit=5&since=122"},
|
||||
"messages": [{"id":55,"appid":2,"message":"hi","title":"hi","priority":4,"date":"2017-01-02T00:00:00Z"}]}`)
|
||||
"messages": [{"id":55,"appid":2,"message":"hi","title":"hi","priority":4,"date":"2017-01-02T00:00:00Z","extras":{"test::string":"string","test::array":[1,2,3],"test::int":1,"test::float":0.5}}]}`)
|
||||
}
|
||||
|
||||
func (s *MessageSuite) Test_GetMessages() {
|
||||
user := s.db.User(5)
|
||||
first := user.App(1).NewMessage(1)
|
||||
second := user.App(2).NewMessage(2)
|
||||
firstExternal := toExternalMessage(&first)
|
||||
secondExternal := toExternalMessage(&second)
|
||||
|
||||
test.WithUser(s.ctx, 5)
|
||||
s.a.GetMessages(s.ctx)
|
||||
|
||||
expected := &model.PagedMessages{
|
||||
Paging: model.Paging{Limit: 100, Size: 2, Next: ""},
|
||||
Messages: []*model.Message{&second, &first},
|
||||
Messages: []*model.MessageExternal{secondExternal, firstExternal},
|
||||
}
|
||||
|
||||
test.BodyEquals(s.T(), expected, s.recorder)
|
||||
|
|
@ -92,7 +99,7 @@ func (s *MessageSuite) Test_GetMessages_WithLimit_ReturnsNext() {
|
|||
// Since: entries with ids from 100 - 96 will be returned (5 entries)
|
||||
expected := &model.PagedMessages{
|
||||
Paging: model.Paging{Limit: 5, Size: 5, Since: 96, Next: "http://example.com/messages?limit=5&since=96"},
|
||||
Messages: messages[:5],
|
||||
Messages: toExternalMessages(messages[:5]),
|
||||
}
|
||||
|
||||
test.BodyEquals(s.T(), expected, s.recorder)
|
||||
|
|
@ -116,7 +123,7 @@ func (s *MessageSuite) Test_GetMessages_WithLimit_WithSince_ReturnsNext() {
|
|||
// Since: entries with ids from 54 - 42 will be returned (13 entries)
|
||||
expected := &model.PagedMessages{
|
||||
Paging: model.Paging{Limit: 13, Size: 13, Since: 42, Next: "http://example.com/messages?limit=13&since=42"},
|
||||
Messages: messages[46 : 46+13],
|
||||
Messages: toExternalMessages(messages[46 : 46+13]),
|
||||
}
|
||||
test.BodyEquals(s.T(), expected, s.recorder)
|
||||
}
|
||||
|
|
@ -159,7 +166,7 @@ func (s *MessageSuite) Test_GetMessagesWithToken() {
|
|||
|
||||
expected := &model.PagedMessages{
|
||||
Paging: model.Paging{Limit: 100, Size: 1, Next: ""},
|
||||
Messages: []*model.Message{&msg},
|
||||
Messages: toExternalMessages([]*model.Message{&msg}),
|
||||
}
|
||||
|
||||
test.BodyEquals(s.T(), expected, s.recorder)
|
||||
|
|
@ -182,7 +189,7 @@ func (s *MessageSuite) Test_GetMessagesWithToken_WithLimit_ReturnsNext() {
|
|||
// Since: entries with ids from 100 - 92 will be returned (9 entries)
|
||||
expected := &model.PagedMessages{
|
||||
Paging: model.Paging{Limit: 9, Size: 9, Since: 92, Next: "http://example.com/app/2/message?limit=9&since=92"},
|
||||
Messages: messages[:9],
|
||||
Messages: toExternalMessages(messages[:9]),
|
||||
}
|
||||
|
||||
test.BodyEquals(s.T(), expected, s.recorder)
|
||||
|
|
@ -205,7 +212,7 @@ func (s *MessageSuite) Test_GetMessagesWithToken_WithLimit_WithSince_ReturnsNext
|
|||
// Since: entries with ids from 54 - 42 will be returned (13 entries)
|
||||
expected := &model.PagedMessages{
|
||||
Paging: model.Paging{Limit: 13, Size: 13, Since: 42, Next: "http://example.com/app/2/message?limit=13&since=42"},
|
||||
Messages: messages[46 : 46+13],
|
||||
Messages: toExternalMessages(messages[46 : 46+13]),
|
||||
}
|
||||
test.BodyEquals(s.T(), expected, s.recorder)
|
||||
}
|
||||
|
|
@ -316,17 +323,17 @@ func (s *MessageSuite) Test_CreateMessage_onJson_allParams() {
|
|||
|
||||
auth.RegisterAuthentication(s.ctx, nil, 4, "app-token")
|
||||
s.db.User(4).AppWithToken(7, "app-token")
|
||||
s.ctx.Request = httptest.NewRequest("POST", "/token", strings.NewReader(`{"title": "mytitle", "message": "mymessage", "priority": 1}`))
|
||||
s.ctx.Request = httptest.NewRequest("POST", "/message", strings.NewReader(`{"title": "mytitle", "message": "mymessage", "priority": 1}`))
|
||||
s.ctx.Request.Header.Set("Content-Type", "application/json")
|
||||
|
||||
s.a.CreateMessage(s.ctx)
|
||||
|
||||
msgs := s.db.GetMessagesByApplication(7)
|
||||
expected := &model.Message{ID: 1, ApplicationID: 7, Title: "mytitle", Message: "mymessage", Priority: 1, Date: t}
|
||||
expected := &model.MessageExternal{ID: 1, ApplicationID: 7, Title: "mytitle", Message: "mymessage", Priority: 1, Date: t}
|
||||
assert.Len(s.T(), msgs, 1)
|
||||
assert.Contains(s.T(), msgs, expected)
|
||||
assert.Equal(s.T(), expected, toExternalMessage(msgs[0]))
|
||||
assert.Equal(s.T(), 200, s.recorder.Code)
|
||||
assert.True(s.T(), s.notified)
|
||||
assert.Equal(s.T(), expected, s.notifiedMessage)
|
||||
}
|
||||
|
||||
func (s *MessageSuite) Test_CreateMessage_WithTitle() {
|
||||
|
|
@ -336,38 +343,38 @@ func (s *MessageSuite) Test_CreateMessage_WithTitle() {
|
|||
|
||||
auth.RegisterAuthentication(s.ctx, nil, 4, "app-token")
|
||||
s.db.User(4).AppWithToken(5, "app-token")
|
||||
s.ctx.Request = httptest.NewRequest("POST", "/token", strings.NewReader(`{"title": "mytitle", "message": "mymessage"}`))
|
||||
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 := s.db.GetMessagesByApplication(5)
|
||||
expected := &model.Message{ID: 1, ApplicationID: 5, Title: "mytitle", Message: "mymessage", Date: t}
|
||||
expected := &model.MessageExternal{ID: 1, ApplicationID: 5, Title: "mytitle", Message: "mymessage", Date: t}
|
||||
assert.Len(s.T(), msgs, 1)
|
||||
assert.Contains(s.T(), msgs, expected)
|
||||
assert.Equal(s.T(), expected, toExternalMessage(msgs[0]))
|
||||
assert.Equal(s.T(), 200, s.recorder.Code)
|
||||
assert.True(s.T(), s.notified)
|
||||
assert.Equal(s.T(), expected, s.notifiedMessage)
|
||||
}
|
||||
|
||||
func (s *MessageSuite) Test_CreateMessage_failWhenNoMessage() {
|
||||
auth.RegisterAuthentication(s.ctx, nil, 4, "app-token")
|
||||
s.db.User(4).AppWithToken(1, "app-token")
|
||||
|
||||
s.ctx.Request = httptest.NewRequest("POST", "/token", strings.NewReader(`{"title": "mytitle"}`))
|
||||
s.ctx.Request = httptest.NewRequest("POST", "/message", strings.NewReader(`{"title": "mytitle"}`))
|
||||
s.ctx.Request.Header.Set("Content-Type", "application/json")
|
||||
|
||||
s.a.CreateMessage(s.ctx)
|
||||
|
||||
assert.Empty(s.T(), s.db.GetMessagesByApplication(1))
|
||||
assert.Equal(s.T(), 400, s.recorder.Code)
|
||||
assert.False(s.T(), s.notified)
|
||||
assert.Nil(s.T(), s.notifiedMessage)
|
||||
}
|
||||
|
||||
func (s *MessageSuite) Test_CreateMessage_WithoutTitle() {
|
||||
auth.RegisterAuthentication(s.ctx, nil, 4, "app-token")
|
||||
s.db.User(4).AppWithTokenAndName(8, "app-token", "Application name")
|
||||
|
||||
s.ctx.Request = httptest.NewRequest("POST", "/token", strings.NewReader(`{"message": "mymessage"}`))
|
||||
s.ctx.Request = httptest.NewRequest("POST", "/message", strings.NewReader(`{"message": "mymessage"}`))
|
||||
s.ctx.Request.Header.Set("Content-Type", "application/json")
|
||||
|
||||
s.a.CreateMessage(s.ctx)
|
||||
|
|
@ -376,14 +383,14 @@ func (s *MessageSuite) Test_CreateMessage_WithoutTitle() {
|
|||
assert.Len(s.T(), msgs, 1)
|
||||
assert.Equal(s.T(), "Application name", msgs[0].Title)
|
||||
assert.Equal(s.T(), 200, s.recorder.Code)
|
||||
assert.True(s.T(), s.notified)
|
||||
assert.Equal(s.T(), "mymessage", s.notifiedMessage.Message)
|
||||
}
|
||||
|
||||
func (s *MessageSuite) Test_CreateMessage_WithBlankTitle() {
|
||||
auth.RegisterAuthentication(s.ctx, nil, 4, "app-token")
|
||||
s.db.User(4).AppWithTokenAndName(8, "app-token", "Application name")
|
||||
|
||||
s.ctx.Request = httptest.NewRequest("POST", "/token", strings.NewReader(`{"message": "mymessage", "title": " "}`))
|
||||
s.ctx.Request = httptest.NewRequest("POST", "/message", strings.NewReader(`{"message": "mymessage", "title": " "}`))
|
||||
s.ctx.Request.Header.Set("Content-Type", "application/json")
|
||||
|
||||
s.a.CreateMessage(s.ctx)
|
||||
|
|
@ -392,20 +399,56 @@ func (s *MessageSuite) Test_CreateMessage_WithBlankTitle() {
|
|||
assert.Len(s.T(), msgs, 1)
|
||||
assert.Equal(s.T(), "Application name", msgs[0].Title)
|
||||
assert.Equal(s.T(), 200, s.recorder.Code)
|
||||
assert.True(s.T(), s.notified)
|
||||
assert.Equal(s.T(), "mymessage", msgs[0].Message)
|
||||
}
|
||||
func (s *MessageSuite) Test_CreateMessage_WithExtras() {
|
||||
auth.RegisterAuthentication(s.ctx, nil, 4, "app-token")
|
||||
s.db.User(4).AppWithTokenAndName(8, "app-token", "Application name")
|
||||
|
||||
t, _ := time.Parse("2006/01/02", "2017/01/02")
|
||||
timeNow = func() time.Time { return t }
|
||||
defer func() { timeNow = time.Now }()
|
||||
|
||||
s.ctx.Request = httptest.NewRequest("POST", "/message", strings.NewReader(`{"message": "mymessage", "title": "msg with extras", "extras": {"gotify::test":{"int":1,"float":0.5,"string":"test","array":[1,2,3]}}}`))
|
||||
s.ctx.Request.Header.Set("Content-Type", "application/json")
|
||||
|
||||
s.a.CreateMessage(s.ctx)
|
||||
|
||||
msgs := s.db.GetMessagesByApplication(8)
|
||||
expected := &model.MessageExternal{
|
||||
ID: 1,
|
||||
ApplicationID: 8,
|
||||
Message: "mymessage",
|
||||
Title: "msg with extras",
|
||||
Date: t,
|
||||
Extras: map[string]interface{}{
|
||||
"gotify::test": map[string]interface{}{
|
||||
"string": "test",
|
||||
"array": []interface{}{float64(1), float64(2), float64(3)},
|
||||
"int": float64(1),
|
||||
"float": float64(0.5),
|
||||
},
|
||||
},
|
||||
}
|
||||
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(), uint(1), s.notifiedMessage.ID)
|
||||
}
|
||||
|
||||
func (s *MessageSuite) Test_CreateMessage_failWhenPriorityNotNumber() {
|
||||
auth.RegisterAuthentication(s.ctx, nil, 4, "app-token")
|
||||
s.db.User(4).AppWithToken(8, "app-token")
|
||||
|
||||
s.ctx.Request = httptest.NewRequest("POST", "/token", strings.NewReader(`{"title": "mytitle", "message": "mymessage", "priority": "asd"}`))
|
||||
s.ctx.Request = httptest.NewRequest("POST", "/message", strings.NewReader(`{"title": "mytitle", "message": "mymessage", "priority": "asd"}`))
|
||||
s.ctx.Request.Header.Set("Content-Type", "application/json")
|
||||
|
||||
s.a.CreateMessage(s.ctx)
|
||||
|
||||
assert.Equal(s.T(), 400, s.recorder.Code)
|
||||
assert.False(s.T(), s.notified)
|
||||
assert.Nil(s.T(), s.notifiedMessage)
|
||||
assert.Empty(s.T(), s.db.GetMessagesByApplication(1))
|
||||
}
|
||||
|
||||
|
|
@ -417,20 +460,19 @@ func (s *MessageSuite) Test_CreateMessage_onQueryData() {
|
|||
timeNow = func() time.Time { return t }
|
||||
defer func() { timeNow = time.Now }()
|
||||
|
||||
s.ctx.Request = httptest.NewRequest("POST", "/token?title=mytitle&message=mymessage&priority=1", nil)
|
||||
s.ctx.Request = httptest.NewRequest("POST", "/message?title=mytitle&message=mymessage&priority=1", nil)
|
||||
s.ctx.Request.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
s.a.CreateMessage(s.ctx)
|
||||
|
||||
expected := &model.Message{ID: 1, ApplicationID: 2, Title: "mytitle", Message: "mymessage", Priority: 1, Date: t}
|
||||
expected := &model.MessageExternal{ID: 1, ApplicationID: 2, Title: "mytitle", Message: "mymessage", Priority: 1, Date: t}
|
||||
|
||||
msgs := s.db.GetMessagesByApplication(2)
|
||||
assert.Len(s.T(), msgs, 1)
|
||||
assert.Contains(s.T(), msgs, expected)
|
||||
assert.Equal(s.T(), expected, toExternalMessage(msgs[0]))
|
||||
assert.Equal(s.T(), 200, s.recorder.Code)
|
||||
assert.True(s.T(), s.notified)
|
||||
assert.Equal(s.T(), uint(1), s.notifiedMessage.ID)
|
||||
}
|
||||
|
||||
func (s *MessageSuite) Test_CreateMessage_onFormData() {
|
||||
auth.RegisterAuthentication(s.ctx, nil, 4, "app-token")
|
||||
s.db.User(4).AppWithToken(99, "app-token")
|
||||
|
|
@ -439,17 +481,17 @@ func (s *MessageSuite) Test_CreateMessage_onFormData() {
|
|||
timeNow = func() time.Time { return t }
|
||||
defer func() { timeNow = time.Now }()
|
||||
|
||||
s.ctx.Request = httptest.NewRequest("POST", "/token", strings.NewReader("title=mytitle&message=mymessage&priority=1"))
|
||||
s.ctx.Request = httptest.NewRequest("POST", "/message", strings.NewReader(`title=mytitle&message=mymessage&priority=1`))
|
||||
s.ctx.Request.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
s.a.CreateMessage(s.ctx)
|
||||
|
||||
expected := &model.Message{ID: 1, ApplicationID: 99, Title: "mytitle", Message: "mymessage", Priority: 1, Date: t}
|
||||
expected := &model.MessageExternal{ID: 1, ApplicationID: 99, Title: "mytitle", Message: "mymessage", Priority: 1, Date: t}
|
||||
msgs := s.db.GetMessagesByApplication(99)
|
||||
assert.Len(s.T(), msgs, 1)
|
||||
assert.Contains(s.T(), msgs, expected)
|
||||
assert.Equal(s.T(), expected, toExternalMessage(msgs[0]))
|
||||
assert.Equal(s.T(), 200, s.recorder.Code)
|
||||
assert.True(s.T(), s.notified)
|
||||
assert.Equal(s.T(), uint(1), s.notifiedMessage.ID)
|
||||
}
|
||||
|
||||
func (s *MessageSuite) withURL(scheme, host, path, query string) {
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ var writeJSON = func(conn *websocket.Conn, v interface{}) error {
|
|||
type client struct {
|
||||
conn *websocket.Conn
|
||||
onClose func(*client)
|
||||
write chan *model.Message
|
||||
write chan *model.MessageExternal
|
||||
userID uint
|
||||
token string
|
||||
once once
|
||||
|
|
@ -31,7 +31,7 @@ type client struct {
|
|||
func newClient(conn *websocket.Conn, userID uint, token string, onClose func(*client)) *client {
|
||||
return &client{
|
||||
conn: conn,
|
||||
write: make(chan *model.Message, 1),
|
||||
write: make(chan *model.MessageExternal, 1),
|
||||
userID: userID,
|
||||
token: token,
|
||||
onClose: onClose,
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ func (a *API) NotifyDeletedClient(userID uint, token string) {
|
|||
}
|
||||
|
||||
// Notify notifies the clients with the given userID that a new messages was created.
|
||||
func (a *API) Notify(userID uint, msg *model.Message) {
|
||||
func (a *API) Notify(userID uint, msg *model.MessageExternal) {
|
||||
a.lock.RLock()
|
||||
defer a.lock.RUnlock()
|
||||
if clients, ok := a.clients[userID]; ok {
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ func TestWriteMessageFails(t *testing.T) {
|
|||
clients := clients(api, 1)
|
||||
assert.NotEmpty(t, clients)
|
||||
|
||||
api.Notify(1, &model.Message{Message: "HI"})
|
||||
api.Notify(1, &model.MessageExternal{Message: "HI"})
|
||||
user.expectNoMessage()
|
||||
}
|
||||
|
||||
|
|
@ -94,7 +94,7 @@ func TestWritePingFails(t *testing.T) {
|
|||
|
||||
time.Sleep(5 * time.Second) // waiting for ping
|
||||
|
||||
api.Notify(1, &model.Message{Message: "HI"})
|
||||
api.Notify(1, &model.MessageExternal{Message: "HI"})
|
||||
user.expectNoMessage()
|
||||
}
|
||||
|
||||
|
|
@ -130,8 +130,8 @@ func TestPing(t *testing.T) {
|
|||
}
|
||||
|
||||
expectNoMessage(user)
|
||||
api.Notify(1, &model.Message{Message: "HI"})
|
||||
user.expectMessage(&model.Message{Message: "HI"})
|
||||
api.Notify(1, &model.MessageExternal{Message: "HI"})
|
||||
user.expectMessage(&model.MessageExternal{Message: "HI"})
|
||||
}
|
||||
|
||||
func TestCloseClientOnNotReading(t *testing.T) {
|
||||
|
|
@ -169,8 +169,8 @@ func TestMessageDirectlyAfterConnect(t *testing.T) {
|
|||
defer user.conn.Close()
|
||||
// the server may take some time to register the client
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
api.Notify(1, &model.Message{Message: "msg"})
|
||||
user.expectMessage(&model.Message{Message: "msg"})
|
||||
api.Notify(1, &model.MessageExternal{Message: "msg"})
|
||||
user.expectMessage(&model.MessageExternal{Message: "msg"})
|
||||
}
|
||||
|
||||
func TestDeleteClientShouldCloseConnection(t *testing.T) {
|
||||
|
|
@ -186,12 +186,12 @@ func TestDeleteClientShouldCloseConnection(t *testing.T) {
|
|||
defer user.conn.Close()
|
||||
// the server may take some time to register the client
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
api.Notify(1, &model.Message{Message: "msg"})
|
||||
user.expectMessage(&model.Message{Message: "msg"})
|
||||
api.Notify(1, &model.MessageExternal{Message: "msg"})
|
||||
user.expectMessage(&model.MessageExternal{Message: "msg"})
|
||||
|
||||
api.NotifyDeletedClient(1, "customtoken")
|
||||
|
||||
api.Notify(1, &model.Message{Message: "msg"})
|
||||
api.Notify(1, &model.MessageExternal{Message: "msg"})
|
||||
user.expectNoMessage()
|
||||
}
|
||||
|
||||
|
|
@ -233,28 +233,28 @@ func TestDeleteMultipleClients(t *testing.T) {
|
|||
// the server may take some time to register the client
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
api.Notify(1, &model.Message{ID: 4, Message: "there"})
|
||||
expectMessage(&model.Message{ID: 4, Message: "there"}, userOne...)
|
||||
api.Notify(1, &model.MessageExternal{ID: 4, Message: "there"})
|
||||
expectMessage(&model.MessageExternal{ID: 4, Message: "there"}, userOne...)
|
||||
expectNoMessage(userTwo...)
|
||||
expectNoMessage(userThree...)
|
||||
|
||||
api.NotifyDeletedClient(1, "1-2")
|
||||
|
||||
api.Notify(1, &model.Message{ID: 2, Message: "there"})
|
||||
expectMessage(&model.Message{ID: 2, Message: "there"}, userOneIPhone, userOneOther)
|
||||
api.Notify(1, &model.MessageExternal{ID: 2, Message: "there"})
|
||||
expectMessage(&model.MessageExternal{ID: 2, Message: "there"}, userOneIPhone, userOneOther)
|
||||
expectNoMessage(userOneBrowser, userOneAndroid)
|
||||
expectNoMessage(userThree...)
|
||||
expectNoMessage(userTwo...)
|
||||
|
||||
api.Notify(2, &model.Message{ID: 2, Message: "there"})
|
||||
api.Notify(2, &model.MessageExternal{ID: 2, Message: "there"})
|
||||
expectNoMessage(userOne...)
|
||||
expectMessage(&model.Message{ID: 2, Message: "there"}, userTwo...)
|
||||
expectMessage(&model.MessageExternal{ID: 2, Message: "there"}, userTwo...)
|
||||
expectNoMessage(userThree...)
|
||||
|
||||
api.Notify(3, &model.Message{ID: 5, Message: "there"})
|
||||
api.Notify(3, &model.MessageExternal{ID: 5, Message: "there"})
|
||||
expectNoMessage(userOne...)
|
||||
expectNoMessage(userTwo...)
|
||||
expectMessage(&model.Message{ID: 5, Message: "there"}, userThree...)
|
||||
expectMessage(&model.MessageExternal{ID: 5, Message: "there"}, userThree...)
|
||||
|
||||
api.Close()
|
||||
}
|
||||
|
|
@ -297,27 +297,27 @@ func TestDeleteUser(t *testing.T) {
|
|||
// the server may take some time to register the client
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
api.Notify(1, &model.Message{ID: 4, Message: "there"})
|
||||
expectMessage(&model.Message{ID: 4, Message: "there"}, userOne...)
|
||||
api.Notify(1, &model.MessageExternal{ID: 4, Message: "there"})
|
||||
expectMessage(&model.MessageExternal{ID: 4, Message: "there"}, userOne...)
|
||||
expectNoMessage(userTwo...)
|
||||
expectNoMessage(userThree...)
|
||||
|
||||
api.NotifyDeletedUser(1)
|
||||
|
||||
api.Notify(1, &model.Message{ID: 2, Message: "there"})
|
||||
api.Notify(1, &model.MessageExternal{ID: 2, Message: "there"})
|
||||
expectNoMessage(userOne...)
|
||||
expectNoMessage(userThree...)
|
||||
expectNoMessage(userTwo...)
|
||||
|
||||
api.Notify(2, &model.Message{ID: 2, Message: "there"})
|
||||
api.Notify(2, &model.MessageExternal{ID: 2, Message: "there"})
|
||||
expectNoMessage(userOne...)
|
||||
expectMessage(&model.Message{ID: 2, Message: "there"}, userTwo...)
|
||||
expectMessage(&model.MessageExternal{ID: 2, Message: "there"}, userTwo...)
|
||||
expectNoMessage(userThree...)
|
||||
|
||||
api.Notify(3, &model.Message{ID: 5, Message: "there"})
|
||||
api.Notify(3, &model.MessageExternal{ID: 5, Message: "there"})
|
||||
expectNoMessage(userOne...)
|
||||
expectNoMessage(userTwo...)
|
||||
expectMessage(&model.Message{ID: 5, Message: "there"}, userThree...)
|
||||
expectMessage(&model.MessageExternal{ID: 5, Message: "there"}, userThree...)
|
||||
|
||||
api.Close()
|
||||
}
|
||||
|
|
@ -362,15 +362,15 @@ func TestMultipleClients(t *testing.T) {
|
|||
expectNoMessage(userTwo...)
|
||||
expectNoMessage(userThree...)
|
||||
|
||||
api.Notify(1, &model.Message{ID: 1, Message: "hello"})
|
||||
api.Notify(1, &model.MessageExternal{ID: 1, Message: "hello"})
|
||||
time.Sleep(1 * time.Second)
|
||||
expectMessage(&model.Message{ID: 1, Message: "hello"}, userOne...)
|
||||
expectMessage(&model.MessageExternal{ID: 1, Message: "hello"}, userOne...)
|
||||
expectNoMessage(userTwo...)
|
||||
expectNoMessage(userThree...)
|
||||
|
||||
api.Notify(2, &model.Message{ID: 2, Message: "there"})
|
||||
api.Notify(2, &model.MessageExternal{ID: 2, Message: "there"})
|
||||
expectNoMessage(userOne...)
|
||||
expectMessage(&model.Message{ID: 2, Message: "there"}, userTwo...)
|
||||
expectMessage(&model.MessageExternal{ID: 2, Message: "there"}, userTwo...)
|
||||
expectNoMessage(userThree...)
|
||||
|
||||
userOneIPhone.conn.Close()
|
||||
|
|
@ -379,21 +379,21 @@ func TestMultipleClients(t *testing.T) {
|
|||
expectNoMessage(userTwo...)
|
||||
expectNoMessage(userThree...)
|
||||
|
||||
api.Notify(1, &model.Message{ID: 3, Message: "how"})
|
||||
expectMessage(&model.Message{ID: 3, Message: "how"}, userOneAndroid, userOneBrowser)
|
||||
api.Notify(1, &model.MessageExternal{ID: 3, Message: "how"})
|
||||
expectMessage(&model.MessageExternal{ID: 3, Message: "how"}, userOneAndroid, userOneBrowser)
|
||||
expectNoMessage(userOneIPhone)
|
||||
expectNoMessage(userTwo...)
|
||||
expectNoMessage(userThree...)
|
||||
|
||||
api.Notify(2, &model.Message{ID: 4, Message: "are"})
|
||||
api.Notify(2, &model.MessageExternal{ID: 4, Message: "are"})
|
||||
|
||||
expectNoMessage(userOne...)
|
||||
expectMessage(&model.Message{ID: 4, Message: "are"}, userTwo...)
|
||||
expectMessage(&model.MessageExternal{ID: 4, Message: "are"}, userTwo...)
|
||||
expectNoMessage(userThree...)
|
||||
|
||||
api.Close()
|
||||
|
||||
api.Notify(2, &model.Message{ID: 5, Message: "you"})
|
||||
api.Notify(2, &model.MessageExternal{ID: 5, Message: "you"})
|
||||
|
||||
expectNoMessage(userOne...)
|
||||
expectNoMessage(userTwo...)
|
||||
|
|
@ -481,7 +481,7 @@ func startReading(client *testingClient) {
|
|||
return
|
||||
}
|
||||
|
||||
actual := &model.Message{}
|
||||
actual := &model.MessageExternal{}
|
||||
json.NewDecoder(bytes.NewBuffer(payload)).Decode(actual)
|
||||
client.readMessage <- *actual
|
||||
}
|
||||
|
|
@ -492,18 +492,18 @@ func createClient(t *testing.T, url string) *testingClient {
|
|||
ws, _, err := websocket.DefaultDialer.Dial(url, nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
readMessages := make(chan model.Message)
|
||||
readMessages := make(chan model.MessageExternal)
|
||||
|
||||
return &testingClient{conn: ws, readMessage: readMessages, t: t}
|
||||
}
|
||||
|
||||
type testingClient struct {
|
||||
conn *websocket.Conn
|
||||
readMessage chan model.Message
|
||||
readMessage chan model.MessageExternal
|
||||
t *testing.T
|
||||
}
|
||||
|
||||
func (c *testingClient) expectMessage(expected *model.Message) {
|
||||
func (c *testingClient) expectMessage(expected *model.MessageExternal) {
|
||||
select {
|
||||
case <-time.After(50 * time.Millisecond):
|
||||
assert.Fail(c.t, "Expected message but none was send :(")
|
||||
|
|
@ -512,7 +512,7 @@ func (c *testingClient) expectMessage(expected *model.Message) {
|
|||
}
|
||||
}
|
||||
|
||||
func expectMessage(expected *model.Message, clients ...*testingClient) {
|
||||
func expectMessage(expected *model.MessageExternal, clients ...*testingClient) {
|
||||
for _, client := range clients {
|
||||
client.expectMessage(expected)
|
||||
}
|
||||
|
|
|
|||
18
api/user.go
18
api/user.go
|
|
@ -54,7 +54,7 @@ func (a *UserAPI) GetUsers(ctx *gin.Context) {
|
|||
|
||||
var resp []*model.UserExternal
|
||||
for _, user := range users {
|
||||
resp = append(resp, toExternal(user))
|
||||
resp = append(resp, toExternalUser(user))
|
||||
}
|
||||
|
||||
ctx.JSON(200, resp)
|
||||
|
|
@ -83,7 +83,7 @@ func (a *UserAPI) GetUsers(ctx *gin.Context) {
|
|||
// $ref: "#/definitions/Error"
|
||||
func (a *UserAPI) GetCurrentUser(ctx *gin.Context) {
|
||||
user := a.DB.GetUserByID(auth.GetUserID(ctx))
|
||||
ctx.JSON(200, toExternal(user))
|
||||
ctx.JSON(200, toExternalUser(user))
|
||||
}
|
||||
|
||||
// CreateUser creates a user
|
||||
|
|
@ -122,10 +122,10 @@ func (a *UserAPI) GetCurrentUser(ctx *gin.Context) {
|
|||
func (a *UserAPI) CreateUser(ctx *gin.Context) {
|
||||
user := model.UserExternalWithPass{}
|
||||
if err := ctx.Bind(&user); err == nil {
|
||||
internal := a.toInternal(&user, []byte{})
|
||||
internal := a.toInternalUser(&user, []byte{})
|
||||
if a.DB.GetUserByName(internal.Name) == nil {
|
||||
a.DB.CreateUser(internal)
|
||||
ctx.JSON(200, toExternal(internal))
|
||||
ctx.JSON(200, toExternalUser(internal))
|
||||
} else {
|
||||
ctx.AbortWithError(400, errors.New("username already exists"))
|
||||
}
|
||||
|
|
@ -171,7 +171,7 @@ func (a *UserAPI) CreateUser(ctx *gin.Context) {
|
|||
func (a *UserAPI) GetUserByID(ctx *gin.Context) {
|
||||
withID(ctx, "id", func(id uint) {
|
||||
if user := a.DB.GetUserByID(uint(id)); user != nil {
|
||||
ctx.JSON(200, toExternal(user))
|
||||
ctx.JSON(200, toExternalUser(user))
|
||||
} else {
|
||||
ctx.AbortWithError(404, errors.New("user does not exist"))
|
||||
}
|
||||
|
|
@ -309,10 +309,10 @@ func (a *UserAPI) UpdateUserByID(ctx *gin.Context) {
|
|||
var user *model.UserExternalWithPass
|
||||
if err := ctx.Bind(&user); err == nil {
|
||||
if oldUser := a.DB.GetUserByID(id); oldUser != nil {
|
||||
internal := a.toInternal(user, oldUser.Pass)
|
||||
internal := a.toInternalUser(user, oldUser.Pass)
|
||||
internal.ID = id
|
||||
a.DB.UpdateUser(internal)
|
||||
ctx.JSON(200, toExternal(internal))
|
||||
ctx.JSON(200, toExternalUser(internal))
|
||||
} else {
|
||||
ctx.AbortWithError(404, errors.New("user does not exist"))
|
||||
}
|
||||
|
|
@ -320,7 +320,7 @@ func (a *UserAPI) UpdateUserByID(ctx *gin.Context) {
|
|||
})
|
||||
}
|
||||
|
||||
func (a *UserAPI) toInternal(response *model.UserExternalWithPass, pw []byte) *model.User {
|
||||
func (a *UserAPI) toInternalUser(response *model.UserExternalWithPass, pw []byte) *model.User {
|
||||
user := &model.User{
|
||||
Name: response.Name,
|
||||
Admin: response.Admin,
|
||||
|
|
@ -333,7 +333,7 @@ func (a *UserAPI) toInternal(response *model.UserExternalWithPass, pw []byte) *m
|
|||
return user
|
||||
}
|
||||
|
||||
func toExternal(internal *model.User) *model.UserExternal {
|
||||
func toExternalUser(internal *model.User) *model.UserExternal {
|
||||
return &model.UserExternal{
|
||||
Name: internal.Name,
|
||||
Admin: internal.Admin,
|
||||
|
|
|
|||
|
|
@ -1490,9 +1490,9 @@
|
|||
"x-go-package": "github.com/gotify/server/model"
|
||||
},
|
||||
"Message": {
|
||||
"description": "The Message holds information about a message which was sent by an Application.",
|
||||
"description": "The MessageExternal holds information about a message which was sent by an Application.",
|
||||
"type": "object",
|
||||
"title": "Message Model",
|
||||
"title": "MessageExternal Model",
|
||||
"required": [
|
||||
"id",
|
||||
"appid",
|
||||
|
|
@ -1516,6 +1516,22 @@
|
|||
"readOnly": true,
|
||||
"example": "2018-02-27T19:36:10.5045044+01:00"
|
||||
},
|
||||
"extras": {
|
||||
"description": "The extra data sent along the message.\n\nThe extra fields are stored in a key-value scheme. Only accepted in CreateMessage requests with application/json content-type.\n\nThe keys should be in the following format: \u0026lt;top-namespace\u0026gt;::[\u0026lt;sub-namespace\u0026gt;::]\u0026lt;action\u0026gt;\n\nThese namespaces are reserved and might be used in the official clients: gotify android ios web server client. Do not use them for other purposes.",
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "object"
|
||||
},
|
||||
"x-go-name": "Extras",
|
||||
"example": {
|
||||
"home::appliances::lighting::on": {
|
||||
"brightness": 15
|
||||
},
|
||||
"home::appliances::thermostat::change_temperature": {
|
||||
"temperature": 23
|
||||
}
|
||||
}
|
||||
},
|
||||
"id": {
|
||||
"description": "The message id.",
|
||||
"type": "integer",
|
||||
|
|
@ -1544,6 +1560,7 @@
|
|||
"example": "Backup"
|
||||
}
|
||||
},
|
||||
"x-go-name": "MessageExternal",
|
||||
"x-go-package": "github.com/gotify/server/model"
|
||||
},
|
||||
"PagedMessages": {
|
||||
|
|
|
|||
|
|
@ -35,5 +35,5 @@ type Application struct {
|
|||
// required: true
|
||||
// example: https://example.com/image.jpeg
|
||||
Image string `json:"image"`
|
||||
Messages []Message `json:"-"`
|
||||
Messages []MessageExternal `json:"-"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,32 @@
|
|||
package model
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Message Model
|
||||
// Message holds information about a message
|
||||
type Message struct {
|
||||
ID uint `gorm:"AUTO_INCREMENT;primary_key;index"`
|
||||
ApplicationID uint
|
||||
Message string
|
||||
Title string
|
||||
Priority int
|
||||
Extras []byte
|
||||
Date time.Time
|
||||
}
|
||||
|
||||
// MessageExternal Model
|
||||
//
|
||||
// The Message holds information about a message which was sent by an Application.
|
||||
// The MessageExternal holds information about a message which was sent by an Application.
|
||||
//
|
||||
// swagger:model Message
|
||||
type Message struct {
|
||||
type MessageExternal struct {
|
||||
// The message id.
|
||||
//
|
||||
// read only: true
|
||||
// required: true
|
||||
// example: 25
|
||||
ID uint `gorm:"AUTO_INCREMENT;primary_key;index" json:"id"`
|
||||
ID uint `json:"id"`
|
||||
// The application id that send this message.
|
||||
//
|
||||
// read only: true
|
||||
|
|
@ -33,6 +46,16 @@ type Message struct {
|
|||
//
|
||||
// example: 2
|
||||
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.
|
||||
//
|
||||
// The keys should be in the following format: <top-namespace>::[<sub-namespace>::]<action>
|
||||
//
|
||||
// These namespaces are reserved and might be used in the official clients: gotify android ios web server client. Do not use them for other purposes.
|
||||
//
|
||||
// example: {"home::appliances::thermostat::change_temperature":{"temperature":23},"home::appliances::lighting::on":{"brightness":15}}
|
||||
Extras map[string]interface{} `form:"-" query:"-" json:"extras,omitempty"`
|
||||
// The date the message was created.
|
||||
//
|
||||
// read only: true
|
||||
|
|
|
|||
|
|
@ -50,5 +50,5 @@ type PagedMessages struct {
|
|||
//
|
||||
// read only: true
|
||||
// required: true
|
||||
Messages []*Message `json:"messages"`
|
||||
Messages []*MessageExternal `json:"messages"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -102,7 +102,11 @@ describe('Messages', () => {
|
|||
}
|
||||
return result;
|
||||
};
|
||||
const m = (title: string, message: string) => ({title, message});
|
||||
const m = (title: string, message: string, extras?: IMessageExtras) => ({
|
||||
title,
|
||||
message,
|
||||
extras,
|
||||
});
|
||||
|
||||
const windows1 = m('Login', 'User jmattheis logged in.');
|
||||
const windows2 = m('Shutdown', 'Windows will be shut down.');
|
||||
|
|
|
|||
|
|
@ -20,6 +20,11 @@ interface IMessage {
|
|||
priority: number;
|
||||
date: string;
|
||||
image?: string;
|
||||
extras?: IMessageExtras;
|
||||
}
|
||||
|
||||
interface IMessageExtras {
|
||||
[key: string]: any; // tslint:disable-line no-any
|
||||
}
|
||||
|
||||
interface IPagedMessages {
|
||||
|
|
|
|||
Loading…
Reference in New Issue