diff --git a/cmd/doc.go b/cmd/doc.go
new file mode 100644
index 00000000..9ec0ac66
--- /dev/null
+++ b/cmd/doc.go
@@ -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
diff --git a/core/doc.go b/core/doc.go
new file mode 100644
index 00000000..756f715a
--- /dev/null
+++ b/core/doc.go
@@ -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
diff --git a/core/notifier/notifiers.go b/core/notifier/notifiers.go
index 46f71b0e..42572652 100644
--- a/core/notifier/notifiers.go
+++ b/core/notifier/notifiers.go
@@ -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() {
diff --git a/handlers/doc.go b/handlers/doc.go
new file mode 100644
index 00000000..52de107d
--- /dev/null
+++ b/handlers/doc.go
@@ -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
diff --git a/handlers/routes.go b/handlers/routes.go
index 2b3927f5..f46aa8ec 100644
--- a/handlers/routes.go
+++ b/handlers/routes.go
@@ -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")
diff --git a/notifiers/discord.go b/notifiers/discord.go
index db4552da..dadda8da 100644
--- a/notifiers/discord.go
+++ b/notifiers/discord.go
@@ -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)
}
diff --git a/notifiers/discord_test.go b/notifiers/discord_test.go
new file mode 100644
index 00000000..21d25b8e
--- /dev/null
+++ b/notifiers/discord_test.go
@@ -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))
+ })
+
+}
diff --git a/notifiers/doc.go b/notifiers/doc.go
new file mode 100644
index 00000000..eae5662a
--- /dev/null
+++ b/notifiers/doc.go
@@ -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
diff --git a/notifiers/email.go b/notifiers/email.go
index 8c4ace9c..50b1b482 100644
--- a/notifiers/email.go
+++ b/notifiers/email.go
@@ -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
}
diff --git a/notifiers/email_test.go b/notifiers/email_test.go
new file mode 100644
index 00000000..a13f0731
--- /dev/null
+++ b/notifiers/email_test.go
@@ -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))
+ })
+
+}
diff --git a/notifiers/emails/failure.html b/notifiers/emails/failure.html
deleted file mode 100644
index 2d11e1c4..00000000
--- a/notifiers/emails/failure.html
+++ /dev/null
@@ -1,69 +0,0 @@
-
-
-
-
-
- Sample Email
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ .Service.Name }} is Offline!
-
- Your Statup service '{{.Service.Name}}' 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}}.
-
-
- {{if .Service.LastResponse }}
- Last Response
-
- {{ .Service.LastResponse }}
-
- {{end}}
-
-
- |
-
-
- |
-
-
- |
-
-
-
-
\ No newline at end of file
diff --git a/notifiers/emails/message.html b/notifiers/emails/message.html
deleted file mode 100644
index 10d861e4..00000000
--- a/notifiers/emails/message.html
+++ /dev/null
@@ -1,61 +0,0 @@
-
-
-
-
-
- Sample Email
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Looks Like Emails Work!
-
- Since you got this email, it confirms that your Statup Status Page email system is working correctly.
-
-
-
- Enjoy using Statup!
- Statup.io Team
-
-
- |
-
-
- |
-
-
- |
-
-
-
-
\ No newline at end of file
diff --git a/notifiers/notifiers_test.go b/notifiers/notifiers_test.go
new file mode 100644
index 00000000..2e527b37
--- /dev/null
+++ b/notifiers/notifiers_test.go
@@ -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(¬ifier.Notification{})
+ notifier.SetDB(db)
+}
diff --git a/notifiers/slack.go b/notifiers/slack.go
index 85723c07..c84add49 100644
--- a/notifiers/slack.go
+++ b/notifiers/slack.go
@@ -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{¬ifier.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
diff --git a/notifiers/slack_test.go b/notifiers/slack_test.go
new file mode 100644
index 00000000..fa2b8804
--- /dev/null
+++ b/notifiers/slack_test.go
@@ -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))
+ })
+
+}
diff --git a/notifiers/twilio.go b/notifiers/twilio.go
index 549df975..141228ce 100644
--- a/notifiers/twilio.go
+++ b/notifiers/twilio.go
@@ -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{¬ifier.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"`
+}
diff --git a/notifiers/twilio_test.go b/notifiers/twilio_test.go
new file mode 100644
index 00000000..8736e911
--- /dev/null
+++ b/notifiers/twilio_test.go
@@ -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))
+ })
+
+}
diff --git a/source/doc.go b/source/doc.go
new file mode 100644
index 00000000..b48bf193
--- /dev/null
+++ b/source/doc.go
@@ -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
diff --git a/source/tmpl/settings.html b/source/tmpl/settings.html
index 9ca577d5..e88a8d36 100644
--- a/source/tmpl/settings.html
+++ b/source/tmpl/settings.html
@@ -32,7 +32,7 @@
Settings
Theme Editor
{{ range .Notifications }}
- {{.Select.Method}}
+ {{.Select.Method}}
{{ end }}
Browse Plugins
Backups
diff --git a/types/core.go b/types/core.go
index 7e6caccd..43daf799 100644
--- a/types/core.go
+++ b/types/core.go
@@ -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"`
diff --git a/types/doc.go b/types/doc.go
new file mode 100644
index 00000000..e968d183
--- /dev/null
+++ b/types/doc.go
@@ -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
diff --git a/utils/doc.go b/utils/doc.go
new file mode 100644
index 00000000..6cf016a6
--- /dev/null
+++ b/utils/doc.go
@@ -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