notifier changes/fixes - mobile push notifications

pull/94/head
Hunter Long 2018-11-01 15:37:20 +01:00
parent 16b076aadd
commit e35904d35c
14 changed files with 321 additions and 69 deletions

View File

@ -1,4 +1,4 @@
VERSION=0.78
VERSION=0.79
BINARY_NAME=statup
GOPATH:=$(GOPATH)
GOCMD=go
@ -15,9 +15,6 @@ TRAVIS_BUILD_CMD='{ "request": { "branch": "master", "message": "Compile master
TEST_DIR=$(GOPATH)/src/github.com/hunterlong/statup
PATH:=$(PATH)
# build and compile Statup and then test
all: dev-deps compile install test-all docker-build-all
# build all arch's and release Statup
release: dev-deps build-all

View File

@ -101,20 +101,20 @@ func (n *ExampleNotifier) Select() *Notification {
// OnSave is a required basic event for the Notifier interface
func (n *ExampleNotifier) OnSave() error {
msg := fmt.Sprintf("received on save trigger")
n.AddQueue(msg)
n.AddQueue(0, msg)
return errors.New("onsave triggered")
}
// OnSuccess is a required basic event for the Notifier interface
func (n *ExampleNotifier) OnSuccess(s *types.Service) {
msg := fmt.Sprintf("received a count trigger for service: %v\n", s.Name)
n.AddQueue(msg)
n.AddQueue(s.Id, msg)
}
// OnFailure is a required basic event for the Notifier interface
func (n *ExampleNotifier) OnFailure(s *types.Service, f *types.Failure) {
msg := fmt.Sprintf("received a failure trigger for service: %v\n", s.Name)
n.AddQueue(msg)
n.AddQueue(s.Id, msg)
}
// OnTest is a option testing event for the Notifier interface
@ -126,61 +126,61 @@ func (n *ExampleNotifier) OnTest() error {
// OnNewService is a option event for new services
func (n *ExampleNotifier) OnNewService(s *types.Service) {
msg := fmt.Sprintf("received a new service trigger for service: %v\n", s.Name)
n.AddQueue(msg)
n.AddQueue(s.Id, msg)
}
// OnUpdatedService is a option event for updated services
func (n *ExampleNotifier) OnUpdatedService(s *types.Service) {
msg := fmt.Sprintf("received a update service trigger for service: %v\n", s.Name)
n.AddQueue(msg)
n.AddQueue(s.Id, msg)
}
// OnDeletedService is a option event for deleted services
func (n *ExampleNotifier) OnDeletedService(s *types.Service) {
msg := fmt.Sprintf("received a delete service trigger for service: %v\n", s.Name)
n.AddQueue(msg)
n.AddQueue(s.Id, msg)
}
// OnNewUser is a option event for new users
func (n *ExampleNotifier) OnNewUser(s *types.User) {
msg := fmt.Sprintf("received a new user trigger for user: %v\n", s.Username)
n.AddQueue(msg)
n.AddQueue(s.Id, msg)
}
// OnUpdatedUser is a option event for updated users
func (n *ExampleNotifier) OnUpdatedUser(s *types.User) {
msg := fmt.Sprintf("received a updated user trigger for user: %v\n", s.Username)
n.AddQueue(msg)
n.AddQueue(s.Id, msg)
}
// OnDeletedUser is a option event for deleted users
func (n *ExampleNotifier) OnDeletedUser(s *types.User) {
msg := fmt.Sprintf("received a deleted user trigger for user: %v\n", s.Username)
n.AddQueue(msg)
n.AddQueue(s.Id, msg)
}
// OnUpdatedCore is a option event when the settings are updated
func (n *ExampleNotifier) OnUpdatedCore(s *types.Core) {
msg := fmt.Sprintf("received a updated core trigger for core: %v\n", s.Name)
n.AddQueue(msg)
n.AddQueue(0, msg)
}
// OnStart is triggered when statup has been started
func (n *ExampleNotifier) OnStart(s *types.Core) {
msg := fmt.Sprintf("received a trigger on Statup boot: %v\n", s.Name)
n.AddQueue(msg)
n.AddQueue(0, msg)
}
// OnNewNotifier is triggered when a new notifier has initialized
func (n *ExampleNotifier) OnNewNotifier(s *Notification) {
msg := fmt.Sprintf("received a new notifier trigger for notifier: %v\n", s.Method)
n.AddQueue(msg)
n.AddQueue(s.Id, msg)
}
// OnUpdatedNotifier is triggered when a notifier has been updated
func (n *ExampleNotifier) OnUpdatedNotifier(s *Notification) {
msg := fmt.Sprintf("received a update notifier trigger for notifier: %v\n", s.Method)
n.AddQueue(msg)
n.AddQueue(s.Id, msg)
}
// Create a new notifier that includes a form for the end user to insert their own values
@ -224,7 +224,7 @@ func ExampleAddNotifier() {
// OnSuccess will be triggered everytime a service is online
func ExampleNotification_OnSuccess() {
msg := fmt.Sprintf("this is a successful message as a string passing into AddQueue function")
example.AddQueue(msg)
example.AddQueue(0, msg)
fmt.Println(len(example.Queue))
// Output: 1
}
@ -232,13 +232,13 @@ func ExampleNotification_OnSuccess() {
// Add a new message into the queue OnSuccess
func ExampleOnSuccess() {
msg := fmt.Sprintf("received a count trigger for service: %v\n", service.Name)
example.AddQueue(msg)
example.AddQueue(0, msg)
}
// Add a new message into the queue OnFailure
func ExampleOnFailure() {
msg := fmt.Sprintf("received a failing service: %v\n", service.Name)
example.AddQueue(msg)
example.AddQueue(0, msg)
}
// OnTest allows your notifier to be testable
@ -258,7 +258,7 @@ func ExampleNotification_CanTest() {
// Add any type of interface to the AddQueue function to be ran in the queue
func ExampleNotification_AddQueue() {
msg := fmt.Sprintf("this is a failing message as a string passing into AddQueue function")
example.AddQueue(msg)
example.AddQueue(0, msg)
queue := example.Queue
fmt.Printf("Example has %v items in the queue", len(queue))
// Output: Example has 2 items in the queue

View File

@ -59,12 +59,18 @@ type Notification struct {
AuthorUrl string `gorm:"-" json:"-"`
Icon string `gorm:"-" json:"-"`
Delay time.Duration `gorm:"-" json:"-"`
Queue []interface{} `gorm:"-" json:"-"`
Queue []*QueueData `gorm:"-" json:"-"`
Running chan bool `gorm:"-" json:"-"`
Online bool `gorm:"-" json:"-"`
testable bool `gorm:"-" json:"-"`
}
// QueueData is the struct for the messaging queue with service
type QueueData struct {
Id int64
Data interface{}
}
// NotificationForm contains the HTML fields for each variable/input you want the notifier to accept.
type NotificationForm struct {
Type string // the html input type (text, password, email)
@ -83,8 +89,9 @@ type NotificationLog struct {
}
// AddQueue will add any type of interface (json, string, struct, etc) into the Notifiers queue
func (n *Notification) AddQueue(msg interface{}) {
n.Queue = append(n.Queue, msg)
func (n *Notification) AddQueue(uid int64, msg interface{}) {
data := &QueueData{uid, msg}
n.Queue = append(n.Queue, data)
}
// CanTest returns true if the notifier implements the OnTest interface
@ -154,18 +161,6 @@ func normalizeType(ty interface{}) string {
}
}
// removeQueue will remove a specific notification and return the new one
func (n *Notification) removeQueue(msg interface{}) interface{} {
var newArr []interface{}
for _, q := range n.Queue {
if q != msg {
newArr = append(newArr, q)
}
}
n.Queue = newArr
return newArr
}
// Log will record a new notification into memory and will show the logs on the settings page
func (n *Notification) makeLog(msg interface{}) {
log := &NotificationLog{
@ -414,6 +409,18 @@ func (n *Notification) ResetQueue() {
n.Queue = nil
}
// ResetQueue will clear the notifiers Queue for a service
func (n *Notification) ResetUniqueQueue(id int64) []*QueueData {
var queue []*QueueData
for _, v := range n.Queue {
if v.Id != id {
queue = append(queue, v)
}
}
n.Queue = queue
return queue
}
// start will start the go routine for the notifier queue
func (n *Notification) start() {
n.Running = make(chan bool)

View File

@ -97,15 +97,15 @@ func TestSelectNotification(t *testing.T) {
func TestAddQueue(t *testing.T) {
msg := "this is a test in the queue!"
example.AddQueue(msg)
example.AddQueue(0, msg)
assert.Equal(t, 1, len(example.Queue))
example.AddQueue(msg)
example.AddQueue(0, msg)
assert.Equal(t, 2, len(example.Queue))
example.AddQueue(msg)
example.AddQueue(0, msg)
assert.Equal(t, 3, len(example.Queue))
example.AddQueue(msg)
example.AddQueue(0, msg)
assert.Equal(t, 4, len(example.Queue))
example.AddQueue(msg)
example.AddQueue(0, msg)
assert.Equal(t, 5, len(example.Queue))
}

View File

@ -74,10 +74,6 @@ func cached(duration, contentType string, handler func(w http.ResponseWriter, r
} else {
c := httptest.NewRecorder()
handler(c, r)
//for k, v := range c.HeaderMap {
// w.Header()[k] = v
//}
//w.WriteHeader(c.Code)
w.Header().Set("Content-Type", contentType)
content := c.Body.Bytes()
if d, err := time.ParseDuration(duration); err == nil {

View File

@ -35,7 +35,7 @@ func Router() *mux.Router {
dir := utils.Directory
storage = NewStorage()
r := mux.NewRouter()
r.Handle("/", cached("120s", "text/html", http.HandlerFunc(indexHandler)))
r.Handle("/", http.HandlerFunc(indexHandler))
if source.UsingAssets(dir) {
indexHandler := http.FileServer(http.Dir(dir + "/assets/"))
r.PathPrefix("/css/").Handler(http.StripPrefix("/font/", http.FileServer(http.Dir(dir+"/assets/font"))))

View File

@ -76,16 +76,16 @@ func (u *discord) Select() *notifier.Notification {
// OnFailure will trigger failing service
func (u *discord) OnFailure(s *types.Service, f *types.Failure) {
msg := fmt.Sprintf(`{"content": "Your service '%v' is currently failing! Reason: %v"}`, s.Name, f.Issue)
u.AddQueue(msg)
u.AddQueue(s.Id, msg)
u.Online = false
}
// OnSuccess will trigger successful service
func (u *discord) OnSuccess(s *types.Service) {
if !u.Online {
u.ResetQueue()
u.ResetUniqueQueue(s.Id)
msg := fmt.Sprintf(`{"content": "Your service '%v' is back online!"}`, s.Name)
u.AddQueue(msg)
u.AddQueue(s.Id, msg)
}
u.Online = true
}
@ -93,7 +93,7 @@ func (u *discord) OnSuccess(s *types.Service) {
// OnSave triggers when this notifier has been saved
func (u *discord) OnSave() error {
msg := fmt.Sprintf(`{"content": "The discord notifier on Statup was just updated."}`)
u.AddQueue(msg)
u.AddQueue(0, msg)
return nil
}

View File

@ -189,14 +189,14 @@ func (u *email) OnFailure(s *types.Service, f *types.Failure) {
Data: interface{}(s),
From: u.Var1,
}
u.AddQueue(email)
u.AddQueue(s.Id, email)
u.Online = false
}
// OnSuccess will trigger successful service
func (u *email) OnSuccess(s *types.Service) {
if !u.Online {
u.ResetQueue()
u.ResetUniqueQueue(s.Id)
email := &emailOutgoing{
To: u.Var2,
Subject: fmt.Sprintf("Service %v is Back Online", s.Name),
@ -204,7 +204,7 @@ func (u *email) OnSuccess(s *types.Service) {
Data: interface{}(s),
From: u.Var1,
}
u.AddQueue(email)
u.AddQueue(s.Id, email)
}
u.Online = true
}

View File

@ -83,16 +83,16 @@ func (u *lineNotifier) Select() *notifier.Notification {
// OnFailure will trigger failing service
func (u *lineNotifier) OnFailure(s *types.Service, f *types.Failure) {
msg := fmt.Sprintf("Your service '%v' is currently offline!", s.Name)
u.AddQueue(msg)
u.AddQueue(s.Id, msg)
u.Online = false
}
// OnSuccess will trigger successful service
func (u *lineNotifier) OnSuccess(s *types.Service) {
if !u.Online {
u.ResetQueue()
u.ResetUniqueQueue(s.Id)
msg := fmt.Sprintf("Your service '%v' is back online!", s.Name)
u.AddQueue(msg)
u.AddQueue(s.Id, msg)
}
u.Online = true
}

125
notifiers/mobile.go Normal file
View File

@ -0,0 +1,125 @@
// Statup
// Copyright (C) 2018. Hunter Long and the project contributors
// Written by Hunter Long <info@socialeck.com> and the project contributors
//
// https://github.com/hunterlong/statup
//
// The licenses for most software and other practical works are designed
// to take away your freedom to share and change the works. By contrast,
// the GNU General Public License is intended to guarantee your freedom to
// share and change all versions of a program--to make sure it remains free
// software for all its users.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package notifiers
import (
"fmt"
"github.com/hunterlong/statup/core/notifier"
"github.com/hunterlong/statup/types"
"github.com/oliveroneill/exponent-server-sdk-golang/sdk"
"strings"
"time"
)
type mobilePush struct {
*notifier.Notification
}
var mobile = &mobilePush{&notifier.Notification{
Method: "mobile",
Title: "Mobile Notifications",
Description: "Receive push notifications on your mobile device using the Statup App.",
Author: "Hunter Long",
AuthorUrl: "https://github.com/hunterlong",
Delay: time.Duration(5 * time.Second),
Icon: "fas fa-mobile-alt",
Form: []notifier.NotificationForm{{
Type: "text",
Title: "Mobile Identifiers",
Placeholder: "A list of your mobile push notification ids",
DbField: "var1",
}}},
}
// init the discord notifier
func init() {
err := notifier.AddNotifier(mobile)
if err != nil {
panic(err)
}
}
func (u *mobilePush) Select() *notifier.Notification {
return u.Notification
}
// OnFailure will trigger failing service
func (u *mobilePush) OnFailure(s *types.Service, f *types.Failure) {
msg := &expo.PushMessage{
Body: fmt.Sprintf("Your service '%v' is currently failing! Reason: %v", s.Name, f.Issue),
Sound: "default",
Title: "Service Offline",
Priority: expo.DefaultPriority,
}
u.AddQueue(s.Id, msg)
u.Online = false
}
// OnSuccess will trigger successful service
func (u *mobilePush) OnSuccess(s *types.Service) {
if !u.Online {
u.ResetUniqueQueue(s.Id)
msg := &expo.PushMessage{
Body: fmt.Sprintf("Your service '%v' is back online!", s.Name),
Sound: "default",
Title: "Service Online",
Priority: expo.DefaultPriority,
}
u.AddQueue(s.Id, msg)
}
u.Online = true
}
// OnSave triggers when this notifier has been saved
func (u *mobilePush) OnSave() error {
msg := &expo.PushMessage{
Body: "This is a test notification",
Sound: "default",
Title: "Notification Test",
Priority: expo.DefaultPriority,
}
u.AddQueue(0, msg)
return nil
}
// OnTest triggers when this notifier has been saved
func (u *mobilePush) OnTest() error {
return nil
}
// Send will send a HTTP Post to the discord API. It accepts type: []byte
func (u *mobilePush) Send(msg interface{}) error {
pushMessage := msg.(*expo.PushMessage)
client := expo.NewPushClient(nil)
splitIds := strings.Split(u.Var1, ",")
for _, id := range splitIds {
pushToken, err := expo.NewExponentPushToken(expo.ExponentPushToken(id))
if err != nil {
return err
}
pushMessage.To = pushToken
response, err := client.Publish(pushMessage)
if err != nil {
return err
}
if response.ValidateResponse() != nil {
fmt.Println(response.PushMessage.To, "failed")
}
}
return nil
}

127
notifiers/mobile_test.go Normal file
View File

@ -0,0 +1,127 @@
// Statup
// Copyright (C) 2018. Hunter Long and the project contributors
// Written by Hunter Long <info@socialeck.com> and the project contributors
//
// https://github.com/hunterlong/statup
//
// The licenses for most software and other practical works are designed
// to take away your freedom to share and change the works. By contrast,
// the GNU General Public License is intended to guarantee your freedom to
// share and change all versions of a program--to make sure it remains free
// software for all its users.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package notifiers
import (
"github.com/hunterlong/statup/core/notifier"
"github.com/stretchr/testify/assert"
"os"
"testing"
"time"
)
var (
MOBILE_ID string
)
func init() {
MOBILE_ID = os.Getenv("MOBILE_ID")
mobile.Var1 = MOBILE_ID
}
func TestMobileNotifier(t *testing.T) {
t.Parallel()
mobile.Var1 = MOBILE_ID
if MOBILE_ID == "" {
t.Log("mobile notifier testing skipped, missing MOBILE_ID environment variable")
t.SkipNow()
}
currentCount = CountNotifiers()
t.Run("Load mobile", func(t *testing.T) {
mobile.Var1 = MOBILE_ID
mobile.Delay = time.Duration(100 * time.Millisecond)
mobile.Limits = 3
err := notifier.AddNotifier(mobile)
assert.Nil(t, err)
assert.Equal(t, "Hunter Long", mobile.Author)
assert.Equal(t, MOBILE_ID, mobile.Var1)
})
t.Run("Load mobile Notifier", func(t *testing.T) {
notifier.Load()
})
t.Run("mobile Notifier Tester", func(t *testing.T) {
assert.True(t, mobile.CanTest())
})
t.Run("mobile Within Limits", func(t *testing.T) {
ok, err := mobile.WithinLimits()
assert.Nil(t, err)
assert.True(t, ok)
})
t.Run("mobile OnFailure", func(t *testing.T) {
mobile.OnFailure(TestService, TestFailure)
assert.Equal(t, 1, len(mobile.Queue))
})
t.Run("mobile OnFailure multiple times", func(t *testing.T) {
for i := 0; i <= 50; i++ {
mobile.OnFailure(TestService, TestFailure)
}
assert.Equal(t, 52, len(mobile.Queue))
})
t.Run("mobile Check Offline", func(t *testing.T) {
assert.False(t, mobile.Online)
})
t.Run("mobile OnSuccess", func(t *testing.T) {
mobile.OnSuccess(TestService)
assert.Equal(t, 1, len(mobile.Queue))
})
t.Run("mobile Queue after being online", func(t *testing.T) {
assert.True(t, mobile.Online)
assert.Equal(t, 1, len(mobile.Queue))
})
t.Run("mobile OnSuccess Again", func(t *testing.T) {
assert.True(t, mobile.Online)
mobile.OnSuccess(TestService)
assert.Equal(t, 1, len(mobile.Queue))
go notifier.Queue(mobile)
time.Sleep(5 * time.Second)
assert.Equal(t, 0, len(mobile.Queue))
})
t.Run("mobile Within Limits again", func(t *testing.T) {
ok, err := mobile.WithinLimits()
assert.Nil(t, err)
assert.True(t, ok)
})
t.Run("mobile Send", func(t *testing.T) {
err := mobile.Send(slackTestMessage)
assert.Nil(t, err)
assert.Equal(t, 0, len(mobile.Queue))
})
t.Run("mobile Test", func(t *testing.T) {
err := mobile.OnTest()
assert.Nil(t, err)
})
t.Run("mobile Queue", func(t *testing.T) {
go notifier.Queue(mobile)
time.Sleep(5 * time.Second)
assert.Equal(t, MOBILE_ID, mobile.Var1)
assert.Equal(t, 0, len(mobile.Queue))
})
}

View File

@ -57,14 +57,14 @@ var slacker = &slack{&notifier.Notification{
}}},
}
func parseSlackMessage(temp string, data interface{}) error {
func parseSlackMessage(id int64, temp string, data interface{}) error {
buf := new(bytes.Buffer)
slackTemp, _ := template.New("slack").Parse(temp)
err := slackTemp.Execute(buf, data)
if err != nil {
return err
}
slacker.AddQueue(buf.String())
slacker.AddQueue(id, buf.String())
return nil
}
@ -120,20 +120,20 @@ func (u *slack) OnFailure(s *types.Service, f *types.Failure) {
Template: failingTemplate,
Time: time.Now().Unix(),
}
parseSlackMessage(failingTemplate, message)
parseSlackMessage(s.Id, failingTemplate, message)
u.Online = false
}
// OnSuccess will trigger successful service
func (u *slack) OnSuccess(s *types.Service) {
if !u.Online {
u.ResetQueue()
u.ResetUniqueQueue(s.Id)
message := slackMessage{
Service: s,
Template: successTemplate,
Time: time.Now().Unix(),
}
parseSlackMessage(successTemplate, message)
parseSlackMessage(s.Id, successTemplate, message)
}
u.Online = true
}
@ -141,6 +141,6 @@ func (u *slack) OnSuccess(s *types.Service) {
// OnSave triggers when this notifier has been saved
func (u *slack) OnSave() error {
message := fmt.Sprintf("Notification %v is receiving updated information.", u.Method)
u.AddQueue(message)
u.AddQueue(0, message)
return nil
}

View File

@ -39,7 +39,7 @@ var twilioNotifier = &twilio{&notifier.Notification{
Description: "Receive SMS text messages directly to your cellphone when a service is offline. You can use a Twilio test account with limits. This notifier uses the <a href=\"https://www.twilio.com/docs/usage/api\">Twilio API</a>.",
Author: "Hunter Long",
AuthorUrl: "https://github.com/hunterlong",
Icon: "fas fa-mobile-alt",
Icon: "far fa-comment-alt",
Delay: time.Duration(10 * time.Second),
Form: []notifier.NotificationForm{{
Type: "text",
@ -115,16 +115,16 @@ func (u *twilio) Send(msg interface{}) error {
// OnFailure will trigger failing service
func (u *twilio) OnFailure(s *types.Service, f *types.Failure) {
msg := fmt.Sprintf("Your service '%v' is currently offline!", s.Name)
u.AddQueue(msg)
u.AddQueue(s.Id, msg)
u.Online = false
}
// OnSuccess will trigger successful service
func (u *twilio) OnSuccess(s *types.Service) {
if !u.Online {
u.ResetQueue()
u.ResetUniqueQueue(s.Id)
msg := fmt.Sprintf("Your service '%v' is back online!", s.Name)
u.AddQueue(msg)
u.AddQueue(s.Id, msg)
}
u.Online = true
}

View File

@ -170,16 +170,16 @@ func (w *webhooker) OnTest() error {
// OnFailure will trigger failing service
func (w *webhooker) OnFailure(s *types.Service, f *types.Failure) {
msg := replaceBodyText(w.Var2, s, f)
webhook.AddQueue(msg)
w.AddQueue(s.Id, msg)
w.Online = false
}
// OnSuccess will trigger successful service
func (w *webhooker) OnSuccess(s *types.Service) {
if !w.Online {
w.ResetQueue()
w.ResetUniqueQueue(s.Id)
msg := replaceBodyText(w.Var2, s, nil)
webhook.AddQueue(msg)
w.AddQueue(s.Id, msg)
}
w.Online = true
}