pull/429/head
Hunter Long 2020-03-02 22:42:37 -08:00
parent d5ffa3a851
commit eb5e291e56
29 changed files with 358 additions and 545 deletions

View File

@ -82,13 +82,13 @@ install:
generate: generate:
cd source && go generate cd source && go generate
cd handlers/graphql && go generate
# remove files for a clean compile/build # remove files for a clean compile/build
clean: clean:
rm -rf ./{logs,assets,plugins,*.db,config.yml,.sass-cache,config.yml,statping,build,.sass-cache,index.html,vendor} rm -rf ./{logs,assets,plugins,*.db,config.yml,.sass-cache,config.yml,statping,build,.sass-cache,index.html,vendor}
rm -rf cmd/{logs,assets,plugins,*.db,config.yml,.sass-cache,*.log,*.html,*.json} rm -rf cmd/{logs,assets,plugins,*.db,config.yml,.sass-cache,*.log,*.html,*.json}
rm -rf core/{logs,assets,plugins,*.db,config.yml,.sass-cache,*.log} rm -rf core/{logs,assets,plugins,*.db,config.yml,.sass-cache,*.log}
rm -rf core/notifier/{logs,assets,plugins,*.db,config.yml,.sass-cache,*.log}
rm -rf handlers/{logs,assets,plugins,*.db,config.yml,.sass-cache,*.log} rm -rf handlers/{logs,assets,plugins,*.db,config.yml,.sass-cache,*.log}
rm -rf notifiers/{logs,assets,plugins,*.db,config.yml,.sass-cache,*.log} rm -rf notifiers/{logs,assets,plugins,*.db,config.yml,.sass-cache,*.log}
rm -rf source/{logs,assets,plugins,*.db,config.yml,.sass-cache,*.log} rm -rf source/{logs,assets,plugins,*.db,config.yml,.sass-cache,*.log}
@ -104,7 +104,7 @@ clean:
find . -name "*.out" -type f -delete find . -name "*.out" -type f -delete
find . -name "*.cpu" -type f -delete find . -name "*.cpu" -type f -delete
find . -name "*.mem" -type f -delete find . -name "*.mem" -type f -delete
rm -rf {build,tmp,docker} rm -rf {build,tmp}
print_details: print_details:
@echo \==== Statping Development Instance ==== @echo \==== Statping Development Instance ====

View File

