pull/429/head
Hunter Long 4 years ago
parent f62dcd4336
commit e0288b21a4

@ -11,12 +11,10 @@ An easy to use Status Page for your websites and applications. Statping will aut
[![GoDoc](https://godoc.org/github.com/golang/gddo?status.svg)](https://godoc.org/github.com/statping/statping) [![Gitter](https://img.shields.io/gitter/room/nwjs/nw.js.svg)](https://gitter.im/statping/general) [![](https://images.microbadger.com/badges/image/hunterlong/statping.svg)](https://microbadger.com/images/hunterlong/statping) [![Docker Pulls](https://img.shields.io/docker/pulls/hunterlong/statping.svg)](https://hub.docker.com/r/hunterlong/statping/builds/)
## A Future-Proof Status Page
Statping strives to remain future-proof and remain intact if a failure is created. Your Statping service should not be running on the same instance you're trying to monitor. If your server crashes your Status Page should still remaining online to notify your users of downtime.
<img align="left" width="320" height="235" src="https://img.cjx.io/statupsiterun.gif">
<p align="center">
<img width="80%" src="https://img.cjx.io/statupsiterun.gif">
</p>
<h2>A Future-Proof Status Page</h2>
Statping strives to remain future-proof and remain intact if a failure is created. Your Statping service should not be running on the same instance you're trying to monitor. If your server crashes your Status Page should still remaining online to notify your users of downtime.
## Lightweight and Fast
Statping is a very lightweight application and is available for Linux, Mac, and Windows. The Docker image is only ~16Mb so you know that this application won't be filling up your hard drive space.

@ -28,7 +28,6 @@ import (
"github.com/statping/statping/source"
"github.com/pkg/errors"
"github.com/statping/statping/database"
"github.com/statping/statping/handlers"
"github.com/statping/statping/types/configs"
"github.com/statping/statping/types/core"
@ -47,6 +46,8 @@ var (
port int
log = utils.Log.WithField("type", "cmd")
httpServer = make(chan bool)
confgs *configs.DbConfig
)
func init() {
@ -119,29 +120,29 @@ func main() {
log.Errorln(err)
}
c, err := configs.LoadConfigs()
confgs, err = configs.LoadConfigs()
if err != nil {
if err := SetupMode(); err != nil {
exit(err)
}
}
if err = configs.ConnectConfigs(c); err != nil {
if err = configs.ConnectConfigs(confgs); err != nil {
exit(err)
}
exists := database.DB().HasTable("core")
exists := confgs.Db.HasTable("core")
if !exists {
if err := c.DropDatabase(); err != nil {
if err := confgs.DropDatabase(); err != nil {
exit(errors.Wrap(err, "error dropping database"))
}
if err := configs.CreateDatabase(); err != nil {
if err := confgs.CreateDatabase(); err != nil {
exit(errors.Wrap(err, "error creating database"))
}
if err := configs.CreateAdminUser(c); err != nil {
if err := configs.CreateAdminUser(confgs); err != nil {
exit(errors.Wrap(err, "error creating default admin user"))
}
@ -151,7 +152,7 @@ func main() {
}
if err := c.MigrateDatabase(); err != nil {
if err := confgs.MigrateDatabase(); err != nil {
exit(err)
}
@ -170,7 +171,7 @@ func main() {
func Close() {
sentry.Flush(3 * time.Second)
utils.CloseLogs()
database.Close()
confgs.Close()
}
func SetupMode() error {

@ -18,9 +18,7 @@ const (
TIME_DAY = "2006-01-02"
)
var (
database Database
)
var database Database
// Database is an interface which DB implements
type Database interface {
@ -112,39 +110,35 @@ type Database interface {
DbType() string
}
func DB() Database {
return database
}
func (it *Db) DbType() string {
return it.Database.Dialect().GetName()
}
func Close() error {
if database == nil {
func Close(db Database) error {
if db == nil {
return nil
}
return database.Close()
return db.Close()
}
func LogMode(b bool) Database {
return database.LogMode(b)
func LogMode(db Database, b bool) Database {
return db.LogMode(b)
}
func Begin(model interface{}) Database {
func Begin(db Database, model interface{}) Database {
if all, ok := model.(string); ok {
if all == "migration" {
return database.Begin()
return db.Begin()
}
}
return database.Model(model).Begin()
return db.Model(model).Begin()
}
func Available() bool {
if database == nil {
func Available(db Database) bool {
if db == nil {
return false
}
if err := database.DB().Ping(); err != nil {
if err := db.DB().Ping(); err != nil {
return false
}
return true
@ -179,11 +173,15 @@ func Openw(dialect string, args ...interface{}) (db Database, err error) {
if err != nil {
return nil, err
}
db = Wrap(gormdb)
database = db
database = Wrap(gormdb)
return database, err
}
func OpenTester() (Database, error) {
newDb, err := Openw("sqlite3", ":memory:?cache=shared")
return newDb, err
}
// Wrap wraps gorm.DB in an interface
func Wrap(db *gorm.DB) Database {
return &Db{

@ -50,11 +50,11 @@ var (
ByAverage = func(column string, multiplier int) By {
switch database.DbType() {
case "mysql":
return By(fmt.Sprintf("CAST(AVG(%s)*%d as UNSIGNED) as amount", column, multiplier))
return By(fmt.Sprintf("CAST(AVG(%s) as UNSIGNED) as amount", column))
case "postgres":
return By(fmt.Sprintf("cast(AVG(%s)*%d as int) as amount", column, multiplier))
return By(fmt.Sprintf("cast(AVG(%s) as int) as amount", column))
default:
return By(fmt.Sprintf("cast(AVG(%s)*%d as int) as amount", column, multiplier))
return By(fmt.Sprintf("cast(AVG(%s) as int) as amount", column))
}
}
)
@ -157,7 +157,7 @@ func ParseQueries(r *http.Request, o isObject) *GroupQuery {
limit = 10000
}
db := o.Db()
q := o.Db()
if grouping == "" {
grouping = "1h"
@ -176,7 +176,7 @@ func ParseQueries(r *http.Request, o isObject) *GroupQuery {
Limit: int(limit),
Offset: int(offset),
FillEmpty: fill,
db: db,
db: q,
}
if startField == 0 {
@ -190,18 +190,18 @@ func ParseQueries(r *http.Request, o isObject) *GroupQuery {
}
if query.Limit != 0 {
db = db.Limit(query.Limit)
q = q.Limit(query.Limit)
}
if query.Offset > 0 {
db = db.Offset(query.Offset)
q = q.Offset(query.Offset)
}
db = db.Where("created_at BETWEEN ? AND ?", db.FormatTime(query.Start), db.FormatTime(query.End))
q = q.Where("created_at BETWEEN ? AND ?", q.FormatTime(query.Start), q.FormatTime(query.End))
if query.Order != "" {
db = db.Order(query.Order)
q = q.Order(query.Order)
}
query.db = db
query.db = q
return query
}

@ -59,8 +59,8 @@ func databaseMaintence(dur time.Duration) {
// DeleteAllSince will delete a specific table's records based on a time.
func DeleteAllSince(table string, date time.Time) {
sql := fmt.Sprintf("DELETE FROM %v WHERE created_at < '%v';", table, database.FormatTime(date))
db := database.Exec(sql)
if db.Error() != nil {
log.Warnln(db.Error())
q := database.Exec(sql)
if q.Error() != nil {
log.Warnln(q.Error())
}
}

@ -5,6 +5,22 @@ HTML,BODY {
background-color: $background-color;
}
.chartmarker {
background-color: white;
padding: 5px;
}
.chartmarker SPAN {
font-size: 11pt;
display: block;
color: #8b8b8b;
}
.apexcharts-tooltip {
box-shadow: none;
}
.contain-card {
.card-header {

@ -4,19 +4,20 @@
<script>
import Api from "../../API"
const timeoptions = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric' };
const axisOptions = {
labels: {
show: false
},
crosshairs: {
show: false
show: true
},
lines: {
show: false
},
tooltip: {
enabled: false
enabled: true
},
axisTicks: {
show: false
@ -25,7 +26,7 @@
show: false
},
marker: {
show: false
show: true
}
};
@ -47,6 +48,9 @@
showing: false,
data: [],
chartOptions: {
noData: {
text: 'Loading...'
},
chart: {
height: 210,
width: "100%",
@ -76,21 +80,47 @@
left: -10,
}
},
dropShadow: {
enabled: false,
},
xaxis: {
type: "datetime",
...axisOptions
labels: {
show: false
},
},
yaxis: {
...axisOptions
labels: {
show: false
},
},
tooltip: {
enabled: false,
marker: {
show: false,
theme: false,
enabled: true,
markers: {
size: 0
},
custom: function({series, seriesIndex, dataPointIndex, w}) {
let service = w.globals.seriesNames[0];
let ts = w.globals;
window.console.log(ts);
let val = series[seriesIndex][dataPointIndex];
if (val > 1000) {
val = (val * 0.1).toFixed(0) + " milliseconds"
} else {
val = (val * 0.01).toFixed(0) + " microseconds"
}
return `<div class="chartmarker"><span>${service} Average Response</span> <span class="font-3">${val}</span></div>`
},
fixed: {
enabled: true,
position: 'topRight',
offsetX: -30,
offsetY: 0,
},
x: {
show: false,
}
},
},
legend: {
show: false,
@ -132,6 +162,7 @@
},
methods: {
async chartHits(group) {
window.console.log(this.service.created_at)
this.data = await Api.service_hits(this.service.id, this.toUnix(this.service.created_at), this.toUnix(new Date()), group, false)
if (this.data.length === 0 && group !== "1h") {
@ -139,7 +170,7 @@
}
this.series = [{
name: this.service.name,
...this.convertToChartData(this.data)
...this.convertToChartData(this.data, 0.01)
}]
this.ready = true
}

@ -127,13 +127,13 @@ func apiClearCacheHandler(w http.ResponseWriter, r *http.Request) {
returnJson(output, w, r)
}
func sendErrorJson(err error, w http.ResponseWriter, r *http.Request) {
func sendErrorJson(err error, w http.ResponseWriter, r *http.Request, statusCode ...int) {
log.Warnln(fmt.Errorf("sending error response for %v: %v", r.URL.String(), err.Error()))
output := apiResponse{
Status: "error",
Error: err.Error(),
}
returnJson(output, w, r)
returnJson(output, w, r, statusCode...)
}
func sendJsonAction(obj interface{}, method string, w http.ResponseWriter, r *http.Request) {

@ -1,5 +1,3 @@
// +build int
package handlers
import (

@ -1,5 +1,3 @@
// +build int
package handlers
import (

@ -43,7 +43,7 @@ func apiAllGroupHandler(r *http.Request) interface{} {
func apiGroupHandler(w http.ResponseWriter, r *http.Request) {
group, err := selectGroup(r)
if err != nil {
sendErrorJson(errors.Wrap(err, "group not found"), w, r)
sendErrorJson(errors.Wrap(err, "group not found"), w, r, http.StatusNotFound)
return
}
returnJson(group, w, r)

@ -1,5 +1,3 @@
// +build int
package handlers
import (

@ -286,8 +286,12 @@ func executeJSResponse(w http.ResponseWriter, r *http.Request, file string, data
//}
}
func returnJson(d interface{}, w http.ResponseWriter, r *http.Request) {
func returnJson(d interface{}, w http.ResponseWriter, r *http.Request, statusCode ...int) {
w.Header().Set("Content-Type", "application/json")
if len(statusCode) != 0 {
code := statusCode[0]
w.WriteHeader(code)
}
json.NewEncoder(w).Encode(d)
}

@ -1,5 +1,3 @@
// +build int
package handlers
import (

@ -1,5 +1,3 @@
// +build int
package handlers
import (
@ -10,7 +8,7 @@ import (
)
func TestAttachment(t *testing.T) {
err := notifiers.AttachNotifiers()
err := notifiers.Migrate()
require.Nil(t, err)
}

@ -91,14 +91,14 @@ func prometheusHandler(w http.ResponseWriter, r *http.Request) {
PrometheusComment(fmt.Sprintf("Service #%d '%s':", ser.Id, ser.Name))
PrometheusExportKey("service_failures", id, name, ser.AllFailures().Count())
PrometheusExportKey("service_latency", id, name, ser.Latency*100)
PrometheusExportKey("service_latency", id, name, ser.Latency)
PrometheusExportKey("service_online", id, name, online)
PrometheusExportKey("service_status_code", id, name, ser.LastStatusCode)
PrometheusExportKey("service_response_length", id, name, len([]byte(ser.LastResponse)))
PrometheusExportKey("service_ping_time", id, name, ser.PingTime)
PrometheusExportKey("service_last_latency", id, name, ser.LastLatency)
PrometheusExportKey("service_last_lookup", id, name, ser.LastLookupTime)
PrometheusExportKey("service_last_check", id, name, time.Now().Sub(ser.LastCheck).Milliseconds())
PrometheusExportKey("service_last_check", id, name, utils.Now().Sub(ser.LastCheck).Milliseconds())
//PrometheusExportKey("service_online_seconds", id, name, ser.SecondsOnline)
//PrometheusExportKey("service_offline_seconds", id, name, ser.SecondsOffline)

@ -89,11 +89,11 @@ func apiCreateServiceHandler(w http.ResponseWriter, r *http.Request) {
func apiServiceUpdateHandler(w http.ResponseWriter, r *http.Request) {
service, err := serviceByID(r)
if err != nil {
sendErrorJson(err, w, r)
sendErrorJson(err, w, r, http.StatusNotFound)
return
}
if err := DecodeJSON(r, &service); err != nil {
sendErrorJson(err, w, r)
sendErrorJson(err, w, r, http.StatusBadRequest)
return
}

@ -1,5 +1,3 @@
// +build int
package handlers
import (

@ -17,7 +17,6 @@ package handlers
import (
"errors"
"github.com/statping/statping/database"
"github.com/statping/statping/notifiers"
"github.com/statping/statping/types/configs"
"github.com/statping/statping/types/core"
@ -62,14 +61,14 @@ func processSetupHandler(w http.ResponseWriter, r *http.Request) {
return
}
exists := database.DB().HasTable("core")
exists := confgs.Db.HasTable("core")
if !exists {
if err := confgs.DropDatabase(); err != nil {
sendErrorJson(err, w, r)
return
}
if err := configs.CreateDatabase(); err != nil {
if err := confgs.CreateDatabase(); err != nil {
sendErrorJson(err, w, r)
return
}

@ -1,5 +1,3 @@
// +build int
package handlers
import (

@ -20,9 +20,9 @@ package source
import (
"fmt"
"github.com/GeertJohan/go.rice"
"github.com/statping/statping/utils"
"github.com/pkg/errors"
"github.com/russross/blackfriday/v2"
"github.com/statping/statping/utils"
"os"
"path/filepath"
"strings"

@ -0,0 +1,117 @@
package checkins
import (
"github.com/statping/statping/database"
"github.com/statping/statping/types/failures"
"github.com/statping/statping/utils"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"testing"
"time"
)
var testCheckin = &Checkin{
ServiceId: 1,
Name: "Test Checkin",
Interval: 60,
GracePeriod: 10,
ApiKey: "tHiSiSaTeStXXX",
CreatedAt: utils.Now(),
UpdatedAt: utils.Now(),
LastHitTime: utils.Now().Add(-15 * time.Second),
}
var testCheckinHits = []*CheckinHit{{
Checkin: 1,
From: "0.0.0.0.0",
CreatedAt: utils.Now().Add(-30 * time.Second),
}, {
Checkin: 2,
From: "0.0.0.0",
CreatedAt: utils.Now().Add(-180 * time.Second),
}}
var testApiKey string
func TestInit(t *testing.T) {
db, err := database.OpenTester()
require.Nil(t, err)
db.AutoMigrate(&Checkin{}, &CheckinHit{}, &failures.Failure{})
db.Create(&testCheckin)
for _, v := range testCheckinHits {
db.Create(&v)
}
assert.True(t, db.HasTable(&Checkin{}))
assert.True(t, db.HasTable(&CheckinHit{}))
assert.True(t, db.HasTable(&failures.Failure{}))
SetDB(db)
}
func TestFind(t *testing.T) {
item, err := Find(1)
require.Nil(t, err)
assert.Equal(t, "Test Checkin", item.Name)
assert.NotEmpty(t, item.ApiKey)
testApiKey = item.ApiKey
}
func TestFindByAPI(t *testing.T) {
item, err := FindByAPI(testApiKey)
require.Nil(t, err)
assert.Equal(t, "Test Checkin", item.Name)
}
func TestAll(t *testing.T) {
items := All()
assert.Len(t, items, 1)
}
func TestCreate(t *testing.T) {
example := &Checkin{
Name: "Example 2",
}
err := example.Create()
example.Close()
require.Nil(t, err)
assert.NotZero(t, example.Id)
assert.Equal(t, "Example 2", example.Name)
assert.NotZero(t, example.CreatedAt)
assert.NotEmpty(t, example.ApiKey)
}
func TestUpdate(t *testing.T) {
item, err := Find(1)
require.Nil(t, err)
item.Name = "Updated"
err = item.Update()
require.Nil(t, err)
assert.Equal(t, "Updated", item.Name)
item.Close()
}
func TestCheckin_Expected(t *testing.T) {
item, err := Find(1)
require.Nil(t, err)
expected := item.Expected()
assert.GreaterOrEqual(t, expected.Seconds(), float64(29))
}
func TestDelete(t *testing.T) {
all := All()
assert.Len(t, all, 2)
item, err := Find(2)
require.Nil(t, err)
err = item.Delete()
require.Nil(t, err)
all = All()
assert.Len(t, all, 1)
}
func TestClose(t *testing.T) {
assert.Nil(t, db.Close())
}

@ -5,48 +5,57 @@ import (
"github.com/statping/statping/utils"
)
func DB() database.Database {
return database.DB().Model(&Checkin{})
}
var db database.Database
var dbHits database.Database
func DBhits() database.Database {
return database.DB().Model(&CheckinHit{})
func SetDB(database database.Database) {
db = database.Model(&Checkin{})
dbHits = database.Model(&CheckinHit{})
}
func Find(id int64) (*Checkin, error) {
var checkin Checkin
db := DB().Where("id = ?", id).Find(&checkin)
return &checkin, db.Error()
q := db.Where("id = ?", id).Find(&checkin)
return &checkin, q.Error()
}
func FindByAPI(key string) (*Checkin, error) {
var checkin Checkin
db := DB().Where("api = ?", key).Find(&checkin)
return &checkin, db.Error()
q := db.Where("api_key = ?", key).Find(&checkin)
return &checkin, q.Error()
}
func All() []*Checkin {
var checkins []*Checkin
DB().Find(&checkins)
db.Find(&checkins)
return checkins
}
func (c *Checkin) Create() error {
c.ApiKey = utils.RandomString(7)
db := DB().Create(c)
q := db.Create(c)
c.Start()
go c.CheckinRoutine()
return db.Error()
return q.Error()
}
func (c *Checkin) Update() error {
db := DB().Update(c)
return db.Error()
q := db.Update(c)
return q.Error()
}
func (c *Checkin) Delete() error {
c.Close()
db := DB().Delete(c)
return db.Error()
}
q := dbHits.Where("checkin = ?", c.Id).Delete(&CheckinHit{})
if err := q.Error(); err != nil {
return err
}
q = db.Model(&Checkin{}).Delete(c)
return q.Error()
}
//func (c *Checkin) AfterDelete() error {
// //q := dbHits.Where("checkin = ?", c.Id).Delete(&CheckinHit{})
// return q.Error()
//}

@ -2,28 +2,28 @@ package checkins
func (c *Checkin) LastHit() *CheckinHit {
var hit CheckinHit
DBhits().Where("checkin = ?", c.Id).Limit(1).Find(&hit)
dbHits.Where("checkin = ?", c.Id).Limit(1).Find(&hit)
return &hit
}
func (c *Checkin) Hits() []*CheckinHit {
var hits []*CheckinHit
DBhits().Where("checkin = ?", c.Id).Find(&hits)
dbHits.Where("checkin = ?", c.Id).Find(&hits)
c.AllHits = hits
return hits
}
func (c *CheckinHit) Create() error {
db := DBhits().Create(c)
return db.Error()
q := dbHits.Create(c)
return q.Error()
}
func (c *CheckinHit) Update() error {
db := DBhits().Update(c)
return db.Error()
q := dbHits.Update(c)
return q.Error()
}
func (c *CheckinHit) Delete() error {
db := DBhits().Delete(c)
return db.Error()
q := dbHits.Delete(c)
return q.Error()
}

@ -7,7 +7,7 @@ import (
func (c *Checkin) CreateFailure(f *failures.Failure) error {
f.Checkin = c.Id
return failures.DB().Create(&f).Error()
return failures.DB().Create(f).Error()
}
func (c *Checkin) FailuresColumnID() (string, int64) {
@ -19,5 +19,5 @@ func (c *Checkin) Failures() failures.Failurer {
}
func (c *Checkin) FailuresSince(t time.Time) failures.Failurer {
return failures.FailuresSince(t, c)
return failures.Since(t, c)
}

@ -0,0 +1,3 @@
package checkins
//go:generate counterfeiter . Checkin

@ -47,7 +47,7 @@ CheckinLoop:
Method: "checkin",
Service: c.ServiceId,
Checkin: c.Id,
PingTime: c.Expected().Seconds(),
PingTime: c.Expected().Milliseconds(),
CreatedAt: time.Time{},
}

@ -1,7 +1,6 @@
package checkins
import (
"github.com/statping/statping/database"
"github.com/statping/statping/types/failures"
"github.com/statping/statping/utils"
"time"
@ -32,24 +31,7 @@ type CheckinHit struct {
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
}
// BeforeCreate for checkinHit will set CreatedAt to UTC
func (c *CheckinHit) BeforeCreate() (err error) {
if c.CreatedAt.IsZero() {
c.CreatedAt = time.Now().UTC()
}
return
}
func (c *Checkin) BeforeCreate() (err error) {
c.ApiKey = utils.RandomString(7)
if c.CreatedAt.IsZero() {
c.CreatedAt = time.Now().UTC()
c.UpdatedAt = time.Now().UTC()
}
return
}
func (c *Checkin) BeforeDelete(tx database.Database) (err error) {
return tx.Where("id = ?", c.ServiceId).
Update("group_id", 0).Error()
return nil
}

@ -5,7 +5,16 @@ import (
"github.com/jinzhu/gorm"
"github.com/pkg/errors"
"github.com/statping/statping/database"
"github.com/statping/statping/types/checkins"
"github.com/statping/statping/types/core"
"github.com/statping/statping/types/failures"
"github.com/statping/statping/types/groups"
"github.com/statping/statping/types/hits"
"github.com/statping/statping/types/incidents"
"github.com/statping/statping/types/messages"
"github.com/statping/statping/types/notifications"
"github.com/statping/statping/types/null"
"github.com/statping/statping/types/services"
"github.com/statping/statping/types/users"
"github.com/statping/statping/utils"
"os"
@ -15,9 +24,6 @@ import (
// Connect will attempt to connect to the sqlite, postgres, or mysql database
func Connect(configs *DbConfig, retry bool) error {
postgresSSL := os.Getenv("POSTGRES_SSLMODE")
if database.Available() {
return nil
}
var conn string
var err error
@ -25,7 +31,7 @@ func Connect(configs *DbConfig, retry bool) error {
case "sqlite", "sqlite3", "memory":
if configs.DbConn == "memory" {
conn = "sqlite3"
configs.DbConn = ":memory"
configs.DbConn = ":memory:"
} else {
conn = findDbFile(configs)
configs.SqlFile = conn
@ -76,14 +82,31 @@ func Connect(configs *DbConfig, retry bool) error {
if dbSession.DB().Ping() == nil {
if utils.VerboseMode >= 4 {
database.LogMode(true).Debug().SetLogger(gorm.Logger{log})
dbSession.LogMode(true).Debug().SetLogger(gorm.Logger{log})
}
log.Infoln(fmt.Sprintf("Database %v connection was successful.", configs.DbConn))
}
configs.Db = dbSession
initModels(configs.Db)
return err
}
func initModels(db database.Database) {
core.SetDB(db)
services.SetDB(db)
hits.SetDB(db)
failures.SetDB(db)
checkins.SetDB(db)
notifications.SetDB(db)
incidents.SetDB(db)
users.SetDB(db)
messages.SetDB(db)
groups.SetDB(db)
}
func CreateAdminUser(configs *DbConfig) error {
log.Infoln(fmt.Sprintf("Core database does not exist, creating now!"))

@ -76,7 +76,7 @@ func (d *DbConfig) DropDatabase() error {
var DbModels = []interface{}{&services.Service{}, &users.User{}, &hits.Hit{}, &failures.Failure{}, &messages.Message{}, &groups.Group{}, &checkins.Checkin{}, &checkins.CheckinHit{}, &notifications.Notification{}, &incidents.Incident{}, &incidents.IncidentUpdate{}}
log.Infoln("Dropping Database Tables...")
for _, t := range DbModels {
if err := database.DB().DropTableIfExists(t); err != nil {
if err := d.Db.DropTableIfExists(t); err != nil {
return err.Error()
}
log.Infof("Dropped table: %T\n", t)
@ -84,19 +84,23 @@ func (d *DbConfig) DropDatabase() error {
return nil
}
func (d *DbConfig) Close() {
d.Db.Close()
}
// CreateDatabase will CREATE TABLES for each of the Statping elements
func CreateDatabase() error {
func (d *DbConfig) CreateDatabase() error {
var err error
var DbModels = []interface{}{&services.Service{}, &users.User{}, &hits.Hit{}, &failures.Failure{}, &messages.Message{}, &groups.Group{}, &checkins.Checkin{}, &checkins.CheckinHit{}, &notifications.Notification{}, &incidents.Incident{}, &incidents.IncidentUpdate{}}
log.Infoln("Creating Database Tables...")
for _, table := range DbModels {
if err := database.DB().CreateTable(table); err.Error() != nil {
if err := d.Db.CreateTable(table); err.Error() != nil {
return err.Error()
}
}
if err := database.DB().Table("core").CreateTable(&core.Core{}); err.Error() != nil {
if err := d.Db.Table("core").CreateTable(&core.Core{}); err.Error() != nil {
return err.Error()
}
log.Infoln("Statping Database Created")

@ -2,7 +2,6 @@ package configs
import (
"fmt"
"github.com/statping/statping/database"
"github.com/statping/statping/types/checkins"
"github.com/statping/statping/types/core"
"github.com/statping/statping/types/failures"
@ -51,7 +50,7 @@ func (c *DbConfig) MigrateDatabase() error {
var DbModels = []interface{}{&services.Service{}, &users.User{}, &hits.Hit{}, &failures.Failure{}, &messages.Message{}, &groups.Group{}, &checkins.Checkin{}, &checkins.CheckinHit{}, &notifications.Notification{}, &incidents.Incident{}, &incidents.IncidentUpdate{}}
log.Infoln("Migrating Database Tables...")
tx := database.Begin("migration")
tx := c.Db.Begin()
defer func() {
if r := recover(); r != nil {
tx.Rollback()
@ -75,19 +74,19 @@ func (c *DbConfig) MigrateDatabase() error {
}
log.Infoln("Statping Database Tables Migrated")
if err := database.DB().Model(&hits.Hit{}).AddIndex("idx_service_hit", "service").Error(); err != nil {
if err := c.Db.Model(&hits.Hit{}).AddIndex("idx_service_hit", "service").Error(); err != nil {
log.Errorln(err)
}
if err := database.DB().Model(&hits.Hit{}).AddIndex("hit_created_at", "created_at").Error(); err != nil {
if err := c.Db.Model(&hits.Hit{}).AddIndex("hit_created_at", "created_at").Error(); err != nil {
log.Errorln(err)
}
if err := database.DB().Model(&failures.Failure{}).AddIndex("idx_service_fail", "service").Error(); err != nil {
if err := c.Db.Model(&failures.Failure{}).AddIndex("idx_service_fail", "service").Error(); err != nil {
log.Errorln(err)
}
if err := database.DB().Model(&failures.Failure{}).AddIndex("idx_checkin_fail", "checkin").Error(); err != nil {
if err := c.Db.Model(&failures.Failure{}).AddIndex("idx_checkin_fail", "checkin").Error(); err != nil {
log.Errorln(err)
}
log.Infoln("Database Indexes Created")

@ -1,13 +1,5 @@
package configs
import (
"github.com/romanyx/polluter"
"github.com/statping/statping/database"
"github.com/statping/statping/utils"
"os"
"testing"
)
//func preparePostgresDB(t *testing.T) (database.Database, error) {
// dbName := fmt.Sprintf("db_%d", time.Now().UnixNano())
// db, err := database.Openw("sqlite3", dbName)
@ -17,20 +9,3 @@ import (
//
// return db, db.Error()
//}
func TestSeedDatabase(t *testing.T) {
t.SkipNow()
dir := utils.Directory
f, err := os.Open(dir + "/testdata.yml")
if err != nil {
t.Fatal(err)
}
defer f.Close()
p := polluter.New(polluter.PostgresEngine(database.DB().DB()))
if err := p.Pollute(f); err != nil {
t.Fatalf("failed to pollute: %s", err)
}
}

@ -1,5 +1,7 @@
package configs
import "github.com/statping/statping/database"
const SqliteFilename = "statping.db"
// DbConfig struct is used for the Db connection and creates the 'config.yml' file
@ -23,4 +25,6 @@ type DbConfig struct {
SqlFile string `yaml:"sqlfile,omitempty" json:"-"`
LocalIP string `yaml:"-" json:"-"`
filename string `yaml:"-" json:"-"`
Db database.Database `yaml:"-" json:"-"`
}

@ -9,27 +9,29 @@ import (
"time"
)
func DB() database.Database {
return database.DB().Table("core")
var db database.Database
func SetDB(database database.Database) {
db = database.Model(&Core{})
}
func Select() (*Core, error) {
var c Core
// SelectCore will return the CoreApp global variable and the settings/configs for Statping
if !database.Available() {
if err := db.DB().Ping(); err != nil {
return nil, errors.New("database has not been initiated yet.")
}
exists := DB().HasTable("core")
exists := db.HasTable("core")
if !exists {
return nil, errors.New("core database has not been setup yet.")
}
db := DB().Find(&c)
if db.Error() != nil {
q := db.Find(&c)
if q.Error() != nil {
return nil, db.Error()
}
App = &c
App.UseCdn = null.NewNullBool(os.Getenv("USE_CDN") == "true")
return App, db.Error()
return App, q.Error()
}
func (c *Core) Create() error {
@ -44,13 +46,13 @@ func (c *Core) Create() error {
Domain: c.Domain,
MigrationId: time.Now().Unix(),
}
db := DB().Create(&newCore)
return db.Error()
q := db.Create(&newCore)
return q.Error()
}
func (c *Core) Update() error {
db := DB().Update(c)
return db.Error()
q := db.Update(c)
return q.Error()
}
func (c *Core) Delete() error {
@ -73,6 +75,6 @@ func Sample() error {
Footer: null.NewNullString(""),
}
db := DB().Create(core)
return db.Error()
q := db.Create(core)
return q.Error()
}

@ -2,33 +2,39 @@ package failures
import "github.com/statping/statping/database"
var db database.Database
func SetDB(database database.Database) {
db = database.Model(&Failure{})
}
func DB() database.Database {
return database.DB().Model(&Failure{})
return db
}
func Find(id int64) (*Failure, error) {
var failure Failure
db := DB().Where("id = ?", id).Find(&failure)
return &failure, db.Error()
q := db.Where("id = ?", id).Find(&failure)
return &failure, q.Error()
}
func All() []*Failure {
var failures []*Failure
DB().Find(&failures)
db.Find(&failures)
return failures
}
func (f *Failure) Create() error {
db := DB().Create(f)
return db.Error()
q := db.Create(f)
return q.Error()
}
func (f *Failure) Update() error {
db := DB().Update(f)
return db.Error()
q := db.Update(f)
return q.Error()
}
func (f *Failure) Delete() error {
db := DB().Delete(f)
return db.Error()
q := db.Delete(f)
return q.Error()
}

@ -18,37 +18,54 @@ func (f Failurer) Db() database.Database {
return f.db
}
func (f Failurer) List() []*Failure {
var fails []*Failure
f.db.Find(&fails)
return fails
func (f Failurer) First() *Failure {
var fail Failure
f.db.Order("id ASC").Limit(1).Find(&fail)
return &fail
}
func (f Failurer) Count() int {
var amount int
f.db.Count(&amount)
return amount
func (f Failurer) Last() *Failure {
var fail Failure
f.db.Order("id DESC").Limit(1).Find(&fail)
return &fail
}
func (f Failurer) Last(amount int) []*Failure {
func (f Failurer) List() []*Failure {
var fails []*Failure
f.db.Limit(amount).Find(&fails)
f.db.Find(&fails)
return fails
}
func (f Failurer) LastAmount(amount int) []*Failure {
var fail []*Failure
f.db.Order("id asc").Limit(amount).Find(&fail)
return fail
}
func (f Failurer) Since(t time.Time) []*Failure {
var fails []*Failure
f.db.Since(t).Find(&fails)
return fails
}
func (f Failurer) Count() int {
var amount int
f.db.Count(&amount)
return amount
}
func (f Failurer) DeleteAll() error {
q := f.db.Delete(&Failure{})
return q.Error()
}
func AllFailures(obj ColumnIDInterfacer) Failurer {
column, id := obj.FailuresColumnID()
return Failurer{DB().Where(fmt.Sprintf("%s = ?", column), id)}
return Failurer{db.Where(fmt.Sprintf("%s = ?", column), id)}
}
func FailuresSince(t time.Time, obj ColumnIDInterfacer) Failurer {
func Since(t time.Time, obj ColumnIDInterfacer) Failurer {
column, id := obj.FailuresColumnID()
timestamp := DB().FormatTime(t)
return Failurer{DB().Where(fmt.Sprintf("%s = ? AND created_at > ?", column), id, timestamp)}
timestamp := db.FormatTime(t)
return Failurer{db.Where(fmt.Sprintf("%s = ? AND created_at > ?", column), id, timestamp)}
}

@ -14,7 +14,7 @@ import (
)
func Samples() error {
tx := DB().Begin()
tx := db.Begin()
sg := new(sync.WaitGroup)
createdAt := utils.Now().Add(-3 * types.Day)

@ -17,18 +17,10 @@ type Failure struct {
ErrorCode int `gorm:"column:error_code" json:"error_code"`
Service int64 `gorm:"index;column:service" json:"-"`
Checkin int64 `gorm:"index;column:checkin" json:"-"`
PingTime float64 `gorm:"column:ping_time" json:"ping"`
PingTime int64 `gorm:"column:ping_time" json:"ping"`
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
}
// BeforeCreate for Failure will set CreatedAt to UTC
func (f *Failure) BeforeCreate() (err error) {
if f.CreatedAt.IsZero() {
f.CreatedAt = time.Now().UTC()
}
return
}
type FailSort []Failure
func (s FailSort) Len() int {

@ -5,35 +5,37 @@ import (
"sort"
)
func DB() database.Database {
return database.DB().Model(&Group{})
var db database.Database
func SetDB(database database.Database) {
db = database.Model(&Group{})
}
func Find(id int64) (*Group, error) {
var group Group
db := DB().Where("id = ?", id).Find(&group)
return &group, db.Error()
q := db.Where("id = ?", id).Find(&group)
return &group, q.Error()
}
func All() []*Group {
var groups []*Group
DB().Find(&groups)
db.Find(&groups)
return groups
}
func (g *Group) Create() error {
db := DB().Create(g)
return db.Error()
q := db.Create(g)
return q.Error()
}
func (g *Group) Update() error {
db := DB().Update(g)
return db.Error()
q := db.Update(g)
return q.Error()
}
func (g *Group) Delete() error {
db := DB().Delete(g)
return db.Error()
q := db.Delete(g)
return q.Error()
}
// SelectGroups returns all groups

@ -0,0 +1,89 @@
package groups
import (
"github.com/statping/statping/database"
"github.com/statping/statping/types/null"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"testing"
)
var example = &Group{
Name: "Example Group",
Public: null.NewNullBool(true),
Order: 1,
}
func TestInit(t *testing.T) {
db, err := database.OpenTester()
require.Nil(t, err)
db.CreateTable(&Group{})
db.Create(&example)
SetDB(db)
}
func TestFind(t *testing.T) {
item, err := Find(1)
require.Nil(t, err)
assert.Equal(t, "Example Group", item.Name)
}
func TestAll(t *testing.T) {
items := All()
assert.Len(t, items, 1)
}
func TestCreate(t *testing.T) {
example := &Group{
Name: "Example 2",
Public: null.NewNullBool(false),
Order: 3,
}
err := example.Create()
require.Nil(t, err)
assert.NotZero(t, example.Id)
assert.Equal(t, "Example 2", example.Name)
assert.NotZero(t, example.CreatedAt)
}
func TestUpdate(t *testing.T) {
item, err := Find(1)
require.Nil(t, err)
item.Name = "Updated"
item.Order = 1
err = item.Update()
require.Nil(t, err)
assert.Equal(t, "Updated", item.Name)
}
func TestSelectGroups(t *testing.T) {
groups := SelectGroups(true, true)
assert.Len(t, groups, 2)
groups = SelectGroups(false, false)
assert.Len(t, groups, 1)
groups = SelectGroups(false, true)
assert.Len(t, groups, 2)
assert.Equal(t, "Updated", groups[0].Name)
assert.Equal(t, "Example 2", groups[1].Name)
}
func TestDelete(t *testing.T) {
all := All()
assert.Len(t, all, 2)
item, err := Find(1)
require.Nil(t, err)
err = item.Delete()
require.Nil(t, err)
all = All()
assert.Len(t, all, 1)
}
func TestClose(t *testing.T) {
assert.Nil(t, db.Close())
}

@ -1,13 +1,12 @@
package groups
import (
"github.com/statping/statping/database"
"github.com/statping/statping/types/services"
)
func (g *Group) Services() []*services.Service {
var services []*services.Service
database.DB().Where("group = ?", g.Id).Find(&services)
db.Where("group = ?", g.Id).Find(&services)
return services
}

@ -7,33 +7,35 @@ import (
var log = utils.Log
func DB() database.Database {
return database.DB().Model(&Hit{})
var db database.Database
func SetDB(database database.Database) {
db = database.Model(&Hit{})
}
func Find(id int64) (*Hit, error) {
var group Hit
db := DB().Where("id = ?", id).Find(&group)
return &group, db.Error()
q := db.Where("id = ?", id).Find(&group)
return &group, q.Error()
}
func All() []*Hit {
var hits []*Hit
DB().Find(&hits)
db.Find(&hits)
return hits
}
func (h *Hit) Create() error {
db := DB().Create(h)
return db.Error()
q := db.Create(h)
return q.Error()
}
func (h *Hit) Update() error {
db := DB().Update(h)
return db.Error()
q := db.Update(h)
return q.Error()
}
func (h *Hit) Delete() error {
db := DB().Delete(h)
return db.Error()
q := db.Delete(h)
return q.Error()
}

@ -42,9 +42,9 @@ func (h Hitters) List() []*Hit {
return hits
}
func (h Hitters) LastCount(amounts int) []*Hit {
func (h Hitters) LastAmount(amount int) []*Hit {
var hits []*Hit
h.db.Order("id asc").Limit(amounts).Find(&hits)
h.db.Order("id asc").Limit(amount).Find(&hits)
return hits
}
@ -54,18 +54,23 @@ func (h Hitters) Count() int {
return count
}
func (h Hitters) DeleteAll() error {
q := h.db.Delete(&Hit{})
return q.Error()
}
func (h Hitters) Sum() float64 {
result := struct {
amount float64
}{0}
h.db.Select("AVG(latency) as amount").Scan(&result).Debug()
h.db.Select("AVG(latency) as amount").Scan(&result)
return result.amount
}
func (h Hitters) Avg() int64 {
func (h Hitters) Avg() float64 {
result := struct {
amount int64
amount float64
}{0}
h.db.Select("AVG(latency) as amount").Scan(&result)
@ -74,11 +79,11 @@ func (h Hitters) Avg() int64 {
func AllHits(obj ColumnIDInterfacer) Hitters {
column, id := obj.HitsColumnID()
return Hitters{DB().Where(fmt.Sprintf("%s = ?", column), id)}
return Hitters{db.Where(fmt.Sprintf("%s = ?", column), id)}
}
func HitsSince(t time.Time, obj ColumnIDInterfacer) Hitters {
func Since(t time.Time, obj ColumnIDInterfacer) Hitters {
column, id := obj.HitsColumnID()
timestamp := DB().FormatTime(t)
return Hitters{DB().Where(fmt.Sprintf("%s = ? AND created_at > ?", column), id, timestamp)}
timestamp := db.FormatTime(t)
return Hitters{db.Where(fmt.Sprintf("%s = ? AND created_at > ?", column), id, timestamp)}
}

@ -16,7 +16,7 @@ import (
var SampleHits = 99900.
func Samples() error {
tx := DB().Begin()
tx := db.Begin()
sg := new(sync.WaitGroup)
for i := int64(1); i <= 5; i++ {
@ -24,7 +24,7 @@ func Samples() error {
if err != nil {
log.Error(err)
}
tx = DB().Begin()
tx = db.Begin()
}
return tx.Error()
@ -44,8 +44,8 @@ func createHitsAt(db database.Database, serviceID int64, sg *sync.WaitGroup) err
hit := &Hit{
Service: serviceID,
Latency: latency,
PingTime: latency * 0.15,
Latency: int64(latency * 10000000),
PingTime: int64(latency * 5000000),
CreatedAt: createdAt,
}

@ -6,8 +6,8 @@ import "time"
type Hit struct {
Id int64 `gorm:"primary_key;column:id" json:"id"`
Service int64 `gorm:"column:service" json:"-"`
Latency float64 `gorm:"column:latency" json:"latency"`
PingTime float64 `gorm:"column:ping_time" json:"ping_time"`
Latency int64 `gorm:"column:latency" json:"latency"`
PingTime int64 `gorm:"column:ping_time" json:"ping_time"`
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
}

@ -2,29 +2,35 @@ package incidents
import "github.com/statping/statping/database"
var db database.Database
func SetDB(database database.Database) {
db = database.Model(&Incident{})
}
func Find(id int64) (*Incident, error) {
var incident Incident
db := database.DB().Model(&Incident{}).Where("id = ?", id).Find(&incident)
return &incident, db.Error()
q := db.Where("id = ?", id).Find(&incident)
return &incident, q.Error()
}
func All() []*Incident {
var incidents []*Incident
database.DB().Model(&Incident{}).Find(&incidents)
db.Find(&incidents)
return incidents
}
func (i *Incident) Create() error {
db := database.DB().Create(i)
return db.Error()
q := db.Create(i)
return q.Error()
}
func (i *Incident) Update() error {
db := database.DB().Update(i)
return db.Error()
q := db.Update(i)
return q.Error()
}
func (i *Incident) Delete() error {
db := database.DB().Delete(i)
return db.Error()
q := db.Delete(i)
return q.Error()
}

@ -1,25 +1,23 @@
package incidents
import "github.com/statping/statping/database"
func (i *Incident) Updates() []*IncidentUpdate {
var updates []*IncidentUpdate
database.DB().Model(&IncidentUpdate{}).Where("incident = ?", i.Id).Find(&updates)
db.Model(&IncidentUpdate{}).Where("incident = ?", i.Id).Find(&updates)
i.AllUpdates = updates
return updates
}
func (i *IncidentUpdate) Create() error {
db := database.DB().Create(i)
return db.Error()
q := db.Create(i)
return q.Error()
}
func (i *IncidentUpdate) Update() error {
db := database.DB().Update(i)
return db.Error()
q := db.Update(i)
return q.Error()
}
func (i *IncidentUpdate) Delete() error {
db := database.DB().Delete(i)
return db.Error()
q := db.Delete(i)
return q.Error()
}

@ -0,0 +1,83 @@
package incidents
import (
"github.com/statping/statping/database"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"testing"
)
var example = &Incident{
Title: "Example",
Description: "No description",
ServiceId: 1,
}
var update1 = &IncidentUpdate{
IncidentId: 1,
Message: "First one here",
Type: "update",
}
var update2 = &IncidentUpdate{
IncidentId: 1,
Message: "Second one here",
Type: "update",
}
func TestInit(t *testing.T) {
db, err := database.OpenTester()
require.Nil(t, err)
db.AutoMigrate(&Incident{}, &IncidentUpdate{})
db.Create(&example)
SetDB(db)
}
func TestFind(t *testing.T) {
item, err := Find(1)
require.Nil(t, err)
assert.Equal(t, "Example", item.Title)
}
func TestAll(t *testing.T) {
items := All()
assert.Len(t, items, 1)
}
func TestCreate(t *testing.T) {
example := &Incident{
Title: "Example 2",
}
err := example.Create()
require.Nil(t, err)
assert.NotZero(t, example.Id)
assert.Equal(t, "Example 2", example.Title)
assert.NotZero(t, example.CreatedAt)
}
func TestUpdate(t *testing.T) {
item, err := Find(1)
require.Nil(t, err)
item.Title = "Updated"
err = item.Update()
require.Nil(t, err)
assert.Equal(t, "Updated", item.Title)
}
func TestDelete(t *testing.T) {
all := All()
assert.Len(t, all, 2)
item, err := Find(1)
require.Nil(t, err)
err = item.Delete()
require.Nil(t, err)
all = All()
assert.Len(t, all, 1)
}
func TestClose(t *testing.T) {
assert.Nil(t, db.Close())
}

@ -2,33 +2,35 @@ package messages
import "github.com/statping/statping/database"
func DB() database.Database {
return database.DB().Model(&Message{})
var db database.Database
func SetDB(database database.Database) {
db = database.Model(&Message{})
}
func Find(id int64) (*Message, error) {
var message Message
db := DB().Where("id = ?", id).Find(&message)
return &message, db.Error()
q := db.Where("id = ?", id).Find(&message)
return &message, q.Error()
}
func All() []*Message {
var messages []*Message
DB().Find(&messages)
db.Find(&messages)
return messages
}
func (m *Message) Create() error {
db := DB().Create(m)
return db.Error()
q := db.Create(m)
return q.Error()
}
func (m *Message) Update() error {
db := DB().Update(m)
return db.Error()
q := db.Update(m)
return q.Error()
}
func (m *Message) Delete() error {
db := DB().Delete(m)
return db.Error()
q := db.Delete(m)
return q.Error()
}

@ -0,0 +1,76 @@
package messages
import (
"github.com/statping/statping/database"
"github.com/statping/statping/utils"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"testing"
"time"
)
var example = &Message{
Title: "Example Message",
Description: "Description here",
StartOn: utils.Now().Add(10 * time.Minute),
EndOn: utils.Now().Add(15 * time.Minute),
ServiceId: 1,
}
func TestInit(t *testing.T) {
db, err := database.OpenTester()
require.Nil(t, err)
db.CreateTable(&Message{})
db.Create(&example)
SetDB(db)
}
func TestFind(t *testing.T) {
item, err := Find(1)
require.Nil(t, err)
assert.Equal(t, "Example Message", item.Title)
}
func TestAll(t *testing.T) {
items := All()
assert.Len(t, items, 1)
}
func TestCreate(t *testing.T) {
example := &Message{
Title: "Example 2",
Description: "New Message here",
}
err := example.Create()
require.Nil(t, err)
assert.NotZero(t, example.Id)
assert.Equal(t, "Example 2", example.Title)
assert.NotZero(t, example.CreatedAt)
}
func TestUpdate(t *testing.T) {
item, err := Find(1)
require.Nil(t, err)
item.Title = "Updated"
err = item.Update()
require.Nil(t, err)
assert.Equal(t, "Updated", item.Title)
}
func TestDelete(t *testing.T) {
all := All()
assert.Len(t, all, 2)
item, err := Find(1)
require.Nil(t, err)
err = item.Delete()
require.Nil(t, err)
all = All()
assert.Len(t, all, 1)
}
func TestClose(t *testing.T) {
assert.Nil(t, db.Close())
}

@ -5,19 +5,21 @@ import (
"github.com/statping/statping/database"
)
func DB() database.Database {
return database.DB().Model(&Notification{})
var db database.Database
func SetDB(database database.Database) {
db = database.Model(&Notification{})
}
func Append(n Notifier) {
allNotifiers = append(allNotifiers, n)
}
func Find(name string) (Notifier, error) {
func Find(name string) (*Notification, error) {
for _, n := range allNotifiers {
notif := n.Select()
if notif.Name() == name || notif.Method == name {
return n, nil
return notif, nil
}
}
return nil, errors.New("notifier not found")
@ -29,9 +31,11 @@ func All() []Notifier {
func (n *Notification) Create() error {
var notif Notification
if DB().Where("method = ?", n.Method).Find(&notif).RecordNotFound() {
return DB().Create(n).Error()
if db.Where("method = ?", n.Method).Find(&notif).RecordNotFound() {
Append(n)
return db.Create(n).Error()
}
Append(n)
return nil
}
@ -44,7 +48,7 @@ func (n *Notification) Update() error {
} else {
n.Close()
}
err := DB().Update(n)
err := db.Update(n)
return err.Error()
}

@ -0,0 +1,135 @@
package notifications
import (
"github.com/statping/statping/database"
"github.com/statping/statping/types/null"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"testing"
)
var form1 = NotificationForm{
Type: "text",
Title: "Example Input",
DbField: "Host",
Required: true,
IsHidden: false,
IsList: false,
IsSwitch: false,
}
var form2 = NotificationForm{
Type: "text",
Title: "Example Input 2",
DbField: "ApiKey",
Required: true,
IsHidden: false,
IsList: false,
IsSwitch: false,
}
var example = &exampleNotif{&Notification{
Method: "example",
Enabled: null.NewNullBool(true),
Limits: 3,
Removable: false,
Form: []NotificationForm{form1, form2},
Delay: 30,
}}
type exampleNotif struct {
*Notification
}
func (e *exampleNotif) OnSave() error {
return nil
}
func (e *exampleNotif) Select() *Notification {
return e.Notification
}
func (e *exampleNotif) Send(data interface{}) error {
return nil
}
func TestInit(t *testing.T) {
db, err := database.OpenTester()
require.Nil(t, err)
db.CreateTable(&Notification{})
db.Create(example.Select())
SetDB(db)
}
func TestFind(t *testing.T) {
Append(example)
itemer, err := Find(example.Method)
require.Nil(t, err)
item := itemer.Select()
require.NotNil(t, item)
assert.Equal(t, "example", item.Method)
assert.Len(t, allNotifiers, 1)
}
func TestAll(t *testing.T) {
items := All()
assert.Len(t, items, 1)
assert.Len(t, allNotifiers, 1)
}
func TestCreate(t *testing.T) {
assert.Len(t, allNotifiers, 1)
example := &Notification{
Method: "anotherexample",
Title: "Example 2",
Description: "New Message here",
}
err := example.Create()
require.Nil(t, err)
assert.NotZero(t, example.Id)
assert.Equal(t, "anotherexample", example.Method)
assert.Equal(t, "Example 2", example.Title)
assert.NotZero(t, example.CreatedAt)
items := All()
assert.Len(t, items, 2)
assert.Len(t, allNotifiers, 2)
}
func TestUpdate(t *testing.T) {
itemer, err := Find("anotherexample")
require.Nil(t, err)
require.NotNil(t, itemer)
item := itemer.Select()
require.NotNil(t, item)
item.Host = "Updated Host Var"
err = item.Update()
require.Nil(t, err)
assert.Equal(t, "Updated Host Var", item.Host)
}
func TestDelete(t *testing.T) {
all := All()
assert.Len(t, all, 2)
itemer, err := Find("example2")
require.Nil(t, err)
item := itemer.Select()
require.NotNil(t, item)
err = item.Delete()
require.Nil(t, err)
all = All()
assert.Len(t, all, 2)
}
func TestClose(t *testing.T) {
assert.Nil(t, db.Close())
}

@ -152,7 +152,7 @@ func reverseLogs(input []*NotificationLog) []*NotificationLog {
// SelectNotification returns the Notification struct from the database
func SelectNotification(n Notifier) (*Notification, error) {
notifier := n.Select()
err := DB().Where("method = ?", notifier.Method).Find(&notifier)
err := db.Where("method = ?", notifier.Method).Find(&notifier)
return notifier, err.Error()
}

@ -0,0 +1,29 @@
package null
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestNewNullBool(t *testing.T) {
val := NewNullBool(true)
assert.True(t, val.Bool)
val = NewNullBool(false)
assert.False(t, val.Bool)
}
func TestNewNullInt64(t *testing.T) {
val := NewNullInt64(29)
assert.Equal(t, int64(29), val.Int64)
}
func TestNewNullString(t *testing.T) {
val := NewNullString("statping.com")
assert.Equal(t, "statping.com", val.String)
}
func TestNewNullFloat64(t *testing.T) {
val := NewNullFloat64(42.222)
assert.Equal(t, float64(42.222), val.Float64)
}

@ -1,7 +1,6 @@
package services
import (
"github.com/statping/statping/database"
"github.com/statping/statping/types/checkins"
)
@ -15,6 +14,6 @@ func CheckinProcess(s *Service) {
func (s *Service) Checkins() []*checkins.Checkin {
var chks []*checkins.Checkin
database.DB().Where("service = ?", s.Id).Find(&chks)
db.Where("service = ?", s.Id).Find(&chks)
return chks
}

@ -10,8 +10,10 @@ import (
var log = utils.Log
func DB() database.Database {
return database.DB().Model(&Service{})
var db database.Database
func SetDB(database database.Database) {
db = database.Model(&Service{})
}
func Find(id int64) (*Service, error) {
@ -24,7 +26,7 @@ func Find(id int64) (*Service, error) {
func all() []*Service {
var services []*Service
DB().Find(&services)
db.Find(&services)
return services
}
@ -42,17 +44,21 @@ func AllInOrder() []Service {
}
func (s *Service) Create() error {
err := DB().Create(s)
err := db.Create(s)
if err.Error() != nil {
log.Errorln(fmt.Sprintf("Failed to create service %v #%v: %v", s.Name, s.Id, err))
return err.Error()
}
return nil
}
func (s *Service) AfterCreate() error {
allServices[s.Id] = s
return nil
}
func (s *Service) Update() error {
db := DB().Update(s)
q := db.Update(s)
allServices[s.Id] = s
@ -68,20 +74,49 @@ func (s *Service) Update() error {
//notifier.OnUpdatedService(s.Service)
return db.Error()
return q.Error()
}
func (s *Service) Delete() error {
db := database.DB().Delete(s)
s.Close()
if err := s.DeleteFailures(); err != nil {
return err
}
if err := s.DeleteHits(); err != nil {
return err
}
delete(allServices, s.Id)
//notifier.OnDeletedService(s.Service)
return db.Error()
q := db.Model(&Service{}).Delete(s)
//notifier.OnDeletedService(s.Service)
return q.Error()
}
func (s *Service) DeleteFailures() error {
query := database.DB().Exec(`DELETE FROM failures WHERE service = ?`, s.Id)
return query.Error()
return s.AllFailures().DeleteAll()
}
func (s *Service) DeleteHits() error {
return s.AllHits().DeleteAll()
}
func (s *Service) DeleteCheckins() error {
for _, c := range s.Checkins() {
if err := c.Delete(); err != nil {
return err
}
}
return nil
}
//func (s *Service) AfterDelete() error {
//
// return nil
//}
func (s *Service) AfterFind() error {
s.UpdateStats()
return nil
}

@ -15,26 +15,13 @@ func (s *Service) AllFailures() failures.Failurer {
return failures.AllFailures(s)
}
func (s *Service) LastFailure() *failures.Failure {
var fail failures.Failure
failures.DB().Where("service = ?", s.Id).Order("id desc").Limit(1).Find(&fail)
return &fail
}
func (s *Service) FailuresCount() int {
var amount int
failures.DB().Where("service = ?", s.Id).Count(&amount)
return amount
}
func (s *Service) FailuresSince(t time.Time) []*failures.Failure {
var fails []*failures.Failure
failures.DB().Where("service = ?", s.Id).Find(&fails)
func (s *Service) FailuresSince(t time.Time) failures.Failurer {
fails := failures.Since(t, s)
return fails
}
func (s *Service) DowntimeText() string {
last := s.LastFailure()
last := s.AllFailures().Last()
if last == nil {
return ""
}

@ -22,5 +22,5 @@ func (s *Service) AllHits() hits.Hitters {
}
func (s *Service) HitsSince(t time.Time) hits.Hitters {
return hits.HitsSince(t, s)
return hits.Since(t, s)
}

@ -4,6 +4,7 @@ import (
"crypto/sha1"
"encoding/hex"
"fmt"
"github.com/statping/statping/types"
"github.com/statping/statping/types/null"
"github.com/statping/statping/utils"
"net/url"
@ -33,6 +34,13 @@ func (s *Service) Close() {
}
}
func humanMicro(val int64) string {
if val < 10000 {
return fmt.Sprintf("%d μs", val)
}
return fmt.Sprintf("%0.0f ms", float64(val)*0.001)
}
// IsRunning returns true if the service go routine is running
func (s *Service) IsRunning() bool {
if s.Running == nil {
@ -68,7 +76,7 @@ func SelectAllServices(start bool) (map[int64]*Service, error) {
CheckinProcess(s)
}
fails := s.AllFailures().Last(limitedFailures)
fails := s.AllFailures().LastAmount(limitedFailures)
s.Failures = fails
for _, c := range s.Checkins() {
@ -137,12 +145,12 @@ func (s *Service) UpdateStats() *Service {
s.Online24Hours = s.OnlineDaysPercent(1)
s.Online7Days = s.OnlineDaysPercent(7)
s.AvgResponse = s.AvgTime()
s.FailuresLast24Hours = len(s.AllFailures().Since(utils.Now().Add(-time.Hour * 24)))
s.FailuresLast24Hours = s.FailuresSince(utils.Now().Add(-time.Hour * 24)).Count()
if s.LastOffline.IsZero() {
lastFail := s.LastFailure()
lastFail := s.AllFailures().Last()
if lastFail != nil {
s.LastOffline = s.LastFailure().CreatedAt
s.LastOffline = lastFail.CreatedAt
}
}
@ -155,29 +163,31 @@ func (s *Service) UpdateStats() *Service {
}
// AvgTime will return the average amount of time for a service to response back successfully
func (s *Service) AvgTime() int64 {
func (s *Service) AvgTime() float64 {
return s.AllHits().Avg()
}
// OnlineDaysPercent returns the service's uptime percent within last 24 hours
func (s *Service) OnlineDaysPercent(days int) float32 {
ago := utils.Now().Add((-24 * time.Duration(days)) * time.Hour)
ago := utils.Now().Add(-time.Duration(days) * types.Day)
return s.OnlineSince(ago)
}
// OnlineSince accepts a time since parameter to return the percent of a service's uptime.
func (s *Service) OnlineSince(ago time.Time) float32 {
failed := s.AllFailures().Since(ago)
if len(failed) == 0 {
failed := s.FailuresSince(ago)
failsList := failed.List()
if len(failsList) == 0 {
s.Online24Hours = 100.00
return s.Online24Hours
}
total := s.AllHits().Since(ago)
if len(total) == 0 {
total := s.HitsSince(ago)
hitsList := total.List()
if len(hitsList) == 0 {
s.Online24Hours = 0
return s.Online24Hours
}
avg := float64(len(failed)) / float64(len(total)) * 100
avg := float64(len(hitsList)) / float64(len(failsList)) * 100
avg = 100 - avg
if avg < 0 {
avg = 0
@ -190,12 +200,12 @@ func (s *Service) OnlineSince(ago time.Time) float32 {
// Downtime returns the amount of time of a offline service
func (s *Service) Downtime() time.Duration {
hit := s.LastHit()
fail := s.LastFailure()
fail := s.AllFailures().Last()
if hit == nil {
return time.Duration(0)
}
if fail == nil {
return utils.Now().Sub(fail.CreatedAt)
return utils.Now().Sub(hit.CreatedAt)
}
return fail.CreatedAt.Sub(hit.CreatedAt)

@ -64,9 +64,9 @@ func parseHost(s *Service) string {
}
// dnsCheck will check the domain name and return a float64 for the amount of time the DNS check took
func dnsCheck(s *Service) (float64, error) {
func dnsCheck(s *Service) (int64, error) {
var err error
t1 := time.Now()
t1 := utils.Now()
host := parseHost(s)
if s.Type == "tcp" {
_, err = net.LookupHost(host)
@ -77,7 +77,7 @@ func dnsCheck(s *Service) (float64, error) {
return 0, err
}
t2 := time.Now()
subTime := t2.Sub(t1).Seconds()
subTime := t2.Sub(t1).Microseconds()
return subTime, err
}
@ -101,7 +101,7 @@ func CheckIcmp(s *Service, record bool) *Service {
}
p.AddIPAddr(ra)
p.OnRecv = func(addr *net.IPAddr, rtt time.Duration) {
s.Latency = rtt.Seconds()
s.Latency = rtt.Microseconds()
recordSuccess(s)
}
err = p.Run()
@ -125,7 +125,7 @@ func CheckTcp(s *Service, record bool) *Service {
return s
}
s.PingTime = dnsLookup
t1 := time.Now()
t1 := utils.Now()
domain := fmt.Sprintf("%v", s.Domain)
if s.Port != 0 {
domain = fmt.Sprintf("%v:%v", s.Domain, s.Port)
@ -146,8 +146,8 @@ func CheckTcp(s *Service, record bool) *Service {
}
return s
}
t2 := time.Now()
s.Latency = t2.Sub(t1).Seconds()
t2 := utils.Now()
s.Latency = t2.Sub(t1).Microseconds()
s.LastResponse = ""
if record {
recordSuccess(s)
@ -171,7 +171,7 @@ func CheckHttp(s *Service, record bool) *Service {
return s
}
s.PingTime = dnsLookup
t1 := time.Now()
t1 := utils.Now()
timeout := time.Duration(s.Timeout) * time.Second
var content []byte
@ -195,8 +195,8 @@ func CheckHttp(s *Service, record bool) *Service {
}
return s
}
t2 := time.Now()
s.Latency = t2.Sub(t1).Seconds()
t2 := utils.Now()
s.Latency = t2.Sub(t1).Microseconds()
s.LastResponse = string(content)
s.LastStatusCode = res.StatusCode
@ -226,21 +226,21 @@ func CheckHttp(s *Service, record bool) *Service {
// recordSuccess will create a new 'hit' record in the database for a successful/online service
func recordSuccess(s *Service) {
s.LastOnline = time.Now().UTC()
s.LastOnline = utils.Now()
s.Online = true
hit := &hits.Hit{
Service: s.Id,
Latency: s.Latency,
PingTime: s.PingTime,
CreatedAt: time.Now().UTC(),
CreatedAt: utils.Now(),
}
if err := hit.Create(); err != nil {
log.Error(err)
}
log.WithFields(utils.ToFields(hit, s)).Infoln(
fmt.Sprintf("Service #%d '%v' Successful Response: %0.2f ms | Lookup in: %0.2f ms | Online: %v | Interval: %d seconds", s.Id, s.Name, hit.Latency*1000, hit.PingTime*1000, s.Online, s.Interval))
s.LastLookupTime = int64(hit.PingTime * 1000)
s.LastLatency = int64(hit.Latency * 1000)
fmt.Sprintf("Service #%d '%v' Successful Response: %s | Lookup in: %s | Online: %v | Interval: %d seconds", s.Id, s.Name, humanMicro(hit.Latency), humanMicro(hit.PingTime), s.Online, s.Interval))
s.LastLookupTime = hit.PingTime
s.LastLatency = hit.Latency
//notifier.OnSuccess(s)
s.SuccessNotified = true
}

@ -0,0 +1,285 @@
package services
import (
"fmt"
"github.com/statping/statping/database"
"github.com/statping/statping/types/checkins"
"github.com/statping/statping/types/failures"
"github.com/statping/statping/types/hits"
"github.com/statping/statping/types/null"
"github.com/statping/statping/utils"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"testing"
"time"
)
var example = &Service{
Name: "Example Service",
Domain: "https://statping.com",
ExpectedStatus: 200,
Interval: 30,
Type: "http",
Method: "GET",
Timeout: 5,
Order: 1,
VerifySSL: null.NewNullBool(true),
Public: null.NewNullBool(true),
GroupId: 1,
Permalink: null.NewNullString("statping"),
LastCheck: utils.Now().Add(-5 * time.Second),
LastOffline: utils.Now().Add(-5 * time.Second),
LastOnline: utils.Now().Add(-60 * time.Second),
}
var hit1 = &hits.Hit{
Service: 1,
Latency: 0.1234,
PingTime: 0.01234,
CreatedAt: utils.Now().Add(-120 * time.Second),
}
var hit2 = &hits.Hit{
Service: 1,
Latency: 0.2345,
PingTime: 0.02345,
CreatedAt: utils.Now().Add(-60 * time.Second),
}
var hit3 = &hits.Hit{
Service: 1,
Latency: 0.3456,
PingTime: 0.03456,
CreatedAt: utils.Now().Add(-30 * time.Second),
}
var exmapleCheckin = &checkins.Checkin{
ServiceId: 1,
Name: "Example Checkin",
Interval: 60,
GracePeriod: 30,
ApiKey: "wdededede",
}
var fail1 = &failures.Failure{
Issue: "example not found",
ErrorCode: 404,
Service: 1,
PingTime: 0.0123,
CreatedAt: utils.Now().Add(-160 * time.Second),
}
var fail2 = &failures.Failure{
Issue: "example 2 not found",
ErrorCode: 500,
Service: 1,
PingTime: 0.0123,
CreatedAt: utils.Now().Add(-5 * time.Second),
}
func TestInit(t *testing.T) {
db, err := database.OpenTester()
require.Nil(t, err)
db.AutoMigrate(&Service{}, &hits.Hit{}, &checkins.Checkin{}, &checkins.CheckinHit{}, &failures.Failure{})
db.Create(&example)
db.Create(&hit1)
db.Create(&hit2)
db.Create(&hit3)
db.Create(&exmapleCheckin)
db.Create(&fail1)
db.Create(&fail2)
checkins.SetDB(db)
failures.SetDB(db)
hits.SetDB(db)
SetDB(db)
}
func TestFind(t *testing.T) {
item, err := Find(1)
require.Nil(t, err)
assert.Equal(t, "Example Service", item.Name)
assert.NotZero(t, item.LastOnline)
assert.NotZero(t, item.LastOffline)
assert.NotZero(t, item.LastCheck)
}
func TestAll(t *testing.T) {
items := All()
assert.Len(t, items, 1)
}
func TestService_Checkins(t *testing.T) {
item, err := Find(1)
require.Nil(t, err)
assert.Len(t, item.Checkins(), 1)
}
func TestService_AllHits(t *testing.T) {
item, err := Find(1)
require.Nil(t, err)
assert.Len(t, item.AllHits().List(), 3)
assert.Equal(t, 3, item.AllHits().Count())
}
func TestService_AllFailures(t *testing.T) {
item, err := Find(1)
require.Nil(t, err)
assert.Len(t, item.AllFailures().List(), 2)
assert.Equal(t, 2, item.AllFailures().Count())
}
func TestService_FirstHit(t *testing.T) {
item, err := Find(1)
require.Nil(t, err)
hit := item.FirstHit()
assert.Equal(t, int64(1), hit.Id)
}
func TestService_LastHit(t *testing.T) {
item, err := Find(1)
require.Nil(t, err)
hit := item.AllHits().Last()
assert.Equal(t, int64(3), hit.Id)
}
func TestService_LastFailure(t *testing.T) {
item, err := Find(1)
require.Nil(t, err)
fail := item.AllFailures().Last()
assert.Equal(t, int64(2), fail.Id)
}
func TestService_Duration(t *testing.T) {
item, err := Find(1)
require.Nil(t, err)
assert.Equal(t, float64(30), item.Duration().Seconds())
}
func TestService_AvgTime(t *testing.T) {
item, err := Find(1)
require.Nil(t, err)
assert.Equal(t, "", item.AvgTime())
}
func TestService_HitsSince(t *testing.T) {
item, err := Find(1)
require.Nil(t, err)
count := item.HitsSince(utils.Now().Add(-30 * time.Second))
assert.Equal(t, 1, count.Count())
count = item.HitsSince(utils.Now().Add(-180 * time.Second))
assert.Equal(t, 3, count.Count())
}
func TestService_IsRunning(t *testing.T) {
item, err := Find(1)
require.Nil(t, err)
assert.False(t, item.IsRunning())
}
func TestService_OnlineDaysPercent(t *testing.T) {
item, err := Find(1)
require.Nil(t, err)
amount := item.OnlineDaysPercent(1)
assert.Equal(t, "", amount)
}
func TestService_Downtime(t *testing.T) {
item, err := Find(1)
require.Nil(t, err)
amount := item.Downtime().Seconds()
assert.Equal(t, "25", fmt.Sprintf("%0.f", amount))
}
func TestService_FailuresSince(t *testing.T) {
item, err := Find(1)
require.Nil(t, err)
count := item.FailuresSince(utils.Now().Add(-6 * time.Second))
assert.Equal(t, 1, count.Count())
count = item.FailuresSince(utils.Now().Add(-180 * time.Second))
assert.Equal(t, 2, count.Count())
}
func TestCreate(t *testing.T) {
example := &Service{
Name: "Example Service 2",
Domain: "https://slack.statping.com",
ExpectedStatus: 200,
Interval: 10,
Type: "http",
Method: "GET",
Timeout: 5,
Order: 3,
VerifySSL: null.NewNullBool(true),
Public: null.NewNullBool(false),
GroupId: 1,
Permalink: null.NewNullString("statping2"),
}
err := example.Create()
require.Nil(t, err)
assert.NotZero(t, example.Id)
assert.Equal(t, "Example Service 2", example.Name)
assert.False(t, example.Public.Bool)
assert.NotZero(t, example.CreatedAt)
assert.Equal(t, int64(2), example.Id)
assert.Len(t, allServices, 2)
}
func TestUpdate(t *testing.T) {
item, err := Find(1)
require.Nil(t, err)
item.Name = "Updated Service"
item.Order = 1
err = item.Update()
require.Nil(t, err)
assert.Equal(t, int64(1), item.Id)
assert.Equal(t, "Updated Service", item.Name)
}
func TestAllInOrder(t *testing.T) {
inOrder := AllInOrder()
assert.Len(t, inOrder, 2)
assert.Equal(t, "Updated Service", inOrder[0].Name)
assert.Equal(t, "Example Service 2", inOrder[1].Name)
}
func TestDelete(t *testing.T) {
all := All()
assert.Len(t, all, 2)
item, err := Find(1)
require.Nil(t, err)
assert.Equal(t, int64(1), item.Id)
err = item.Delete()
require.Nil(t, err)
all = All()
assert.Len(t, all, 1)
}
func TestService_CheckService(t *testing.T) {
item, err := Find(2)
require.Nil(t, err)
hitsCount := item.AllHits().Count()
failsCount := item.AllFailures().Count()
assert.Equal(t, 3, hitsCount)
assert.Equal(t, 2, failsCount)
item.CheckService(true)
assert.Equal(t, 4, hitsCount)
assert.Equal(t, 2, failsCount)
}
func TestClose(t *testing.T) {
assert.Nil(t, db.Close())
}

@ -41,11 +41,11 @@ type Service struct {
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
Online bool `gorm:"-" json:"online"`
Latency float64 `gorm:"-" json:"latency"`
PingTime float64 `gorm:"-" json:"ping_time"`
Latency int64 `gorm:"-" json:"latency"`
PingTime int64 `gorm:"-" json:"ping_time"`
Online24Hours float32 `gorm:"-" json:"online_24_hours"`
Online7Days float32 `gorm:"-" json:"online_7_days"`
AvgResponse int64 `gorm:"-" json:"avg_response"`
AvgResponse float64 `gorm:"-" json:"avg_response"`
FailuresLast24Hours int `gorm:"-" json:"failures_24_hours"`
Running chan bool `gorm:"-" json:"-"`
Checkpoint time.Time `gorm:"-" json:"-"`

@ -4,56 +4,56 @@ import (
"github.com/prometheus/common/log"
"github.com/statping/statping/database"
"github.com/statping/statping/utils"
"time"
)
func DB() database.Database {
return database.DB().Model(&User{})
var db database.Database
func SetDB(database database.Database) {
db = database.Model(&User{})
}
func Find(id int64) (*User, error) {
var user User
db := DB().Where("id = ?", id).Find(&user)
return &user, db.Error()
q := db.Where("id = ?", id).Find(&user)
return &user, q.Error()
}
func FindByUsername(username string) (*User, error) {
var user User
db := DB().Where("username = ?", username).Find(&user)
return &user, db.Error()
q := db.Where("username = ?", username).Find(&user)
return &user, q.Error()
}
func All() []*User {
var users []*User
DB().Find(&users)
db.Find(&users)
return users
}
func (u *User) Create() error {
u.CreatedAt = time.Now().UTC()
u.Password = utils.HashPassword(u.Password)
if u.ApiKey == "" || u.ApiSecret == "" {
u.ApiKey = utils.NewSHA1Hash(16)
u.ApiSecret = utils.NewSHA1Hash(16)
}
db := DB().Create(u)
q := db.Create(u)
if db.Error() == nil {
log.Warnf("User #%d (%s) has been created", u.Id, u.Username)
}
return db.Error()
return q.Error()
}
func (u *User) Update() error {
//u.ApiKey = utils.NewSHA1Hash(5)
//u.ApiSecret = utils.NewSHA1Hash(10)
db := DB().Update(u)
return db.Error()
q := db.Update(u)
return q.Error()
}
func (u *User) Delete() error {
db := DB().Delete(u)
q := db.Delete(u)
if db.Error() == nil {
log.Warnf("User #%d (%s) has been deleted", u.Id, u.Username)
}
return db.Error()
return q.Error()
}
func (u *User) BeforeCreate() error {
u.Password = utils.HashPassword(u.Password)
u.ApiKey = utils.NewSHA1Hash(16)
u.ApiSecret = utils.NewSHA1Hash(16)
return nil
}

@ -2,7 +2,6 @@ package users
import (
"github.com/statping/statping/types/null"
"github.com/statping/statping/utils"
"time"
)
@ -18,14 +17,3 @@ type User struct {
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
}
// BeforeCreate for User will set CreatedAt to UTC
func (u *User) BeforeCreate() (err error) {
u.ApiKey = utils.RandomString(16)
u.ApiSecret = utils.RandomString(16)
if u.CreatedAt.IsZero() {
u.CreatedAt = time.Now().UTC()
u.UpdatedAt = time.Now().UTC()
}
return
}

@ -0,0 +1,92 @@
package users
import (
"github.com/statping/statping/database"
"github.com/statping/statping/types/null"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"testing"
)
var example = &User{
Username: "example_user",
Email: "Description here",
Password: "password123",
Admin: null.NewNullBool(true),
}
func TestInit(t *testing.T) {
db, err := database.OpenTester()
require.Nil(t, err)
db.CreateTable(&User{})
db.Create(&example)
SetDB(db)
}
func TestFind(t *testing.T) {
item, err := Find(1)
require.Nil(t, err)
assert.Equal(t, "example_user", item.Username)
assert.NotEmpty(t, item.ApiKey)
assert.NotEmpty(t, item.ApiSecret)
assert.NotEqual(t, "password123", item.Password)
assert.True(t, item.Admin.Bool)
}
func TestFindByUsername(t *testing.T) {
item, err := FindByUsername("example_user")
require.Nil(t, err)
assert.Equal(t, "example_user", item.Username)
assert.NotEmpty(t, item.ApiKey)
assert.NotEmpty(t, item.ApiSecret)
assert.NotEqual(t, "password123", item.Password)
assert.True(t, item.Admin.Bool)
}
func TestAll(t *testing.T) {
items := All()
assert.Len(t, items, 1)
}
func TestCreate(t *testing.T) {
example := &User{
Username: "exampleuser2",
Password: "password12345",
Email: "info@yahoo.com",
}
err := example.Create()
require.Nil(t, err)
assert.NotZero(t, example.Id)
assert.Equal(t, "exampleuser2", example.Username)
assert.NotEqual(t, "password12345", example.Password)
assert.NotZero(t, example.CreatedAt)
assert.NotEmpty(t, example.ApiKey)
assert.NotEmpty(t, example.ApiSecret)
}
func TestUpdate(t *testing.T) {
item, err := Find(1)
require.Nil(t, err)
item.Username = "updated_user"
err = item.Update()
require.Nil(t, err)
assert.Equal(t, "updated_user", item.Username)
}
func TestDelete(t *testing.T) {
all := All()
assert.Len(t, all, 2)
item, err := Find(1)
require.Nil(t, err)
err = item.Delete()
require.Nil(t, err)
all = All()
assert.Len(t, all, 1)
}
func TestClose(t *testing.T) {
assert.Nil(t, db.Close())
}
Loading…
Cancel
Save