notifier testing - doc.go added - timezone column

pull/78/head
Hunter Long 2018-09-15 15:21:58 -07:00
parent e39bf6ef0a
commit f144b8cec1
22 changed files with 492 additions and 177 deletions

12
cmd/doc.go Normal file
View File

@ -0,0 +1,12 @@
// Package main for building the Statup CLI binary application. This package
// connects to all the other packages to make a runnable binary for multiple
// operating system.
//
// To build Statup from source, run the follow command in the root directory:
// // go build -o statup ./cmd
//
// Remember that you'll need to compile the static assets using Rice:
// // cd source && rice embed-go
//
// by Hunter Long
package main

6
core/doc.go Normal file
View File

@ -0,0 +1,6 @@
// Package core contains the main functionality of Statup. This includes everything for
// Services, Hits, Failures, Users, service checking mechanisms, databases, and notifiers
// in the notifier package
//
// by Hunter Long
package core

View File

@ -262,7 +262,8 @@ CheckNotifier:
case <-time.After(rateLimit):
notification = n.Select()
if len(notification.Queue) > 0 {
if notification.WithinLimits() {
ok, _ := notification.WithinLimits()
if ok {
msg := notification.Queue[0]
err := n.Send(msg)
if err != nil {
@ -371,23 +372,27 @@ func isEnabled(n interface{}) bool {
func inLimits(n interface{}) bool {
notifier := n.(Notifier).Select()
return notifier.WithinLimits()
ok, _ := notifier.WithinLimits()
return ok
}
func (notify *Notification) WithinLimits() bool {
func (notify *Notification) WithinLimits() (bool, error) {
if notify.SentLastMinute() == 0 {
return true, nil
}
if notify.SentLastMinute() >= notify.Limits {
return false
return false, errors.New(fmt.Sprintf("notifier sent %v out of %v in last minute", notify.SentLastMinute(), notify.Limits))
}
if notify.Delay.Seconds() == 0 {
notify.Delay = time.Duration(500 * time.Millisecond)
}
if notify.LastSent().Seconds() == 0 {
return true
return true, nil
}
if notify.Delay.Seconds() >= notify.LastSent().Seconds() {
return false
return false, errors.New(fmt.Sprintf("notifiers delay (%v) is greater than last message sent (%v)", notify.Delay.Seconds(), notify.LastSent().Seconds()))
}
return true
return true, nil
}
func (n *Notification) ResetQueue() {

5
handlers/doc.go Normal file
View File

@ -0,0 +1,5 @@
// Package handlers holds all the HTTP requests and routes. All HTTP related
// functions are in this package.
//
// by Hunter Long
package handlers

View File

@ -51,19 +51,7 @@ func Router() *mux.Router {
r.Handle("/charts/{id}.js", http.HandlerFunc(RenderServiceChartHandler))
r.Handle("/setup", http.HandlerFunc(SetupHandler)).Methods("GET")
r.Handle("/setup", http.HandlerFunc(ProcessSetupHandler)).Methods("POST")
r.Handle("/dashboard", http.HandlerFunc(DashboardHandler)).Methods("GET")
r.Handle("/dashboard", http.HandlerFunc(LoginHandler)).Methods("POST")
r.Handle("/logout", http.HandlerFunc(LogoutHandler))
r.Handle("/services", http.HandlerFunc(ServicesHandler)).Methods("GET")
r.Handle("/services", http.HandlerFunc(CreateServiceHandler)).Methods("POST")
r.Handle("/services/reorder", http.HandlerFunc(ReorderServiceHandler)).Methods("POST")
r.Handle("/service/{id}", http.HandlerFunc(ServicesViewHandler)).Methods("GET")
r.Handle("/service/{id}", http.HandlerFunc(ServicesUpdateHandler)).Methods("POST")
r.Handle("/service/{id}/edit", http.HandlerFunc(ServicesViewHandler))
r.Handle("/service/{id}/delete", http.HandlerFunc(ServicesDeleteHandler))
r.Handle("/service/{id}/delete_failures", http.HandlerFunc(ServicesDeleteFailuresHandler)).Methods("GET")
r.Handle("/service/{id}/checkin", http.HandlerFunc(CheckinCreateUpdateHandler)).Methods("POST")
r.Handle("/users", http.HandlerFunc(UsersHandler)).Methods("GET")
r.Handle("/dashboard", http.HandlerFunc(DashboardHandler)).Methods("GET").HandlerFunc(UsersHandler).Methods("GET")
r.Handle("/users", http.HandlerFunc(CreateUserHandler)).Methods("POST")
r.Handle("/user/{id}", http.HandlerFunc(UsersEditHandler)).Methods("GET")
r.Handle("/user/{id}", http.HandlerFunc(UpdateUserHandler)).Methods("POST")

View File

@ -59,7 +59,6 @@ func init() {
func (u *Discord) Send(msg interface{}) error {
message := msg.([]byte)
fmt.Println("sending: ", message)
req, _ := http.NewRequest("POST", discorder.GetValue("host"), bytes.NewBuffer(message))
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
@ -76,7 +75,6 @@ func (u *Discord) Select() *notifier.Notification {
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)
fmt.Println(msg)
u.AddQueue(msg)
}

60
notifiers/discord_test.go Normal file
View File

@ -0,0 +1,60 @@
package notifiers
import (
"github.com/hunterlong/statup/core/notifier"
"github.com/stretchr/testify/assert"
"os"
"testing"
"time"
)
var (
DISCORD_URL = os.Getenv("DISCORD_URL")
discordMessage = `{"content": "The Discord notifier on Statup has been tested!"}`
)
func init() {
discorder.Host = DISCORD_URL
}
func TestDiscordNotifier(t *testing.T) {
if DISCORD_URL == "" {
t.Log("Discord notifier testing skipped, missing DISCORD_URL environment variable")
t.SkipNow()
}
currentCount = CountNotifiers()
t.Run("Load Discord", func(t *testing.T) {
discorder.Host = DISCORD_URL
discorder.Delay = time.Duration(100 * time.Millisecond)
err := notifier.AddNotifier(discorder)
assert.Nil(t, err)
assert.Equal(t, "Hunter Long", discorder.Author)
assert.Equal(t, DISCORD_URL, discorder.Host)
assert.Equal(t, currentCount+1, CountNotifiers())
})
t.Run("Load Discord Notifier", func(t *testing.T) {
count := notifier.Load()
assert.Equal(t, currentCount+1, len(count))
})
t.Run("Discord Within Limits", func(t *testing.T) {
ok, err := discorder.WithinLimits()
assert.Nil(t, err)
assert.True(t, ok)
})
t.Run("Discord Send", func(t *testing.T) {
err := discorder.Send([]byte(discordMessage))
assert.Nil(t, err)
})
t.Run("Discord Queue", func(t *testing.T) {
go notifier.Queue(discorder)
time.Sleep(1 * time.Second)
assert.Equal(t, DISCORD_URL, discorder.Host)
assert.Equal(t, 0, len(discorder.Queue))
})
}

9
notifiers/doc.go Normal file
View File

@ -0,0 +1,9 @@
// Package notifiers holds all the notifiers for Statup, which also includes
// user created notifiers that have been accepted in a Push Request. Read the
// Statup notifier wiki at: https://github.com/hunterlong/statup/wiki
//
// To see a full example of a notifier with all events, visit Statup's
// notifier example code: https://github.com/hunterlong/statup/wiki/Notifier-Example
//
// by Hunter Long
package notifiers

View File

@ -17,6 +17,7 @@ package notifiers
import (
"bytes"
"crypto/tls"
"fmt"
"github.com/hunterlong/statup/core/notifier"
"github.com/hunterlong/statup/types"
@ -85,12 +86,12 @@ func init() {
}
func (u *Email) Send(msg interface{}) error {
//email := msg.(*EmailOutgoing)
//err := u.dialSend(email)
//if err != nil {
// utils.Log(3, fmt.Sprintf("Email Notifier could not send email: %v", err))
// return err
//}
email := msg.(*EmailOutgoing)
err := u.dialSend(email)
if err != nil {
utils.Log(3, fmt.Sprintf("Email Notifier could not send email: %v", err))
return err
}
return nil
}
@ -145,6 +146,9 @@ func (u *Email) OnSave() error {
}
func (u *Email) dialSend(email *EmailOutgoing) error {
mailer = gomail.NewDialer(emailer.Host, emailer.Port, emailer.Username, emailer.Password)
mailer.TLSConfig = &tls.Config{InsecureSkipVerify: true}
emailSource(email)
m := gomail.NewMessage()
m.SetHeader("From", email.From)
m.SetHeader("To", email.To)
@ -157,7 +161,7 @@ func (u *Email) dialSend(email *EmailOutgoing) error {
return nil
}
func SendEmail(email *EmailOutgoing) {
func emailSource(email *EmailOutgoing) {
source := EmailTemplate(email.Template, email.Data)
email.Source = source
}

96
notifiers/email_test.go Normal file
View File

@ -0,0 +1,96 @@
package notifiers
import (
"fmt"
"github.com/hunterlong/statup/core/notifier"
"github.com/hunterlong/statup/utils"
"github.com/stretchr/testify/assert"
"os"
"testing"
"time"
)
var (
EMAIL_HOST = os.Getenv("EMAIL_HOST")
EMAIL_USER = os.Getenv("EMAIL_USER")
EMAIL_PASS = os.Getenv("EMAIL_PASS")
EMAIL_OUTGOING = os.Getenv("EMAIL_OUTGOING")
EMAIL_SEND_TO = os.Getenv("EMAIL_SEND_TO")
EMAIL_PORT = utils.StringInt(os.Getenv("EMAIL_PORT"))
)
var testEmail *EmailOutgoing
func init() {
emailer.Host = EMAIL_HOST
emailer.Username = EMAIL_USER
emailer.Password = EMAIL_PASS
emailer.Var1 = EMAIL_OUTGOING
emailer.Var2 = EMAIL_SEND_TO
emailer.Port = int(EMAIL_PORT)
}
func TestEmailNotifier(t *testing.T) {
if EMAIL_HOST == "" || EMAIL_USER == "" || EMAIL_PASS == "" {
t.Log("Email notifier testing skipped, missing EMAIL_ environment variables")
t.SkipNow()
}
currentCount = CountNotifiers()
t.Run("New Emailer", func(t *testing.T) {
emailer.Host = EMAIL_HOST
emailer.Username = EMAIL_USER
emailer.Password = EMAIL_PASS
emailer.Var1 = EMAIL_OUTGOING
emailer.Var2 = EMAIL_SEND_TO
emailer.Port = int(EMAIL_PORT)
emailer.Delay = time.Duration(100 * time.Millisecond)
message := "this is a test email!"
testEmail = &EmailOutgoing{
To: emailer.GetValue("var2"),
Subject: fmt.Sprintf("Service %v is Failing", TestService.Name),
Template: MESSAGE,
Data: interface{}(message),
From: emailer.GetValue("var1"),
}
})
t.Run("Add Email Notifier", func(t *testing.T) {
err := notifier.AddNotifier(emailer)
assert.Nil(t, err)
assert.Equal(t, "Hunter Long", emailer.Author)
assert.Equal(t, EMAIL_HOST, emailer.Host)
assert.Equal(t, currentCount+1, CountNotifiers())
})
t.Run("Emailer Load", func(t *testing.T) {
count := notifier.Load()
assert.Equal(t, currentCount+1, len(count))
})
t.Run("Email Within Limits", func(t *testing.T) {
ok, err := emailer.WithinLimits()
assert.Nil(t, err)
assert.True(t, ok)
})
t.Run("Emailer Test Source", func(t *testing.T) {
emailSource(testEmail)
assert.NotEmpty(t, testEmail.Source)
})
t.Run("Email Send", func(t *testing.T) {
err := emailer.Send(testEmail)
assert.Nil(t, err)
})
t.Run("Email Run Queue", func(t *testing.T) {
go notifier.Queue(emailer)
time.Sleep(5 * time.Second)
assert.Equal(t, EMAIL_HOST, emailer.Host)
assert.Equal(t, 0, len(emailer.Queue))
})
}

View File

@ -1,69 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Sample Email</title>
</head>
<body style="-webkit-text-size-adjust: none; box-sizing: border-box; color: #74787E; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; height: 100%; line-height: 1.4; margin: 0; width: 100% !important;" bgcolor="#F2F4F6"><style type="text/css">
body {
width: 100% !important; height: 100%; margin: 0; line-height: 1.4; background-color: #F2F4F6; color: #74787E; -webkit-text-size-adjust: none;
}
@media only screen and (max-width: 600px) {
.email-body_inner {
width: 100% !important;
}
.email-footer {
width: 100% !important;
}
}
@media only screen and (max-width: 500px) {
.button {
width: 100% !important;
}
}
</style>
<table class="email-wrapper" width="100%" cellpadding="0" cellspacing="0" style="box-sizing: border-box; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; margin: 0; padding: 0; width: 100%;" bgcolor="#F2F4F6">
<tr>
<td align="center" style="box-sizing: border-box; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; word-break: break-word;">
<table class="email-content" width="100%" cellpadding="0" cellspacing="0" style="box-sizing: border-box; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; margin: 0; padding: 0; width: 100%;">
<tr>
<td class="email-body" width="100%" cellpadding="0" cellspacing="0" style="-premailer-cellpadding: 0; -premailer-cellspacing: 0; border-bottom-color: #EDEFF2; border-bottom-style: solid; border-bottom-width: 1px; border-top-color: #EDEFF2; border-top-style: solid; border-top-width: 1px; box-sizing: border-box; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; margin: 0; padding: 0; width: 100%; word-break: break-word;" bgcolor="#FFFFFF">
<table class="email-body_inner" align="center" width="570" cellpadding="0" cellspacing="0" style="box-sizing: border-box; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; margin: 0 auto; padding: 0; width: 570px;" bgcolor="#FFFFFF">
<tr>
<td class="content-cell" style="box-sizing: border-box; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; padding: 35px; word-break: break-word;">
<h1 style="box-sizing: border-box; color: #2F3133; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; font-size: 19px; font-weight: bold; margin-top: 0;" align="left">{{ .Service.Name }} is Offline!</h1>
<p style="box-sizing: border-box; color: #74787E; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; font-size: 16px; line-height: 1.5em; margin-top: 0;" align="left">
Your Statup service '<a target="_blank" href="{{.Service.Domain}}">{{.Service.Name}}</a>' has been triggered with a HTTP status code of '{{.Service.LastStatusCode}}' and is currently offline based on your requirements. This failure was created on {{.Service.CreatedAt}}.
</p>
{{if .Service.LastResponse }}
<h1 style="box-sizing: border-box; color: #2F3133; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; font-size: 19px; font-weight: bold; margin-top: 0;" align="left">Last Response</h1>
<p style="box-sizing: border-box; color: #74787E; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; font-size: 16px; line-height: 1.5em; margin-top: 0;" align="left">
{{ .Service.LastResponse }}
</p>
{{end}}
<table class="body-sub" style="border-top-color: #EDEFF2; border-top-style: solid; border-top-width: 1px; box-sizing: border-box; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; margin-top: 25px; padding-top: 25px;">
<td style="box-sizing: border-box; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; word-break: break-word;">
<a href="/service/{{.Service.Id}}" class="button button--blue" target="_blank" style="-webkit-text-size-adjust: none; background: #3869D4; border-color: #3869d4; border-radius: 3px; border-style: solid; border-width: 10px 18px; box-shadow: 0 2px 3px rgba(0, 0, 0, 0.16); box-sizing: border-box; color: #FFF; display: inline-block; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; text-decoration: none;">View Service</a>
</td>
<td style="box-sizing: border-box; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; word-break: break-word;">
<a href="/dashboard" class="button button--blue" target="_blank" style="-webkit-text-size-adjust: none; background: #3869D4; border-color: #3869d4; border-radius: 3px; border-style: solid; border-width: 10px 18px; box-shadow: 0 2px 3px rgba(0, 0, 0, 0.16); box-sizing: border-box; color: #FFF; display: inline-block; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; text-decoration: none;">Statup Dashboard</a>
</td>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>

View File

@ -1,61 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Sample Email</title>
</head>
<body style="-webkit-text-size-adjust: none; box-sizing: border-box; color: #74787E; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; height: 100%; line-height: 1.4; margin: 0; width: 100% !important;" bgcolor="#F2F4F6"><style type="text/css">
body {
width: 100% !important; height: 100%; margin: 0; line-height: 1.4; background-color: #F2F4F6; color: #74787E; -webkit-text-size-adjust: none;
}
@media only screen and (max-width: 600px) {
.email-body_inner {
width: 100% !important;
}
.email-footer {
width: 100% !important;
}
}
@media only screen and (max-width: 500px) {
.button {
width: 100% !important;
}
}
</style>
<table class="email-wrapper" width="100%" cellpadding="0" cellspacing="0" style="box-sizing: border-box; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; margin: 0; padding: 0; width: 100%;" bgcolor="#F2F4F6">
<tr>
<td align="center" style="box-sizing: border-box; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; word-break: break-word;">
<table class="email-content" width="100%" cellpadding="0" cellspacing="0" style="box-sizing: border-box; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; margin: 0; padding: 0; width: 100%;">
<tr>
<td class="email-body" width="100%" cellpadding="0" cellspacing="0" style="-premailer-cellpadding: 0; -premailer-cellspacing: 0; border-bottom-color: #EDEFF2; border-bottom-style: solid; border-bottom-width: 1px; border-top-color: #EDEFF2; border-top-style: solid; border-top-width: 1px; box-sizing: border-box; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; margin: 0; padding: 0; width: 100%; word-break: break-word;" bgcolor="#FFFFFF">
<table class="email-body_inner" align="center" width="570" cellpadding="0" cellspacing="0" style="box-sizing: border-box; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; margin: 0 auto; padding: 0; width: 570px;" bgcolor="#FFFFFF">
<tr>
<td class="content-cell" style="box-sizing: border-box; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; padding: 35px; word-break: break-word;">
<h1 style="box-sizing: border-box; color: #2F3133; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; font-size: 19px; font-weight: bold; margin-top: 0;" align="left">Looks Like Emails Work!</h1>
<p style="box-sizing: border-box; color: #74787E; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; font-size: 16px; line-height: 1.5em; margin-top: 0;" align="left">
Since you got this email, it confirms that your Statup Status Page email system is working correctly.
</p>
</p>
<p style="box-sizing: border-box; color: #74787E; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; font-size: 16px; line-height: 1.5em; margin-top: 0;" align="left">
Enjoy using Statup!
<br />Statup.io Team</p>
<table class="body-sub" style="border-top-color: #EDEFF2; border-top-style: solid; border-top-width: 1px; box-sizing: border-box; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; margin-top: 25px; padding-top: 25px;">
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>

View File

@ -0,0 +1,60 @@
package notifiers
import (
"github.com/hunterlong/statup/core/notifier"
"github.com/hunterlong/statup/source"
"github.com/hunterlong/statup/types"
"github.com/hunterlong/statup/utils"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/sqlite"
)
var (
dir string
db *gorm.DB
currentCount int
)
var TestService = &types.Service{
Name: "Interpol - All The Rage Back Home",
Domain: "https://www.youtube.com/watch?v=-u6DvRyyKGU",
ExpectedStatus: 200,
Interval: 30,
Type: "http",
Method: "GET",
Timeout: 20,
}
var TestFailure = &types.Failure{
Issue: "testing",
}
var TestUser = &types.User{
Username: "admin",
Email: "info@email.com",
}
var TestCore = &types.Core{
Name: "testing notifiers",
}
func CountNotifiers() int {
return len(notifier.AllCommunications)
}
func init() {
dir = utils.Directory
source.Assets()
utils.InitLogs()
injectDatabase()
}
func injectDatabase() {
utils.DeleteFile(dir + "/statup.db")
db, err := gorm.Open("sqlite3", dir+"/statup.db")
if err != nil {
panic(err)
}
db.CreateTable(&notifier.Notification{})
notifier.SetDB(db)
}

View File

@ -21,6 +21,7 @@ import (
"github.com/hunterlong/statup/core/notifier"
"github.com/hunterlong/statup/types"
"github.com/hunterlong/statup/utils"
"io/ioutil"
"net/http"
"text/template"
"time"
@ -56,7 +57,7 @@ var slacker = &Slack{&notifier.Notification{
}}},
}
func sendSlack(temp string, data interface{}) error {
func parseSlackMessage(temp string, data interface{}) error {
buf := new(bytes.Buffer)
slackTemp, _ := template.New("slack").Parse(temp)
err := slackTemp.Execute(buf, data)
@ -67,7 +68,7 @@ func sendSlack(temp string, data interface{}) error {
return nil
}
type slackMessage struct {
type SlackMessage struct {
Service *types.Service
Template string
Time int64
@ -84,10 +85,13 @@ func init() {
func (u *Slack) Send(msg interface{}) error {
message := msg.(string)
client := new(http.Client)
_, err := client.Post(u.Host, "application/json", bytes.NewBuffer([]byte(message)))
res, err := client.Post(u.Host, "application/json", bytes.NewBuffer([]byte(message)))
if err != nil {
return err
}
defer res.Body.Close()
contents, _ := ioutil.ReadAll(res.Body)
fmt.Println(string(contents))
return nil
}
@ -98,18 +102,18 @@ func (u *Slack) Select() *notifier.Notification {
func (u *Slack) OnTest(n notifier.Notification) (bool, error) {
utils.Log(1, "Slack notifier loaded")
msg := fmt.Sprintf("You're Statup Slack Notifier is working correctly!")
err := sendSlack(TEST_TEMPLATE, msg)
err := parseSlackMessage(TEST_TEMPLATE, msg)
return true, err
}
// ON SERVICE FAILURE, DO YOUR OWN FUNCTIONS
func (u *Slack) OnFailure(s *types.Service, f *types.Failure) {
message := slackMessage{
message := SlackMessage{
Service: s,
Template: FAILURE,
Time: time.Now().Unix(),
}
sendSlack(FAILING_TEMPLATE, message)
parseSlackMessage(FAILING_TEMPLATE, message)
}
// ON SERVICE SUCCESS, DO YOUR OWN FUNCTIONS

71
notifiers/slack_test.go Normal file
View File

@ -0,0 +1,71 @@
package notifiers
import (
"github.com/hunterlong/statup/core/notifier"
"github.com/stretchr/testify/assert"
"os"
"testing"
"time"
)
var (
SLACK_URL = os.Getenv("SLACK_URL")
slackMessage = `{"text":"this is a test from the Slack notifier!"}`
slackTestMessage = SlackMessage{
Service: TestService,
Template: FAILURE,
Time: time.Now().Unix(),
}
)
func init() {
slacker.Host = SLACK_URL
}
func TestSlackNotifier(t *testing.T) {
if SLACK_URL == "" {
t.Log("Slack notifier testing skipped, missing SLACK_URL environment variable")
t.SkipNow()
}
currentCount = CountNotifiers()
t.Run("Load Slack", func(t *testing.T) {
slacker.Host = SLACK_URL
slacker.Delay = time.Duration(100 * time.Millisecond)
err := notifier.AddNotifier(slacker)
assert.Nil(t, err)
assert.Equal(t, "Hunter Long", slacker.Author)
assert.Equal(t, SLACK_URL, slacker.Host)
assert.Equal(t, currentCount+1, CountNotifiers())
})
t.Run("Load Slack Notifier", func(t *testing.T) {
count := notifier.Load()
assert.Equal(t, currentCount+1, len(count))
})
t.Run("Slack parse message", func(t *testing.T) {
err := parseSlackMessage("this is a test message!", slackTestMessage)
assert.Nil(t, err)
assert.Equal(t, 1, len(slacker.Queue))
})
t.Run("Slack Within Limits", func(t *testing.T) {
ok, err := slacker.WithinLimits()
assert.Nil(t, err)
assert.True(t, ok)
})
t.Run("Slack Send", func(t *testing.T) {
err := slacker.Send(slackMessage)
assert.Nil(t, err)
})
t.Run("Slack Queue", func(t *testing.T) {
go notifier.Queue(slacker)
time.Sleep(1 * time.Second)
assert.Equal(t, SLACK_URL, slacker.Host)
assert.Equal(t, 0, len(slacker.Queue))
})
}

View File

@ -16,10 +16,13 @@
package notifiers
import (
"encoding/json"
"errors"
"fmt"
"github.com/hunterlong/statup/core/notifier"
"github.com/hunterlong/statup/types"
"github.com/hunterlong/statup/utils"
"io/ioutil"
"net/http"
"net/url"
"strings"
@ -27,14 +30,9 @@ import (
)
const (
TWILIO_ID = 3
TWILIO_METHOD = "twilio"
)
var (
twilioMessages []string
)
type Twilio struct {
*notifier.Notification
}
@ -59,12 +57,12 @@ var twilio = &Twilio{&notifier.Notification{
}, {
Type: "text",
Title: "SMS to Phone Number",
Placeholder: "+18555555555",
Placeholder: "18555555555",
DbField: "Var1",
}, {
Type: "text",
Title: "From Phone Number",
Placeholder: "+18555555555",
Placeholder: "18555555555",
DbField: "Var2",
}}},
}
@ -86,19 +84,24 @@ func (u *Twilio) Send(msg interface{}) error {
twilioUrl := fmt.Sprintf("https://api.twilio.com/2010-04-01/Accounts/%v/Messages.json", u.GetValue("api_key"))
client := &http.Client{}
v := url.Values{}
v.Set("To", u.Var1)
v.Set("From", u.Var2)
v.Set("To", "+"+u.Var1)
v.Set("From", "+"+u.Var2)
v.Set("Body", message)
rb := *strings.NewReader(v.Encode())
req, err := http.NewRequest("POST", twilioUrl, &rb)
req.SetBasicAuth(u.ApiKey, u.ApiSecret)
req.Header.Add("Accept", "application/json")
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
client.Do(req)
res, err := client.Do(req)
if err != nil {
utils.Log(3, fmt.Sprintf("Issue sending Twilio notification: %v", err))
return err
}
defer res.Body.Close()
contents, _ := ioutil.ReadAll(res.Body)
success, twilioRes := twilioSuccess(contents)
if !success {
return errors.New(fmt.Sprintf("twilio didn't receive the expected status of 'enque' from API got: %v", twilioRes))
}
return nil
}
@ -121,3 +124,37 @@ func (u *Twilio) OnSave() error {
return nil
}
func twilioSuccess(res []byte) (bool, TwilioResponse) {
var obj TwilioResponse
json.Unmarshal(res, &obj)
if obj.Status == "queued" {
return true, obj
}
return false, obj
}
type TwilioResponse struct {
Sid string `json:"sid"`
DateCreated string `json:"date_created"`
DateUpdated string `json:"date_updated"`
DateSent interface{} `json:"date_sent"`
AccountSid string `json:"account_sid"`
To string `json:"to"`
From string `json:"from"`
MessagingServiceSid interface{} `json:"messaging_service_sid"`
Body string `json:"body"`
Status string `json:"status"`
NumSegments string `json:"num_segments"`
NumMedia string `json:"num_media"`
Direction string `json:"direction"`
APIVersion string `json:"api_version"`
Price interface{} `json:"price"`
PriceUnit string `json:"price_unit"`
ErrorCode interface{} `json:"error_code"`
ErrorMessage interface{} `json:"error_message"`
URI string `json:"uri"`
SubresourceUris struct {
Media string `json:"media"`
} `json:"subresource_uris"`
}

66
notifiers/twilio_test.go Normal file
View File

@ -0,0 +1,66 @@
package notifiers
import (
"github.com/hunterlong/statup/core/notifier"
"github.com/stretchr/testify/assert"
"os"
"testing"
"time"
)
var (
TWILIO_SID = os.Getenv("TWILIO_SID")
TWILIO_SECRET = os.Getenv("TWILIO_SECRET")
TWILIO_FROM = os.Getenv("TWILIO_FROM")
TWILIO_TO = os.Getenv("TWILIO_TO")
twilioMessage = "The Twilio notifier on Statup has been tested!"
)
func init() {
twilio.ApiKey = TWILIO_SID
twilio.ApiSecret = TWILIO_SECRET
twilio.Var1 = TWILIO_TO
twilio.Var2 = TWILIO_FROM
}
func TestTwilioNotifier(t *testing.T) {
if TWILIO_SID == "" || TWILIO_SECRET == "" || TWILIO_FROM == "" {
t.Log("Twilio notifier testing skipped, missing TWILIO_SID environment variable")
t.SkipNow()
}
currentCount = CountNotifiers()
t.Run("Load Twilio", func(t *testing.T) {
twilio.ApiKey = TWILIO_SID
twilio.Delay = time.Duration(100 * time.Millisecond)
err := notifier.AddNotifier(twilio)
assert.Nil(t, err)
assert.Equal(t, "Hunter Long", twilio.Author)
assert.Equal(t, TWILIO_SID, twilio.ApiKey)
assert.Equal(t, currentCount+1, CountNotifiers())
})
t.Run("Load Twilio Notifier", func(t *testing.T) {
count := notifier.Load()
assert.Equal(t, currentCount+1, len(count))
})
t.Run("Twilio Within Limits", func(t *testing.T) {
ok, err := twilio.WithinLimits()
assert.Nil(t, err)
assert.True(t, ok)
})
t.Run("Twilio Send", func(t *testing.T) {
err := twilio.Send(twilioMessage)
assert.Nil(t, err)
})
t.Run("Twilio Queue", func(t *testing.T) {
go notifier.Queue(twilio)
time.Sleep(1 * time.Second)
assert.Equal(t, TWILIO_SID, twilio.ApiKey)
assert.Equal(t, 0, len(twilio.Queue))
})
}

8
source/doc.go Normal file
View File

@ -0,0 +1,8 @@
// Package source holds all the assets for Statup. This includes
// CSS, JS, SCSS, HTML and other website related content.
// This package uses Rice to compile all assets into a single 'rice-box.go' file.
//
// To compile all the assets run `rice embed-go` in the source directory.
//
// by Hunter Long
package source

View File

@ -32,7 +32,7 @@
<a class="nav-link active" id="v-pills-home-tab" data-toggle="pill" href="#v-pills-home" role="tab" aria-controls="v-pills-home" aria-selected="true">Settings</a>
<a class="nav-link" id="v-pills-style-tab" data-toggle="pill" href="#v-pills-style" role="tab" aria-controls="v-pills-style" aria-selected="false">Theme Editor</a>
{{ range .Notifications }}
<a class="nav-link text-capitalize" id="v-pills-{{underscore .Select.Method}}-tab" data-toggle="pill" href="#v-pills-{{underscore .Select.Method}}" role="tab" aria-controls="v-pills-{{underscore .Select.Method}}" aria-selected="false">{{.Select.Method}}</a>
<a class="nav-link text-capitalize" id="v-pills-{{underscore .Select.Method}}-tab" data-toggle="pill" href="#v-pills-{{underscore .Select.Method}}" role="tab" aria-controls="v-pills-{{underscore .Select.Method}}" aria-selected="false">{{.Select.Method}} <span class="badge badge-pill badge-secondary"></span></a>
{{ end }}
<a class="nav-link" id="v-pills-browse-tab" data-toggle="pill" href="#v-pills-browse" role="tab" aria-controls="v-pills-home" aria-selected="false">Browse Plugins</a>
<a class="nav-link d-none" id="v-pills-backups-tab" data-toggle="pill" href="#v-pills-backups" role="tab" aria-controls="v-pills-backups" aria-selected="false">Backups</a>

View File

@ -21,6 +21,7 @@ type Core struct {
Version string `gorm:"column:version" json:"version"`
MigrationId int64 `gorm:"column:migration_id" json:"migration_id,omitempty"`
UseCdn bool `gorm:"column:use_cdn;default:false" json:"using_cdn,omitempty"`
Timezone float32 `gorm:"column:timezone;default:-8.0" json:"timezone,omitempty"`
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
DbConnection string `gorm:"-" json:"database"`

5
types/doc.go Normal file
View File

@ -0,0 +1,5 @@
// Package type contains all of the structs for objects in Statup including
// services, hits, failures, Core, and others.
//
// by Hunter Long
package types

10
utils/doc.go Normal file
View File

@ -0,0 +1,10 @@
// Package utils contains common methods used in most packages in Statup.
// This package contains multiple function like:
// Logging, encryption, type conversions, setting utils.Directory as the current directory,
// running local CMD commands, and creaing/deleting files/folder.
//
// You can overwrite the utils.Directory global variable by including
// STATUP_DIR environment variable to be an absolute path.
//
// by Hunter Long
package utils