@ -19,14 +19,11 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/hunterlong/statping/core" "github.com/hunterlong/statping/core"
"github.com/hunterlong/statping/handlers"
"github.com/hunterlong/statping/source" "github.com/hunterlong/statping/source"
"github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/utils" "github.com/hunterlong/statping/utils"
"github.com/joho/godotenv" "github.com/joho/godotenv"
"github.com/pkg/errors" "github.com/pkg/errors"
"io/ioutil" "io/ioutil"
"net/http/httptest"
"time" "time"
) )
@ -72,25 +69,25 @@ func catchCLI(args []string) error {
updateDisplay() updateDisplay()
return errors.New("end") return errors.New("end")
case "static": case "static":
var err error //var err error
if err = runLogs(); err != nil { //if err = runLogs(); err != nil {
return err // return err
} //}
if err = runAssets(); err != nil { //if err = runAssets(); err != nil {
return err // return err
} //}
fmt.Printf("Statping v%v Exporting Static 'index.html' page...\n", VERSION) //fmt.Printf("Statping v%v Exporting Static 'index.html' page...\n", VERSION)
if _, err = core.LoadConfigFile(dir); err != nil { //if _, err = core.LoadConfigFile(dir); err != nil {
log.Errorln("config.yml file not found") // log.Errorln("config.yml file not found")
return err // return err
} //}
indexSource := ExportIndexHTML() //indexSource := ExportIndexHTML()
//core.CloseDB() ////core.CloseDB()
if err = utils.SaveFile(dir+"/index.html", indexSource); err != nil { //if err = utils.SaveFile(dir+"/index.html", indexSource); err != nil {
log.Errorln(err) // log.Errorln(err)
return err // return err
} //}
log.Infoln("Exported Statping index page: 'index.html'") //log.Infoln("Exported Statping index page: 'index.html'")
case "help": case "help":
HelpEcho() HelpEcho()
return errors.New("end") return errors.New("end")
@ -103,10 +100,11 @@ func catchCLI(args []string) error {
if err = runAssets(); err != nil { if err = runAssets(); err != nil {
return err return err
} }
if _, err = core.LoadConfigFile(dir); err != nil { configs, err := core.LoadConfigFile(dir)
if err != nil {
return err return err
} }
if err = core.CoreApp.Connect(false, dir); err != nil { if err = core.CoreApp.Connect(configs, false, dir); err != nil {
return err return err
} }
if data, err = core.ExportSettings(); err != nil { if data, err = core.ExportSettings(); err != nil {
@ -167,19 +165,19 @@ func catchCLI(args []string) error {
} }
// ExportIndexHTML returns the HTML of the index page as a string // ExportIndexHTML returns the HTML of the index page as a string
func ExportIndexHTML() []byte { //func ExportIndexHTML() []byte {
source.Assets() // source.Assets()
core.CoreApp.Connect(false, utils.Directory) // core.CoreApp.Connect(core.CoreApp., utils.Directory)
core.SelectAllServices(false) // core.SelectAllServices(false)
core.CoreApp.UseCdn = types.NewNullBool(true) // core.CoreApp.UseCdn = types.NewNullBool(true)
for _, srv := range core.Services() { // for _, srv := range core.Services() {
core.CheckService(srv, true) // core.CheckService(srv, true)
} // }
w := httptest.NewRecorder() // w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/", nil) // r := httptest.NewRequest("GET", "/", nil)
handlers.ExecuteResponse(w, r, "index.gohtml", nil, nil) // handlers.ExecuteResponse(w, r, "index.gohtml", nil, nil)
return w.Body.Bytes() // return w.Body.Bytes()
} //}
func updateDisplay() error { func updateDisplay() error {
gitCurrent, err := checkGithubUpdates() gitCurrent, err := checkGithubUpdates()
@ -202,11 +200,11 @@ func updateDisplay() error {
// runOnce will initialize the Statping application and check each service 1 time, will not run HTTP server // runOnce will initialize the Statping application and check each service 1 time, will not run HTTP server
func runOnce() error { func runOnce() error {
_, err := core.LoadConfigFile(utils.Directory) configs, err := core.LoadConfigFile(utils.Directory)
if err != nil { if err != nil {
return errors.Wrap(err, "config.yml file not found") return errors.Wrap(err, "config.yml file not found")
} }
err = core.CoreApp.Connect(false, utils.Directory) err = core.CoreApp.Connect(configs, false, utils.Directory)
if err != nil { if err != nil {
return errors.Wrap(err, "issue connecting to database") return errors.Wrap(err, "issue connecting to database")
} }

View File

@ -62,6 +62,13 @@ func parseFlags() {
flag.Parse() flag.Parse()
} }
func exit(err error) {
fmt.Printf("%+v", core.Configs())
panic(err)
//log.Fatalln(err)
//os.Exit(2)
}
// main will run the Statping application // main will run the Statping application
func main() { func main() {
var err error var err error
@ -80,35 +87,54 @@ func main() {
if err != nil { if err != nil {
if err.Error() == "end" { if err.Error() == "end" {
os.Exit(0) os.Exit(0)
return
} }
fmt.Println(err) exit(err)
os.Exit(1)
} }
} }
log.Info(fmt.Sprintf("Starting Statping v%v", VERSION)) log.Info(fmt.Sprintf("Starting Statping v%v", VERSION))
updateDisplay()
config, err := core.LoadConfigFile(utils.Directory) if err := updateDisplay(); err != nil {
log.Warnln(err)
}
// check if DB_CONN was set, and load config from that
autoConfigDb := utils.Getenv("DB_CONN", "").(string)
if autoConfigDb != "" {
log.Infof("Environment variable 'DB_CONN' was set to %s, loading configs from ENV.", autoConfigDb)
if _, err := core.LoadUsingEnv(); err != nil {
exit(err)
return
} else {
afterConfigLoaded()
}
}
// attempt to load config.yml file from current directory, if no file, then start in setup mode.
_, err = core.LoadConfigFile(utils.Directory)
if err != nil { if err != nil {
log.Errorln(err) log.Errorln(err)
core.CoreApp.Setup = false core.CoreApp.Setup = false
writeAble, err := utils.DirWritable(utils.Directory) writeAble, err := utils.DirWritable(utils.Directory)
if err != nil { if err != nil {
log.Fatalln(err) exit(err)
return
} }
if !writeAble { if !writeAble {
log.Fatalf("Statping does not have write permissions at: %v\nYou can change this directory by setting the STATPING_DIR environment variable to a dedicated path before starting.", utils.Directory) log.Fatalf("Statping does not have write permissions at: %v\nYou can change this directory by setting the STATPING_DIR environment variable to a dedicated path before starting.", utils.Directory)
return
} }
if err := handlers.RunHTTPServer(ipAddress, port); err != nil { if err := handlers.RunHTTPServer(ipAddress, port); err != nil {
log.Fatalln(err) log.Fatalln(err)
} }
} else {
afterConfigLoaded()
} }
}
core.CoreApp.Config = config.DbConfig func afterConfigLoaded() {
if err := mainProcess(); err != nil { if err := mainProcess(); err != nil {
log.Fatalln(err) exit(err)
os.Exit(2)
} }
} }
@ -140,7 +166,7 @@ func loadDotEnvs() error {
func mainProcess() error { func mainProcess() error {
dir := utils.Directory dir := utils.Directory
var err error var err error
err = core.CoreApp.Connect(false, dir) err = core.CoreApp.Connect(core.Configs(), false, dir)
if err != nil { if err != nil {
log.Errorln(fmt.Sprintf("could not connect to database: %v", err)) log.Errorln(fmt.Sprintf("could not connect to database: %v", err))
return err return err

View File

@ -17,7 +17,6 @@ package core
import ( import (
"fmt" "fmt"
"github.com/ararog/timeago"
"github.com/hunterlong/statping/database" "github.com/hunterlong/statping/database"
"github.com/hunterlong/statping/types" "github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/utils" "github.com/hunterlong/statping/utils"
@ -108,37 +107,6 @@ func SelectCheckin(api string) *Checkin {
return nil return nil
} }
// AllHits returns all of the CheckinHits for a given Checkin
func (c *Checkin) AllHits() []*types.CheckinHit {
var checkins []*types.CheckinHit
Database(&types.CheckinHit{}).Where("checkin = ?", c.Id).Order("id DESC").Find(&checkins)
return checkins
}
// Hits returns all of the CheckinHits for a given Checkin
func (c *Checkin) AllFailures() []*types.Failure {
var failures []*types.Failure
Database(&types.Failure{}).
Where("checkin = ?", c.Id).
Where("method = 'checkin'").
Order("id desc").
Find(&failures)
return failures
}
func (c *Checkin) GetFailures(count int) []*types.Failure {
var failures []*types.Failure
Database(&types.Failure{}).
Where("checkin = ?", c.Id).
Where("method = 'checkin'").
Limit(count).
Order("id desc").
Find(&failures)
return failures
}
// Create will create a new Checkin // Create will create a new Checkin
func (c *Checkin) Delete() { func (c *Checkin) Delete() {
c.Close() c.Close()
@ -171,35 +139,6 @@ func (c *Checkin) Create() (int64, error) {
return c.Id, err return c.Id, err
} }
// Update will update a Checkin
func (c *Checkin) Update() (int64, error) {
row := Database(c).Update(&c)
if row.Error() != nil {
log.Warnln(row.Error())
return 0, row.Error()
}
return c.Id, row.Error()
}
// Create will create a new successful checkinHit
func (c *CheckinHit) Create() (int64, error) {
if c.CreatedAt.IsZero() {
c.CreatedAt = utils.Now()
}
row := Database(c).Create(&c)
if row.Error() != nil {
log.Warnln(row.Error())
return 0, row.Error()
}
return c.Id, row.Error()
}
// Ago returns the duration of time between now and the last successful checkinHit
func (c *CheckinHit) Ago() string {
got, _ := timeago.TimeAgoWithTime(utils.Now(), c.CreatedAt)
return got
}
// RecheckCheckinFailure will check if a Service Checkin has been reported yet // RecheckCheckinFailure will check if a Service Checkin has been reported yet
func (c *Checkin) RecheckCheckinFailure(guard chan struct{}) { func (c *Checkin) RecheckCheckinFailure(guard chan struct{}) {
between := utils.Now().Sub(utils.Now()).Seconds() between := utils.Now().Sub(utils.Now()).Seconds()

View File

@ -30,15 +30,9 @@ type ErrorResponse struct {
} }
// LoadConfigFile will attempt to load the 'config.yml' file in a specific directory // LoadConfigFile will attempt to load the 'config.yml' file in a specific directory
func LoadConfigFile(directory string) (*DbConfig, error) { func LoadConfigFile(directory string) (*types.DbConfig, error) {
var configs *DbConfig var configs *types.DbConfig
dbConn := utils.Getenv("DB_CONN", "").(string)
if dbConn != "" {
log.Infof("DB_CONN=%s environment variable was found, waiting for database...", dbConn)
return LoadUsingEnv()
}
log.Debugln("Attempting to read config file at: " + directory + "/config.yml") log.Debugln("Attempting to read config file at: " + directory + "/config.yml")
file, err := utils.OpenFile(directory + "/config.yml") file, err := utils.OpenFile(directory + "/config.yml")
if err != nil { if err != nil {
@ -50,61 +44,75 @@ func LoadConfigFile(directory string) (*DbConfig, error) {
return nil, errors.Wrap(err, "yaml file not formatted correctly") return nil, errors.Wrap(err, "yaml file not formatted correctly")
} }
log.WithFields(utils.ToFields(configs)).Debugln("read config file: " + directory + "/config.yml") log.WithFields(utils.ToFields(configs)).Debugln("read config file: " + directory + "/config.yml")
CoreApp.Config = configs.DbConfig
CoreApp.config = configs
return configs, err return configs, err
} }
func Configs() *types.DbConfig {
return CoreApp.config
}
// LoadUsingEnv will attempt to load database configs based on environment variables. If DB_CONN is set if will force this function. // LoadUsingEnv will attempt to load database configs based on environment variables. If DB_CONN is set if will force this function.
func LoadUsingEnv() (*DbConfig, error) { func LoadUsingEnv() (*types.DbConfig, error) {
Configs, err := EnvToConfig() configs, err := EnvToConfig()
if err != nil { if err != nil {
return Configs, err return configs, err
} }
CoreApp.Name = utils.Getenv("NAME", "").(string) CoreApp.Name = utils.Getenv("NAME", "").(string)
CoreApp.Domain = utils.Getenv("DOMAIN", Configs.LocalIP).(string) CoreApp.Domain = utils.Getenv("DOMAIN", "").(string)
CoreApp.UseCdn = types.NewNullBool(utils.Getenv("USE_CDN", false).(bool)) CoreApp.UseCdn = types.NewNullBool(utils.Getenv("USE_CDN", false).(bool))
err = CoreApp.Connect(true, utils.Directory) err = CoreApp.Connect(configs, true, utils.Directory)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "error connecting to database") return nil, errors.Wrap(err, "error connecting to database")
} }
if err := Configs.Save(); err != nil { if err := SaveConfig(configs); err != nil {
return nil, errors.Wrap(err, "error saving configuration") return nil, errors.Wrap(err, "error saving configuration")
} }
exists := DbSession.HasTable("core") exists := database.Get().HasTable("core")
if !exists { if !exists {
log.Infoln(fmt.Sprintf("Core database does not exist, creating now!")) return InitialSetup(configs)
if err := CoreApp.DropDatabase(); err != nil {
return nil, errors.Wrap(err, "error dropping database")
}
if err := CoreApp.CreateDatabase(); err != nil {
return nil, errors.Wrap(err, "error creating database")
}
CoreApp, err = Configs.InsertCore()
if err != nil {
return nil, errors.Wrap(err, "error creating the core database")
}
username := utils.Getenv("ADMIN_USER", "admin").(string)
password := utils.Getenv("ADMIN_PASSWORD", "admin").(string)
admin := &types.User{
Username: username,
Password: utils.HashPassword(password),
Email: "info@admin.com",
Admin: types.NewNullBool(true),
}
if _, err := database.Create(admin); err != nil {
return nil, errors.Wrap(err, "error creating admin")
}
if err := SampleData(); err != nil {
return nil, errors.Wrap(err, "error connecting sample data")
}
return Configs, err
} }
return Configs, nil
CoreApp.config = configs
return configs, nil
}
func InitialSetup(configs *types.DbConfig) (*types.DbConfig, error) {
log.Infoln(fmt.Sprintf("Core database does not exist, creating now!"))
if err := CoreApp.DropDatabase(); err != nil {
return nil, errors.Wrap(err, "error dropping database")
}
if err := CoreApp.CreateDatabase(); err != nil {
return nil, errors.Wrap(err, "error creating database")
}
CoreApp, err := InsertCore(configs)
if err != nil {
return nil, errors.Wrap(err, "error creating the core database")
}
username := utils.Getenv("ADMIN_USER", "admin").(string)
password := utils.Getenv("ADMIN_PASSWORD", "admin").(string)
admin := &types.User{
Username: username,
Password: utils.HashPassword(password),
Email: "info@admin.com",
Admin: types.NewNullBool(true),
}
if _, err := database.Create(admin); err != nil {
return nil, errors.Wrap(err, "error creating admin")
}
if err := SampleData(); err != nil {
return nil, errors.Wrap(err, "error connecting sample data")
}
CoreApp.config = configs
return configs, err
} }
// defaultPort accepts a database type and returns its default port // defaultPort accepts a database type and returns its default port
@ -122,7 +130,7 @@ func defaultPort(db string) int {
} }
// EnvToConfig converts environment variables to a DbConfig // EnvToConfig converts environment variables to a DbConfig
func EnvToConfig() (*DbConfig, error) { func EnvToConfig() (*types.DbConfig, error) {
var err error var err error
dbConn := utils.Getenv("DB_CONN", "").(string) dbConn := utils.Getenv("DB_CONN", "").(string)
@ -173,10 +181,7 @@ func EnvToConfig() (*DbConfig, error) {
Location: utils.Directory, Location: utils.Directory,
SqlFile: sqlFile, SqlFile: sqlFile,
} }
return config, err
CoreApp.Config = config
return &DbConfig{config}, err
} }
// SampleData runs all the sample data for a new Statping installation // SampleData runs all the sample data for a new Statping installation
@ -202,3 +207,10 @@ func DeleteConfig() error {
} }
return nil return nil
} }
func IsSetup() bool {
if CoreApp.config != nil {
return true
}
return false
}

View File

@ -35,6 +35,7 @@ type PluginRepos types.PluginRepos
type Core struct { type Core struct {
*types.Core *types.Core
services map[int64]*Service services map[int64]*Service
config *types.DbConfig
} }
var ( var (
@ -93,91 +94,45 @@ func InitApp() error {
// InsertNotifierDB inject the Statping database instance to the Notifier package // InsertNotifierDB inject the Statping database instance to the Notifier package
func InsertNotifierDB() error { func InsertNotifierDB() error {
if DbSession == nil { if !database.Available() {
err := CoreApp.Connect(false, utils.Directory) err := CoreApp.Connect(CoreApp.config, false, utils.Directory)
if err != nil { if err != nil {
return errors.New("database connection has not been created") return errors.New("database connection has not been created")
} }
} }
notifier.SetDB(DbSession) notifier.SetDB(database.Get())
return nil return nil
} }
// InsertIntegratorDB inject the Statping database instance to the Integrations package // InsertIntegratorDB inject the Statping database instance to the Integrations package
func InsertIntegratorDB() error { func InsertIntegratorDB() error {
if DbSession == nil { if !database.Available() {
err := CoreApp.Connect(false, utils.Directory) err := CoreApp.Connect(CoreApp.config, false, utils.Directory)
if err != nil { if err != nil {
return errors.New("database connection has not been created") return errors.New("database connection has not been created")
} }
} }
integrations.SetDB(DbSession) integrations.SetDB(database.Get())
return nil return nil
} }
// UpdateCore will update the CoreApp variable inside of the 'core' table in database
func UpdateCore(c *Core) (*Core, error) {
db := Database(&Core{}).Update(&c)
return c, db.Error()
}
// CurrentTime will return the current local time
func (c Core) CurrentTime() string {
t := time.Now().UTC()
current := utils.Timezoner(t, c.Timezone)
ansic := "Monday 03:04:05 PM"
return current.Format(ansic)
}
// Messages will return the current local time
func (c Core) Messages() []*Message {
var message []*Message
Database(&Message{}).Where("service = ?", 0).Limit(10).Find(&message)
return message
}
// UsingAssets will return true if /assets folder is present // UsingAssets will return true if /assets folder is present
func (c Core) UsingAssets() bool { func (c Core) UsingAssets() bool {
return source.UsingAssets(utils.Directory) return source.UsingAssets(utils.Directory)
} }
// SassVars opens the file /assets/scss/variables.scss to be edited in Theme
func (c Core) SassVars() string {
if !source.UsingAssets(utils.Directory) {
return ""
}
return source.OpenAsset("scss/variables.scss")
}
// BaseSASS is the base design , this opens the file /assets/scss/base.scss to be edited in Theme
func (c Core) BaseSASS() string {
if !source.UsingAssets(utils.Directory) {
return ""
}
return source.OpenAsset("scss/base.scss")
}
// MobileSASS is the -webkit responsive custom css designs. This opens the
// file /assets/scss/mobile.scss to be edited in Theme
func (c Core) MobileSASS() string {
if !source.UsingAssets(utils.Directory) {
return ""
}
return source.OpenAsset("scss/mobile.scss")
}
// SelectCore will return the CoreApp global variable and the settings/configs for Statping // SelectCore will return the CoreApp global variable and the settings/configs for Statping
func SelectCore() (*Core, error) { func SelectCore() (*Core, error) {
if DbSession == nil { if !database.Available() {
log.Traceln("database has not been initiated yet.") log.Traceln("database has not been initiated yet.")
return nil, errors.New("database has not been initiated yet.") return nil, errors.New("database has not been initiated yet.")
} }
exists := DbSession.HasTable("core") exists := database.Get().HasTable("core")
if !exists { if !exists {
log.Errorf("core database has not been setup yet, does not have the 'core' table") log.Errorf("core database has not been setup yet, does not have the 'core' table")
return nil, errors.New("core database has not been setup yet.") return nil, errors.New("core database has not been setup yet.")
} }
db := Database(&Core{}).First(&CoreApp) db := database.Core().First(&CoreApp)
if db.Error() != nil { if db.Error() != nil {
return nil, db.Error() return nil, db.Error()
} }

View File

@ -30,6 +30,8 @@ var (
skipNewDb bool skipNewDb bool
) )
var configs *types.DbConfig
func init() { func init() {
dir = utils.Directory dir = utils.Directory
utils.InitLogs() utils.InitLogs()
@ -50,27 +52,29 @@ func TestDbConfig_Save(t *testing.T) {
t.SkipNow() t.SkipNow()
} }
config := &DbConfig{&types.DbConfig{ config := &types.DbConfig{
DbConn: "sqlite", DbConn: "sqlite",
Project: "Tester", Project: "Tester",
Location: dir, Location: dir,
}} }
err := config.Save() err := SaveConfig(config)
require.Nil(t, err) require.Nil(t, err)
assert.Equal(t, "sqlite", CoreApp.Config.DbConn) assert.Equal(t, "sqlite", config.DbConn)
assert.NotEmpty(t, CoreApp.Config.ApiKey) assert.NotEmpty(t, config.ApiKey)
assert.NotEmpty(t, CoreApp.Config.ApiSecret) assert.NotEmpty(t, config.ApiSecret)
} }
func TestLoadDbConfig(t *testing.T) { func TestLoadDbConfig(t *testing.T) {
Configs, err := LoadConfigFile(dir) Configs, err := LoadConfigFile(dir)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, "sqlite", Configs.DbConn) assert.Equal(t, "sqlite", Configs.DbConn)
configs = Configs
} }
func TestDbConnection(t *testing.T) { func TestDbConnection(t *testing.T) {
err := CoreApp.Connect(false, dir) err := CoreApp.Connect(configs, false, dir)
assert.Nil(t, err) assert.Nil(t, err)
} }
@ -102,9 +106,9 @@ func TestSeedDatabase(t *testing.T) {
} }
func TestReLoadDbConfig(t *testing.T) { func TestReLoadDbConfig(t *testing.T) {
err := CoreApp.Connect(false, dir) err := CoreApp.Connect(configs, false, dir)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, "sqlite", CoreApp.Config.DbConn) assert.Equal(t, "sqlite", CoreApp.config.DbConn)
} }
func TestSelectCore(t *testing.T) { func TestSelectCore(t *testing.T) {

View File

@ -18,70 +18,35 @@ package core
import ( import (
"bufio" "bufio"
"fmt" "fmt"
"github.com/pkg/errors"
"os"
"path/filepath"
"time"
_ "github.com/jinzhu/gorm/dialects/mysql"
_ "github.com/jinzhu/gorm/dialects/postgres"
_ "github.com/jinzhu/gorm/dialects/sqlite"
"github.com/go-yaml/yaml" "github.com/go-yaml/yaml"
"github.com/hunterlong/statping/core/notifier" "github.com/hunterlong/statping/core/notifier"
"github.com/hunterlong/statping/database" "github.com/hunterlong/statping/database"
"github.com/hunterlong/statping/types" "github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/utils" "github.com/hunterlong/statping/utils"
"github.com/jinzhu/gorm" "github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mysql"
_ "github.com/jinzhu/gorm/dialects/postgres"
_ "github.com/jinzhu/gorm/dialects/sqlite"
"github.com/pkg/errors"
"os"
"path/filepath"
"time"
) )
var ( var (
// DbSession stores the Statping database session // DbSession stores the Statping database session
DbSession database.Database DbModels []interface{}
DbModels []interface{}
) )
func init() { func init() {
DbModels = []interface{}{&types.Service{}, &types.User{}, &types.Hit{}, &types.Failure{}, &types.Message{}, &types.Group{}, &types.Checkin{}, &types.CheckinHit{}, &notifier.Notification{}, &types.Incident{}, &types.IncidentUpdate{}, &types.Integration{}} DbModels = []interface{}{&types.Service{}, &types.User{}, &types.Hit{}, &types.Failure{}, &types.Message{}, &types.Group{}, &types.Checkin{}, &types.CheckinHit{}, &notifier.Notification{}, &types.Incident{}, &types.IncidentUpdate{}, &types.Integration{}}
} }
// DbConfig stores the config.yml file for the statup configuration
type DbConfig struct {
*types.DbConfig
}
func Database(obj interface{}) database.Database {
switch obj.(type) {
case *types.Service, *Service, []*Service:
return DbSession.Model(&types.Service{})
case *types.Hit, *Hit, []*Hit:
return DbSession.Model(&types.Hit{})
case *types.Failure, *Failure, []*Failure:
return DbSession.Model(&types.Failure{})
case *types.Core, *Core:
return DbSession.Table("core").Model(&CoreApp)
case *types.Checkin, *Checkin, []*Checkin:
return DbSession.Model(&types.Checkin{})
case *types.CheckinHit, *CheckinHit, []*CheckinHit:
return DbSession.Model(&types.CheckinHit{})
case *types.User, *User, []*User:
return DbSession.Model(&types.User{})
case *types.Group, *Group, []*Group:
return DbSession.Model(&types.Group{})
case *types.Incident, *Incident, []*Incident:
return DbSession.Model(&types.Incident{})
case *types.IncidentUpdate, *IncidentUpdate, []*IncidentUpdate:
return DbSession.Model(&types.IncidentUpdate{})
case *types.Message, *Message, []*Message:
return DbSession.Model(&types.Message{})
default:
return DbSession
}
}
// CloseDB will close the database connection if available // CloseDB will close the database connection if available
func CloseDB() { func CloseDB() {
if DbSession != nil { database.Close()
DbSession.DB().Close()
}
} }
//// AfterFind for Core will set the timezone //// AfterFind for Core will set the timezone
@ -140,11 +105,11 @@ func CloseDB() {
//} //}
// InsertCore create the single row for the Core settings in Statping // InsertCore create the single row for the Core settings in Statping
func (d *DbConfig) InsertCore() (*Core, error) { func InsertCore(d *types.DbConfig) (*Core, error) {
apiKey := utils.Getenv("API_KEY", utils.NewSHA1Hash(40)) apiKey := utils.Getenv("API_KEY", utils.NewSHA1Hash(40))
apiSecret := utils.Getenv("API_SECRET", utils.NewSHA1Hash(40)) apiSecret := utils.Getenv("API_SECRET", utils.NewSHA1Hash(40))
CoreApp = &Core{Core: &types.Core{ core := &types.Core{
Name: d.Project, Name: d.Project,
Description: d.Description, Description: d.Description,
ConfigFile: "config.yml", ConfigFile: "config.yml",
@ -152,22 +117,34 @@ func (d *DbConfig) InsertCore() (*Core, error) {
ApiSecret: apiSecret.(string), ApiSecret: apiSecret.(string),
Domain: d.Domain, Domain: d.Domain,
MigrationId: time.Now().Unix(), MigrationId: time.Now().Unix(),
Config: d.DbConfig, }
}}
query := DbSession.Create(CoreApp.Core) CoreApp = &Core{Core: core}
return CoreApp, query.Error()
CoreApp.config = d
_, err := database.Create(CoreApp.Core)
return CoreApp, err
} }
func findDbFile() string { func findDbFile() string {
if CoreApp.Config.SqlFile != "" { if CoreApp.config == nil {
return CoreApp.Config.SqlFile return findSQLin(utils.Directory)
} }
if CoreApp.config.SqlFile != "" {
return CoreApp.config.SqlFile
}
return utils.Directory + "/" + types.SqliteFilename
}
func findSQLin(path string) string {
filename := types.SqliteFilename filename := types.SqliteFilename
err := filepath.Walk(utils.Directory, func(path string, info os.FileInfo, err error) error { err := filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
if info.IsDir() { if info.IsDir() {
return nil return nil
} }
if filepath.Ext(path) == ".db" { if filepath.Ext(path) == ".db" {
fmt.Println("DB file is now: ", info.Name())
filename = info.Name() filename = info.Name()
} }
return nil return nil
@ -179,20 +156,19 @@ func findDbFile() string {
} }
// Connect will attempt to connect to the sqlite, postgres, or mysql database // Connect will attempt to connect to the sqlite, postgres, or mysql database
func (c *Core) Connect(retry bool, location string) error { func (c *Core) Connect(configs *types.DbConfig, retry bool, location string) error {
postgresSSL := os.Getenv("POSTGRES_SSLMODE") postgresSSL := os.Getenv("POSTGRES_SSLMODE")
if DbSession != nil { if database.Available() {
return nil return nil
} }
var conn string var conn string
var err error var err error
configs := c.Config
switch configs.DbConn { switch configs.DbConn {
case "sqlite": case "sqlite", "sqlite3":
sqlFilename := findDbFile() conn = findDbFile()
conn = sqlFilename configs.SqlFile = fmt.Sprintf("%s/%s", utils.Directory, conn)
log.Infof("SQL database file at: %v/%v", utils.Directory, conn) log.Infof("SQL database file at: %s", configs.SqlFile)
configs.DbConn = "sqlite3" configs.DbConn = "sqlite3"
case "mysql": case "mysql":
host := fmt.Sprintf("%v:%v", configs.DbHost, configs.DbPort) host := fmt.Sprintf("%v:%v", configs.DbHost, configs.DbPort)
@ -210,10 +186,10 @@ func (c *Core) Connect(retry bool, location string) error {
log.WithFields(utils.ToFields(c, conn)).Debugln("attempting to connect to database") log.WithFields(utils.ToFields(c, conn)).Debugln("attempting to connect to database")
dbSession, err := database.Openw(configs.DbConn, conn) dbSession, err := database.Openw(configs.DbConn, conn)
if err != nil { if err != nil {
log.Debugln(fmt.Sprintf("Database connection error %v", err)) log.Debugln(fmt.Sprintf("Database connection error %s", err))
if retry { if retry {
log.Errorln(fmt.Sprintf("Database connection to '%v' is not available, trying again in 5 seconds...", configs.DbHost)) log.Errorln(fmt.Sprintf("Database %s connection to '%s' is not available, trying again in 5 seconds...", configs.DbConn, configs.DbHost))
return c.waitForDb() return c.waitForDb(configs)
} else { } else {
return err return err
} }
@ -229,19 +205,21 @@ func (c *Core) Connect(retry bool, location string) error {
dbSession.DB().SetConnMaxLifetime(maxLifeConn.(time.Duration)) dbSession.DB().SetConnMaxLifetime(maxLifeConn.(time.Duration))
if dbSession.DB().Ping() == nil { if dbSession.DB().Ping() == nil {
DbSession = dbSession
if utils.VerboseMode >= 4 { if utils.VerboseMode >= 4 {
DbSession.LogMode(true).Debug().SetLogger(gorm.Logger{log}) database.LogMode(true).Debug().SetLogger(gorm.Logger{log})
} }
log.Infoln(fmt.Sprintf("Database %v connection was successful.", configs.DbConn)) log.Infoln(fmt.Sprintf("Database %v connection was successful.", configs.DbConn))
} }
CoreApp.config = configs
return err return err
} }
// waitForDb will sleep for 5 seconds and try to connect to the database again // waitForDb will sleep for 5 seconds and try to connect to the database again
func (c *Core) waitForDb() error { func (c *Core) waitForDb(configs *types.DbConfig) error {
time.Sleep(5 * time.Second) time.Sleep(5 * time.Second)
return c.Connect(true, utils.Directory) return c.Connect(configs, true, utils.Directory)
} }
// Update will save the config.yml file // Update will save the config.yml file
@ -252,18 +230,20 @@ func (c *Core) UpdateConfig() error {
log.Errorln(err) log.Errorln(err)
return err return err
} }
data, err := yaml.Marshal(c.Config) defer config.Close()
data, err := yaml.Marshal(c.config)
if err != nil { if err != nil {
log.Errorln(err) log.Errorln(err)
return err return err
} }
config.WriteString(string(data)) config.WriteString(string(data))
config.Close()
return err return err
} }
// Save will initially create the config.yml file // Save will initially create the config.yml file
func (d *DbConfig) Save() error { func SaveConfig(d *types.DbConfig) error {
config, err := os.Create(utils.Directory + "/config.yml") config, err := os.Create(utils.Directory + "/config.yml")
if err != nil { if err != nil {
log.Errorln(err) log.Errorln(err)
@ -271,20 +251,28 @@ func (d *DbConfig) Save() error {
} }
defer config.Close() defer config.Close()
log.WithFields(utils.ToFields(d)).Debugln("saving config file at: " + utils.Directory + "/config.yml") log.WithFields(utils.ToFields(d)).Debugln("saving config file at: " + utils.Directory + "/config.yml")
CoreApp.Config = d.DbConfig
apiKey := utils.Getenv("API_KEY", utils.NewSHA1Hash(16)) if d.ApiKey == "" || d.ApiSecret == "" {
apiSecret := utils.Getenv("API_SECRET", utils.NewSHA1Hash(16)) apiKey := utils.Getenv("API_KEY", utils.NewSHA1Hash(16))
apiSecret := utils.Getenv("API_SECRET", utils.NewSHA1Hash(16))
CoreApp.config.ApiKey = apiKey.(string)
CoreApp.config.ApiSecret = apiSecret.(string)
}
if d.DbConn == "sqlite3" {
d.DbConn = "sqlite"
}
CoreApp.Config.ApiKey = apiKey.(string)
CoreApp.Config.ApiSecret = apiSecret.(string)
data, err := yaml.Marshal(d) data, err := yaml.Marshal(d)
if err != nil { if err != nil {
log.Errorln(err) log.Errorln(err)
return err return err
} }
config.WriteString(string(data)) if _, err := config.WriteString(string(data)); err != nil {
log.WithFields(utils.ToFields(d)).Infoln("saved config file at: " + utils.Directory + "/config.yml") return errors.Wrap(err, "error writing to config.yml")
}
log.WithFields(utils.ToFields(d)).Infoln("Saved config file at: " + utils.Directory + "/config.yml")
CoreApp.config = d
return err return err
} }
@ -299,8 +287,8 @@ func (c *Core) CreateCore() *Core {
Domain: c.Domain, Domain: c.Domain,
MigrationId: time.Now().Unix(), MigrationId: time.Now().Unix(),
} }
db := Database(newCore).Create(&newCore) _, err := database.Create(&newCore)
if db.Error() == nil { if err == nil {
CoreApp = &Core{Core: newCore} CoreApp = &Core{Core: newCore}
} }
CoreApp, err := SelectCore() CoreApp, err := SelectCore()
@ -313,11 +301,11 @@ func (c *Core) CreateCore() *Core {
// DropDatabase will DROP each table Statping created // DropDatabase will DROP each table Statping created
func (c *Core) DropDatabase() error { func (c *Core) DropDatabase() error {
log.Infoln("Dropping Database Tables...") log.Infoln("Dropping Database Tables...")
tables := []string{"checkins", "checkin_hits", "notifications", "core", "failures", "hits", "services", "users", "messages", "incidents", "incident_updates"} for _, t := range DbModels {
for _, t := range tables { if err := database.Get().DropTableIfExists(t); err != nil {
if err := DbSession.DropTableIfExists(t); err != nil {
return err.Error() return err.Error()
} }
log.Infof("Dropped table: %T\n", t)
} }
return nil return nil
} }
@ -327,11 +315,11 @@ func (c *Core) CreateDatabase() error {
var err error var err error
log.Infoln("Creating Database Tables...") log.Infoln("Creating Database Tables...")
for _, table := range DbModels { for _, table := range DbModels {
if err := DbSession.CreateTable(table); err.Error() != nil { if err := database.Get().CreateTable(table); err.Error() != nil {
return err.Error() return err.Error()
} }
} }
if err := DbSession.Table("core").CreateTable(&types.Core{}); err.Error() != nil { if err := database.Get().Table("core").CreateTable(&types.Core{}); err.Error() != nil {
return err.Error() return err.Error()
} }
log.Infoln("Statping Database Created") log.Infoln("Statping Database Created")
@ -394,7 +382,7 @@ func (c *Core) ServicesFromEnvFile() error {
// If this function has an issue, it will ROLLBACK to the previous state. // If this function has an issue, it will ROLLBACK to the previous state.
func (c *Core) MigrateDatabase() error { func (c *Core) MigrateDatabase() error {
log.Infoln("Migrating Database Tables...") log.Infoln("Migrating Database Tables...")
tx := DbSession.Begin() tx := database.Begin("migration")
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
tx.Rollback() tx.Rollback()
@ -412,11 +400,11 @@ func (c *Core) MigrateDatabase() error {
log.Errorln(fmt.Sprintf("Statping Database could not be migrated: %v", tx.Error())) log.Errorln(fmt.Sprintf("Statping Database could not be migrated: %v", tx.Error()))
return tx.Error() return tx.Error()
} }
log.Infoln("Statping Database Migrated")
if err := tx.Commit().Error(); err != nil { if err := tx.Commit().Error(); err != nil {
return err return err
} }
log.Infoln("Statping Database Migrated")
return nil return nil
} }

View File

@ -27,9 +27,3 @@ const (
limitedFailures = 32 limitedFailures = 32
limitedHits = 32 limitedHits = 32
) )
// Delete will remove a Failure record from the database
func (f *Failure) Delete() error {
db := Database(&types.Failure{}).Delete(f)
return db.Error()
}

View File

@ -2,7 +2,6 @@ package core
import ( import (
"github.com/hunterlong/statping/types" "github.com/hunterlong/statping/types"
"time"
) )
type Incident struct { type Incident struct {
@ -17,69 +16,3 @@ type IncidentUpdate struct {
func ReturnIncident(u *types.Incident) *Incident { func ReturnIncident(u *types.Incident) *Incident {
return &Incident{u} return &Incident{u}
} }
// SelectIncident returns the Incident based on the Incident's ID.
func SelectIncident(id int64) (*Incident, error) {
var incident Incident
err := Database(incident).Where("id = ?", id).First(&incident)
return &incident, err.Error()
}
// AllIncidents will return all incidents and updates recorded
func AllIncidents() []*Incident {
var incidents []*Incident
Database(incidents).Find(&incidents).Order("id desc")
for _, i := range incidents {
var updates []*types.IncidentUpdate
Database(updates).Find(&updates).Order("id desc")
i.Updates = updates
}
return incidents
}
// Incidents will return the all incidents for a service
func (s *Service) Incidents() []*Incident {
var incidentArr []*Incident
Database(incidentArr).Where("service = ?", s.Id).Order("id desc").Find(&incidentArr)
return incidentArr
}
// AllUpdates will return the all updates for an incident
func (i *Incident) AllUpdates() []*IncidentUpdate {
var updatesArr []*IncidentUpdate
Database(updatesArr).Where("incident = ?", i.Id).Order("id desc").Find(&updatesArr)
return updatesArr
}
// Delete will remove a incident
func (i *Incident) Delete() error {
err := Database(i).Delete(i)
return err.Error()
}
// Create will create a incident and insert it into the database
func (i *Incident) Create() (int64, error) {
i.CreatedAt = time.Now().UTC()
db := Database(i).Create(i)
return i.Id, db.Error()
}
// Update will update a incident
func (i *Incident) Update() (int64, error) {
i.UpdatedAt = time.Now().UTC()
db := Database(i).Update(i)
return i.Id, db.Error()
}
// Delete will remove a incident update
func (i *IncidentUpdate) Delete() error {
err := Database(i).Delete(i)
return err.Error()
}
// Create will create a incident update and insert it into the database
func (i *IncidentUpdate) Create() (int64, error) {
i.CreatedAt = time.Now().UTC()
db := Database(i).Create(i)
return i.Id, db.Error()
}

View File

@ -16,64 +16,14 @@
package core package core
import ( import (
"fmt"
"github.com/hunterlong/statping/types" "github.com/hunterlong/statping/types"
"time"
) )
type Message struct { type Message struct {
*types.Message *types.Message
} }
// SelectServiceMessages returns all messages for a service
func SelectServiceMessages(id int64) []*Message {
var message []*Message
Database(&Message{}).Where("service = ?", id).Limit(10).Find(&message)
return message
}
// ReturnMessage will convert *types.Message to *core.Message // ReturnMessage will convert *types.Message to *core.Message
func ReturnMessage(m *types.Message) *Message { func ReturnMessage(m *types.Message) *Message {
return &Message{m} return &Message{m}
} }
// SelectMessages returns all messages
func SelectMessages() ([]*Message, error) {
var messages []*Message
db := Database(&Message{}).Find(&messages).Order("id desc")
return messages, db.Error()
}
// SelectMessage returns a Message based on the ID passed
func SelectMessage(id int64) (*Message, error) {
var message Message
db := Database(&Message{}).Where("id = ?", id).Find(&message)
return &message, db.Error()
}
// Create will create a Message and insert it into the database
func (m *Message) Create() (int64, error) {
m.CreatedAt = time.Now().UTC()
db := Database(&Message{}).Create(m)
if db.Error() != nil {
log.Errorln(fmt.Sprintf("Failed to create message %v #%v: %v", m.Title, m.Id, db.Error()))
return 0, db.Error()
}
return m.Id, nil
}
// Delete will delete a Message from database
func (m *Message) Delete() error {
db := Database(&Message{}).Delete(m)
return db.Error()
}
// Update will update a Message in the database
func (m *Message) Update() (*Message, error) {
db := Database(&Message{}).Update(m)
if db.Error() != nil {
log.Errorln(fmt.Sprintf("Failed to update message %v #%v: %v", m.Title, m.Id, db.Error()))
return nil, db.Error()
}
return m, nil
}

View File

@ -21,11 +21,12 @@ import (
"github.com/hunterlong/statping/database" "github.com/hunterlong/statping/database"
"github.com/hunterlong/statping/types" "github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/utils" "github.com/hunterlong/statping/utils"
"sync"
"time"
_ "github.com/jinzhu/gorm/dialects/mysql" _ "github.com/jinzhu/gorm/dialects/mysql"
_ "github.com/jinzhu/gorm/dialects/postgres" _ "github.com/jinzhu/gorm/dialects/postgres"
_ "github.com/jinzhu/gorm/dialects/sqlite" _ "github.com/jinzhu/gorm/dialects/sqlite"
"sync"
"time"
) )
var ( var (
@ -271,7 +272,7 @@ func insertSampleCheckins() error {
// InsertSampleHits will create a couple new hits for the sample services // InsertSampleHits will create a couple new hits for the sample services
func InsertSampleHits() error { func InsertSampleHits() error {
tx := Database(&Hit{}).Begin() tx := database.Begin(&types.Hit{})
sg := new(sync.WaitGroup) sg := new(sync.WaitGroup)
for i := int64(1); i <= 5; i++ { for i := int64(1); i <= 5; i++ {
sg.Add(1) sg.Add(1)
@ -280,7 +281,7 @@ func InsertSampleHits() error {
log.Infoln(fmt.Sprintf("Adding %v sample hit records to service %v", SampleHits, service.Name)) log.Infoln(fmt.Sprintf("Adding %v sample hit records to service %v", SampleHits, service.Name))
createdAt := sampleStart createdAt := sampleStart
p := utils.NewPerlin(2., 2., 10, seed) p := utils.NewPerlin(2., 2., 10, seed)
go func() { go func(sg *sync.WaitGroup) {
defer sg.Done() defer sg.Done()
for hi := 0.; hi <= float64(SampleHits); hi++ { for hi := 0.; hi <= float64(SampleHits); hi++ {
latency := p.Noise1D(hi / 500) latency := p.Noise1D(hi / 500)
@ -292,7 +293,7 @@ func InsertSampleHits() error {
} }
tx = tx.Create(&hit) tx = tx.Create(&hit)
} }
}() }(sg)
} }
sg.Wait() sg.Wait()
if err := tx.Commit().Error(); err != nil { if err := tx.Commit().Error(); err != nil {
@ -626,14 +627,14 @@ func TmpRecords(dbFile string) error {
CoreApp = NewCore() CoreApp = NewCore()
CoreApp.Name = "Tester" CoreApp.Name = "Tester"
CoreApp.Setup = true CoreApp.Setup = true
configs := &DbConfig{&types.DbConfig{ configs := &types.DbConfig{
DbConn: "sqlite", DbConn: "sqlite",
Project: "Tester", Project: "Tester",
Location: utils.Directory, Location: utils.Directory,
SqlFile: sqlFile, SqlFile: sqlFile,
}} }
log.Infoln("saving config.yml in: " + utils.Directory) log.Infoln("saving config.yml in: " + utils.Directory)
if err := configs.Save(); err != nil { if err := SaveConfig(configs); err != nil {
log.Error(err) log.Error(err)
} }
log.Infoln("loading config.yml from: " + utils.Directory) log.Infoln("loading config.yml from: " + utils.Directory)
@ -653,7 +654,7 @@ func TmpRecords(dbFile string) error {
} }
log.Infoln("loading config.yml from: " + utils.Directory) log.Infoln("loading config.yml from: " + utils.Directory)
if err := CoreApp.Connect(false, utils.Directory); err != nil { if err := CoreApp.Connect(configs, false, utils.Directory); err != nil {
log.Error(err) log.Error(err)
} }
log.Infoln("selecting the Core variable") log.Infoln("selecting the Core variable")
@ -684,7 +685,7 @@ func TmpRecords(dbFile string) error {
log.Infoln(tmpSqlFile + " not found, creating a new database...") log.Infoln(tmpSqlFile + " not found, creating a new database...")
if err := CoreApp.Connect(false, utils.Directory); err != nil { if err := CoreApp.Connect(configs, false, utils.Directory); err != nil {
return err return err
} }
log.Infoln("creating database") log.Infoln("creating database")

View File

@ -58,9 +58,9 @@ func TestSelectCheckin(t *testing.T) {
func TestUpdateCheckin(t *testing.T) { func TestUpdateCheckin(t *testing.T) {
testCheckin.Interval = 60 testCheckin.Interval = 60
testCheckin.GracePeriod = 15 testCheckin.GracePeriod = 15
id, err := testCheckin.Update() err := database.Update(testCheckin)
assert.Nil(t, err) assert.Nil(t, err)
assert.NotZero(t, id) assert.NotZero(t, testCheckin.Id)
assert.NotEmpty(t, testCheckin.ApiKey) assert.NotEmpty(t, testCheckin.ApiKey)
service := SelectService(1) service := SelectService(1)
checkin := service.Checkins()[0] checkin := service.Checkins()[0]
@ -82,8 +82,9 @@ func TestCreateCheckinHits(t *testing.T) {
} }
_, err := database.Create(hit) _, err := database.Create(hit)
require.Nil(t, err) require.Nil(t, err)
hits := testCheckin.AllHits()
assert.Equal(t, 1, len(hits)) checks := service.Checkins()
assert.Equal(t, 1, len(checks))
} }
func TestSelectCheckinMethods(t *testing.T) { func TestSelectCheckinMethods(t *testing.T) {

View File

@ -33,13 +33,6 @@ func uwrap(u *database.UserObj) *User {
return &User{u} return &User{u}
} }
// CountUsers returns the amount of users
func CountUsers() int64 {
var amount int64
Database(&User{}).Count(&amount)
return amount
}
// SelectUser returns the User based on the User's ID. // SelectUser returns the User based on the User's ID.
func SelectUser(id int64) (*User, error) { func SelectUser(id int64) (*User, error) {
user, err := database.User(id) user, err := database.User(id)

View File

@ -112,6 +112,6 @@ func TestDeleteUser(t *testing.T) {
} }
func TestDbConfig_Close(t *testing.T) { func TestDbConfig_Close(t *testing.T) {
err := DbSession.Close() err := database.Close()
assert.Nil(t, err) assert.Nil(t, err)
} }

View File

@ -3,6 +3,10 @@ package database
import ( import (
"github.com/hunterlong/statping/types" "github.com/hunterlong/statping/types"
"reflect" "reflect"
_ "github.com/jinzhu/gorm/dialects/mysql"
_ "github.com/jinzhu/gorm/dialects/postgres"
_ "github.com/jinzhu/gorm/dialects/sqlite"
) )
type CrudObject interface { type CrudObject interface {

View File

@ -4,12 +4,13 @@ import (
"database/sql" "database/sql"
"github.com/hunterlong/statping/types" "github.com/hunterlong/statping/types"
"github.com/jinzhu/gorm" "github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mysql"
_ "github.com/jinzhu/gorm/dialects/postgres"
_ "github.com/jinzhu/gorm/dialects/sqlite"
"net/http" "net/http"
"strings" "strings"
"time" "time"
_ "github.com/jinzhu/gorm/dialects/mysql"
_ "github.com/jinzhu/gorm/dialects/postgres"
_ "github.com/jinzhu/gorm/dialects/sqlite"
) )
const ( const (
@ -117,6 +118,7 @@ type Database interface {
} }
type Objects interface { type Objects interface {
Core() Database
Services() Database Services() Database
Users() Database Users() Database
Groups() Database Groups() Database
@ -130,6 +132,51 @@ type Objects interface {
Integrations() Database Integrations() Database
} }
func Close() error {
if database == nil {
return nil
}
return database.Close()
}
func LogMode(b bool) Database {
return database.LogMode(b)
}
func Begin(model interface{}) Database {
if all, ok := model.(string); ok {
if all == "migration" {
return database.Begin()
}
}
return database.Model(model).Begin()
}
func Core() Database {
return database.Core()
}
func Get() Database {
if Available() {
return database
}
return nil
}
func Available() bool {
if database == nil {
return false
}
if err := database.DB().Ping(); err != nil {
return false
}
return true
}
func (d *Db) Core() Database {
return d.Table("core").Model(&types.Service{})
}
func (d *Db) Services() Database { func (d *Db) Services() Database {
return d.Model(&types.Service{}) return d.Model(&types.Service{})
} }

View File

@ -6,6 +6,10 @@ import (
"github.com/hunterlong/statping/utils" "github.com/hunterlong/statping/utils"
"os" "os"
"time" "time"
_ "github.com/jinzhu/gorm/dialects/mysql"
_ "github.com/jinzhu/gorm/dialects/postgres"
_ "github.com/jinzhu/gorm/dialects/sqlite"
) )
var ( var (
@ -39,6 +43,8 @@ func StartMaintenceRoutine() {
func databaseMaintence(dur time.Duration) { func databaseMaintence(dur time.Duration) {
deleteAfter := time.Now().UTC().Add(dur) deleteAfter := time.Now().UTC().Add(dur)
time.Sleep(20 * types.Second)
for range time.Tick(maintenceDuration) { for range time.Tick(maintenceDuration) {
log.Infof("Deleting failures older than %s", dur.String()) log.Infof("Deleting failures older than %s", dur.String())
DeleteAllSince("failures", deleteAfter) DeleteAllSince("failures", deleteAfter)

View File

@ -6,13 +6,13 @@ services:
container_name: postgres container_name: postgres
image: postgres image: postgres
volumes: volumes:
- ./docker/databases/postgres:/var/lib/postgresql/data - ../docker/databases/postgres:/var/lib/postgresql/data
environment: environment:
POSTGRES_PASSWORD: password123 POSTGRES_PASSWORD: password123
POSTGRES_DB: statping POSTGRES_DB: statping
POSTGRES_USER: root POSTGRES_USER: root
networks: ports:
- statping - 5432:5432
healthcheck: healthcheck:
test: ["CMD-SHELL", "pg_isready -U root"] test: ["CMD-SHELL", "pg_isready -U root"]
interval: 15s interval: 15s
@ -23,15 +23,15 @@ services:
container_name: mysql container_name: mysql
image: mysql:5.7 image: mysql:5.7
volumes: volumes:
- ./docker/databases/mysql:/var/lib/mysql - ../docker/databases/mysql:/var/lib/mysql
restart: always restart: always
environment: environment:
MYSQL_ROOT_PASSWORD: password123 MYSQL_ROOT_PASSWORD: password123
MYSQL_DATABASE: statping MYSQL_DATABASE: statping
MYSQL_USER: root MYSQL_USER: root
MYSQL_PASSWORD: password MYSQL_PASSWORD: password
networks: ports:
- statping - 3306:3306
healthcheck: healthcheck:
test: [ "CMD", "mysqladmin" ,"ping", "-h", "localhost" ] test: [ "CMD", "mysqladmin" ,"ping", "-h", "localhost" ]
timeout: 20s timeout: 20s
@ -55,27 +55,18 @@ services:
PMA_USER: root PMA_USER: root
PMA_PASSWORD: password123 PMA_PASSWORD: password123
PMA_PORT: 3306 PMA_PORT: 3306
networks:
- statping
sqlite-web: sqlite-web:
container_name: sqlite-web container_name: sqlite-web
image: coleifer/sqlite-web image: coleifer/sqlite-web
restart: on-failure restart: on-failure
command: sqlite_web -H 0.0.0.0 -r -x /data/statping.db command: sqlite_web -H 0.0.0.0 -r -x /data/statping.db
depends_on:
statping:
condition: service_healthy
ports: ports:
- 6050:8080 - 6050:8080
links:
- statping
volumes: volumes:
- ./docker/statping/sqlite/statping.db:/data/statping.db:ro - ../docker/statping/sqlite/statping.db:/data/statping.db:ro
environment: environment:
SQLITE_DATABASE: /data/statping.db SQLITE_DATABASE: /data/statping.db
networks:
- statping
pgadmin4: pgadmin4:
container_name: pgadmin4 container_name: pgadmin4
@ -91,25 +82,16 @@ services:
- 7000:5050 - 7000:5050
links: links:
- postgres:postgres - postgres:postgres
networks:
- statping
prometheus: prometheus:
container_name: prometheus container_name: prometheus
image: prom/prometheus:v2.0.0 image: prom/prometheus:v2.0.0
restart: on-failure restart: on-failure
volumes: volumes:
- ./dev/prometheus.yml:/etc/prometheus/prometheus.yml - ./prometheus.yml:/etc/prometheus/prometheus.yml
- ./docker/databases/prometheus:/prometheus - ../docker/databases/prometheus:/prometheus
links:
- statping
- statping_mysql
- statping_postgres
- statping_dev
ports: ports:
- 7050:9090 - 7050:9090
networks:
- statping
healthcheck: healthcheck:
test: "/bin/wget -q -Y off http://localhost:9090/status -O /dev/null > /dev/null 2>&1" test: "/bin/wget -q -Y off http://localhost:9090/status -O /dev/null > /dev/null 2>&1"
interval: 10s interval: 10s

View File

@ -52,7 +52,7 @@ func apiRenewHandler(w http.ResponseWriter, r *http.Request) {
var err error var err error
core.CoreApp.ApiKey = utils.NewSHA1Hash(40) core.CoreApp.ApiKey = utils.NewSHA1Hash(40)
core.CoreApp.ApiSecret = utils.NewSHA1Hash(40) core.CoreApp.ApiSecret = utils.NewSHA1Hash(40)
core.CoreApp, err = core.UpdateCore(core.CoreApp) err = database.Update(core.CoreApp)
if err != nil { if err != nil {
sendErrorJson(err, w, r) sendErrorJson(err, w, r)
return return
@ -91,7 +91,7 @@ func apiCoreHandler(w http.ResponseWriter, r *http.Request) {
app.Timezone = c.Timezone app.Timezone = c.Timezone
} }
app.UseCdn = types.NewNullBool(c.UseCdn.Bool) app.UseCdn = types.NewNullBool(c.UseCdn.Bool)
core.CoreApp, err = core.UpdateCore(app) err = database.Update(app)
returnJson(core.CoreApp, w, r) returnJson(core.CoreApp, w, r)
} }

View File

@ -36,7 +36,6 @@ func TestResetDatabase(t *testing.T) {
t.Log(err) t.Log(err)
require.Nil(t, err) require.Nil(t, err)
require.NotNil(t, core.CoreApp) require.NotNil(t, core.CoreApp)
require.NotNil(t, core.CoreApp.Config)
} }
func TestFailedHTTPServer(t *testing.T) { func TestFailedHTTPServer(t *testing.T) {

View File

@ -24,17 +24,17 @@ func apiCreateIncidentHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
newIncident := core.ReturnIncident(incident) newIncident := core.ReturnIncident(incident)
_, err = newIncident.Create() obj, err := database.Create(newIncident)
if err != nil { if err != nil {
sendErrorJson(err, w, r) sendErrorJson(err, w, r)
return return
} }
sendJsonAction(newIncident, "create", w, r) sendJsonAction(obj, "create", w, r)
} }
func apiIncidentUpdateHandler(w http.ResponseWriter, r *http.Request) { func apiIncidentUpdateHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r) vars := mux.Vars(r)
incident, err := core.SelectIncident(utils.ToInt(vars["id"])) incident, err := database.Incident(utils.ToInt(vars["id"]))
if err != nil { if err != nil {
sendErrorJson(err, w, r) sendErrorJson(err, w, r)
return return
@ -47,7 +47,7 @@ func apiIncidentUpdateHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
_, err = incident.Update() err = database.Update(&incident)
if err != nil { if err != nil {
sendErrorJson(err, w, r) sendErrorJson(err, w, r)
return return
@ -57,12 +57,12 @@ func apiIncidentUpdateHandler(w http.ResponseWriter, r *http.Request) {
func apiDeleteIncidentHandler(w http.ResponseWriter, r *http.Request) { func apiDeleteIncidentHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r) vars := mux.Vars(r)
incident, err := core.SelectIncident(utils.ToInt(vars["id"])) incident, err := database.Incident(utils.ToInt(vars["id"]))
if err != nil { if err != nil {
sendErrorJson(err, w, r) sendErrorJson(err, w, r)
return return
} }
err = incident.Delete() err = database.Delete(incident)
if err != nil { if err != nil {
sendErrorJson(err, w, r) sendErrorJson(err, w, r)
return return

View File

@ -32,7 +32,7 @@ func healthCheckHandler(w http.ResponseWriter, r *http.Request) {
health := map[string]interface{}{ health := map[string]interface{}{
"services": len(core.Services()), "services": len(core.Services()),
"online": true, "online": true,
"setup": core.CoreApp.Config != nil, "setup": core.IsSetup(),
} }
returnJson(health, w, r) returnJson(health, w, r)
} }

View File

@ -19,27 +19,15 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/hunterlong/statping/core" "github.com/hunterlong/statping/database"
"github.com/hunterlong/statping/types" "github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/utils" "github.com/hunterlong/statping/utils"
"net/http" "net/http"
) )
func apiAllMessagesHandler(r *http.Request) interface{} { func apiAllMessagesHandler(r *http.Request) interface{} {
messages, err := core.SelectMessages() messages := database.AllMessages()
if err != nil { return messages
log.Error(err)
return nil
}
return joinMessages(messages)
}
func joinMessages(messages []*core.Message) []*types.Message {
var m []*types.Message
for _, v := range messages {
m = append(m, v.Message)
}
return m
} }
func apiMessageCreateHandler(w http.ResponseWriter, r *http.Request) { func apiMessageCreateHandler(w http.ResponseWriter, r *http.Request) {
@ -50,18 +38,17 @@ func apiMessageCreateHandler(w http.ResponseWriter, r *http.Request) {
sendErrorJson(err, w, r) sendErrorJson(err, w, r)
return return
} }
msg := core.ReturnMessage(message) _, err = database.Create(message)
_, err = msg.Create()
if err != nil { if err != nil {
sendErrorJson(err, w, r) sendErrorJson(err, w, r)
return return
} }
sendJsonAction(msg, "create", w, r) sendJsonAction(message, "create", w, r)
} }
func apiMessageGetHandler(w http.ResponseWriter, r *http.Request) { func apiMessageGetHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r) vars := mux.Vars(r)
message, err := core.SelectMessage(utils.ToInt(vars["id"])) message, err := database.Message(utils.ToInt(vars["id"]))
if err != nil { if err != nil {
sendErrorJson(err, w, r) sendErrorJson(err, w, r)
return return
@ -71,12 +58,12 @@ func apiMessageGetHandler(w http.ResponseWriter, r *http.Request) {
func apiMessageDeleteHandler(w http.ResponseWriter, r *http.Request) { func apiMessageDeleteHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r) vars := mux.Vars(r)
message, err := core.SelectMessage(utils.ToInt(vars["id"])) message, err := database.Message(utils.ToInt(vars["id"]))
if err != nil { if err != nil {
sendErrorJson(err, w, r) sendErrorJson(err, w, r)
return return
} }
err = message.Delete() err = database.Delete(message)
if err != nil { if err != nil {
sendErrorJson(err, w, r) sendErrorJson(err, w, r)
return return
@ -86,7 +73,7 @@ func apiMessageDeleteHandler(w http.ResponseWriter, r *http.Request) {
func apiMessageUpdateHandler(w http.ResponseWriter, r *http.Request) { func apiMessageUpdateHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r) vars := mux.Vars(r)
message, err := core.SelectMessage(utils.ToInt(vars["id"])) message, err := database.Message(utils.ToInt(vars["id"]))
if err != nil { if err != nil {
sendErrorJson(fmt.Errorf("message #%v was not found", vars["id"]), w, r) sendErrorJson(fmt.Errorf("message #%v was not found", vars["id"]), w, r)
return return
@ -97,7 +84,7 @@ func apiMessageUpdateHandler(w http.ResponseWriter, r *http.Request) {
sendErrorJson(err, w, r) sendErrorJson(err, w, r)
return return
} }
_, err = message.Update() err = database.Update(message)
if err != nil { if err != nil {
sendErrorJson(err, w, r) sendErrorJson(err, w, r)
return return

View File

@ -135,7 +135,7 @@ func cached(duration, contentType string, handler func(w http.ResponseWriter, r
content := CacheStorage.Get(r.RequestURI) content := CacheStorage.Get(r.RequestURI)
w.Header().Set("Content-Type", contentType) w.Header().Set("Content-Type", contentType)
w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Origin", "*")
if core.CoreApp.Config == nil { if !core.IsSetup() {
handler(w, r) handler(w, r)
return return
} }

View File

@ -52,7 +52,7 @@ func processSetupHandler(w http.ResponseWriter, r *http.Request) {
sample, _ := strconv.ParseBool(r.PostForm.Get("sample_data")) sample, _ := strconv.ParseBool(r.PostForm.Get("sample_data"))
dir := utils.Directory dir := utils.Directory
config := &core.DbConfig{DbConfig: &types.DbConfig{ configs := &types.DbConfig{
DbConn: dbConn, DbConn: dbConn,
DbHost: dbHost, DbHost: dbHost,
DbUser: dbUser, DbUser: dbUser,
@ -67,11 +67,11 @@ func processSetupHandler(w http.ResponseWriter, r *http.Request) {
Email: email, Email: email,
Error: nil, Error: nil,
Location: utils.Directory, Location: utils.Directory,
}} }
log.WithFields(utils.ToFields(core.CoreApp, config)).Debugln("new configs posted") log.WithFields(utils.ToFields(core.CoreApp, configs)).Debugln("new configs posted")
if err := config.Save(); err != nil { if err := core.SaveConfig(configs); err != nil {
log.Errorln(err) log.Errorln(err)
sendErrorJson(err, w, r) sendErrorJson(err, w, r)
return return
@ -83,7 +83,7 @@ func processSetupHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
if err = core.CoreApp.Connect(false, dir); err != nil { if err = core.CoreApp.Connect(configs, false, dir); err != nil {
log.Errorln(err) log.Errorln(err)
core.DeleteConfig() core.DeleteConfig()
sendErrorJson(err, w, r) sendErrorJson(err, w, r)
@ -100,7 +100,7 @@ func processSetupHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
core.CoreApp, err = config.InsertCore() core.CoreApp, err = core.InsertCore(configs)
if err != nil { if err != nil {
log.Errorln(err) log.Errorln(err)
sendErrorJson(err, w, r) sendErrorJson(err, w, r)
@ -108,9 +108,9 @@ func processSetupHandler(w http.ResponseWriter, r *http.Request) {
} }
admin := &types.User{ admin := &types.User{
Username: config.Username, Username: configs.Username,
Password: config.Password, Password: configs.Password,
Email: config.Email, Email: configs.Email,
Admin: types.NewNullBool(true), Admin: types.NewNullBool(true),
} }
database.Create(admin) database.Create(admin)
@ -129,8 +129,8 @@ func processSetupHandler(w http.ResponseWriter, r *http.Request) {
Message string `json:"message"` Message string `json:"message"`
Config *types.DbConfig `json:"config"` Config *types.DbConfig `json:"config"`
}{ }{
"okokok", "success",
config.DbConfig, configs,
} }
returnJson(out, w, r) returnJson(out, w, r)
} }

View File

@ -60,8 +60,8 @@ func apiUserUpdateHandler(w http.ResponseWriter, r *http.Request) {
func apiUserDeleteHandler(w http.ResponseWriter, r *http.Request) { func apiUserDeleteHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r) vars := mux.Vars(r)
users := core.CountUsers() users := database.AllUsers()
if users == 1 { if len(users) == 1 {
sendErrorJson(errors.New("cannot delete the last user"), w, r) sendErrorJson(errors.New("cannot delete the last user"), w, r)
return return
} }

View File

@ -78,7 +78,7 @@ func TestSaveAndCompileAsset(t *testing.T) {
themeCSS, err := utils.OpenFile(dir + "/assets/css/theme.css") themeCSS, err := utils.OpenFile(dir + "/assets/css/theme.css")
require.Nil(t, err) require.Nil(t, err)
assert.Equal(t, scssData, themeCSS) assert.Contains(t, themeCSS, `color: #333;`)
} }
func TestOpenAsset(t *testing.T) { func TestOpenAsset(t *testing.T) {
@ -88,12 +88,7 @@ func TestOpenAsset(t *testing.T) {
func TestDeleteAssets(t *testing.T) { func TestDeleteAssets(t *testing.T) {
assert.True(t, UsingAssets(dir)) assert.True(t, UsingAssets(dir))
//assert.Nil(t, DeleteAllAssets(dir)) assert.Nil(t, DeleteAllAssets(dir))
assert.False(t, UsingAssets(dir))
}
func TestCopyToPluginFailed(t *testing.T) {
//assert.Nil(t, DeleteAllAssets(dir))
assert.False(t, UsingAssets(dir)) assert.False(t, UsingAssets(dir))
} }

View File

@ -48,7 +48,6 @@ type Core struct {
Started time.Time `gorm:"-" json:"started_on"` Started time.Time `gorm:"-" json:"started_on"`
Plugins []*Info `gorm:"-" json:"-"` Plugins []*Info `gorm:"-" json:"-"`
Notifications []AllNotifiers `gorm:"-" json:"-"` Notifications []AllNotifiers `gorm:"-" json:"-"`
Config *DbConfig `gorm:"-" json:"-"`
Integrations []Integrator `gorm:"-" json:"-"` Integrations []Integrator `gorm:"-" json:"-"`
} }