2018-08-16 06:22:20 +00:00
// Statup
// Copyright (C) 2018. Hunter Long and the project contributors
// Written by Hunter Long <info@socialeck.com> and the project contributors
//
// https://github.com/hunterlong/statup
//
// The licenses for most software and other practical works are designed
// to take away your freedom to share and change the works. By contrast,
// the GNU General Public License is intended to guarantee your freedom to
// share and change all versions of a program--to make sure it remains free
// software for all its users.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
2018-06-30 00:57:05 +00:00
package core
import (
"fmt"
"github.com/go-yaml/yaml"
2018-09-12 04:14:22 +00:00
"github.com/hunterlong/statup/core/notifier"
2018-06-30 00:57:05 +00:00
"github.com/hunterlong/statup/types"
"github.com/hunterlong/statup/utils"
2018-09-05 10:54:57 +00:00
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mssql"
_ "github.com/jinzhu/gorm/dialects/mysql"
_ "github.com/jinzhu/gorm/dialects/postgres"
_ "github.com/jinzhu/gorm/dialects/sqlite"
2018-06-30 00:57:05 +00:00
"os"
"time"
)
var (
2018-09-06 05:28:35 +00:00
DbSession * gorm . DB
2018-06-30 00:57:05 +00:00
)
2018-09-27 03:20:02 +00:00
type DbConfig types . DbConfig
2018-09-05 10:54:57 +00:00
2018-09-10 22:16:23 +00:00
// failuresDB returns the 'failures' database column
func failuresDB ( ) * gorm . DB {
return DbSession . Model ( & types . Failure { } )
}
// hitsDB returns the 'hits' database column
2018-09-05 10:54:57 +00:00
func hitsDB ( ) * gorm . DB {
2018-09-10 09:01:04 +00:00
return DbSession . Model ( & types . Hit { } )
2018-09-05 10:54:57 +00:00
}
2018-09-10 22:16:23 +00:00
// servicesDB returns the 'services' database column
2018-09-05 10:54:57 +00:00
func servicesDB ( ) * gorm . DB {
2018-09-10 09:01:04 +00:00
return DbSession . Model ( & types . Service { } )
2018-09-05 10:54:57 +00:00
}
2018-09-10 22:16:23 +00:00
// coreDB returns the single column 'core'
2018-09-05 10:54:57 +00:00
func coreDB ( ) * gorm . DB {
2018-09-10 09:01:04 +00:00
return DbSession . Table ( "core" ) . Model ( & CoreApp )
2018-09-05 10:54:57 +00:00
}
2018-09-10 22:16:23 +00:00
// usersDB returns the 'users' database column
2018-09-05 10:54:57 +00:00
func usersDB ( ) * gorm . DB {
2018-09-10 09:01:04 +00:00
return DbSession . Model ( & types . User { } )
2018-09-05 10:54:57 +00:00
}
2018-10-03 10:47:32 +00:00
// checkinDB returns the Checkin records for a service
2018-09-05 10:54:57 +00:00
func checkinDB ( ) * gorm . DB {
return DbSession . Model ( & types . Checkin { } )
}
2018-10-03 10:47:32 +00:00
// checkinHitsDB returns the 'hits' from the Checkin record
func checkinHitsDB ( ) * gorm . DB {
return DbSession . Model ( & types . CheckinHit { } )
}
2018-09-25 07:03:49 +00:00
// HitsBetween returns the gorm database query for a collection of service hits between a time range
2018-10-03 03:48:40 +00:00
func ( s * Service ) HitsBetween ( t1 , t2 time . Time , group string , column string ) * gorm . DB {
selector := Dbtimestamp ( group , column )
2018-10-02 08:39:47 +00:00
return DbSession . Model ( & types . Hit { } ) . Select ( selector ) . Where ( "service = ? AND created_at BETWEEN ? AND ?" , s . Id , t1 . Format ( types . TIME_DAY ) , t2 . Format ( types . TIME_DAY ) ) . Order ( "timeframe asc" , false ) . Group ( "timeframe" )
2018-09-25 04:19:59 +00:00
}
func CloseDB ( ) {
if DbSession != nil {
DbSession . DB ( ) . Close ( )
}
}
2018-09-06 05:28:35 +00:00
// Close shutsdown the database connection
2018-09-05 10:54:57 +00:00
func ( db * DbConfig ) Close ( ) error {
2018-09-06 05:28:35 +00:00
return DbSession . DB ( ) . Close ( )
2018-09-05 10:54:57 +00:00
}
2018-09-17 22:13:42 +00:00
func ( s * Service ) AfterFind ( ) ( err error ) {
s . CreatedAt = utils . Timezoner ( s . CreatedAt , CoreApp . Timezone )
return
}
func ( s * Hit ) AfterFind ( ) ( err error ) {
s . CreatedAt = utils . Timezoner ( s . CreatedAt , CoreApp . Timezone )
return
}
func ( f * Failure ) AfterFind ( ) ( err error ) {
f . CreatedAt = utils . Timezoner ( f . CreatedAt , CoreApp . Timezone )
return
}
func ( u * User ) AfterFind ( ) ( err error ) {
u . CreatedAt = utils . Timezoner ( u . CreatedAt , CoreApp . Timezone )
return
}
func ( u * Hit ) BeforeCreate ( ) ( err error ) {
2018-09-25 04:19:59 +00:00
if u . CreatedAt . IsZero ( ) {
u . CreatedAt = time . Now ( ) . UTC ( )
}
2018-09-17 22:13:42 +00:00
return
}
func ( u * Failure ) BeforeCreate ( ) ( err error ) {
u . CreatedAt = time . Now ( ) . UTC ( )
return
}
func ( u * User ) BeforeCreate ( ) ( err error ) {
u . CreatedAt = time . Now ( ) . UTC ( )
return
}
func ( u * Service ) BeforeCreate ( ) ( err error ) {
u . CreatedAt = time . Now ( ) . UTC ( )
return
}
2018-09-06 05:28:35 +00:00
// InsertCore create the single row for the Core settings in Statup
2018-09-05 10:54:57 +00:00
func ( db * DbConfig ) InsertCore ( ) ( * Core , error ) {
CoreApp = & Core { Core : & types . Core {
Name : db . Project ,
Description : db . Description ,
Config : "config.yml" ,
ApiKey : utils . NewSHA1Hash ( 9 ) ,
ApiSecret : utils . NewSHA1Hash ( 16 ) ,
Domain : db . Domain ,
MigrationId : time . Now ( ) . Unix ( ) ,
} }
CoreApp . DbConnection = db . DbConn
query := coreDB ( ) . Create ( & CoreApp )
return CoreApp , query . Error
}
2018-09-06 05:28:35 +00:00
// Connect will attempt to connect to the sqlite, postgres, or mysql database
2018-09-05 10:54:57 +00:00
func ( db * DbConfig ) Connect ( retry bool , location string ) error {
2018-06-30 00:57:05 +00:00
var err error
2018-09-05 10:54:57 +00:00
if DbSession != nil {
DbSession = nil
}
2018-09-06 05:28:35 +00:00
var conn , dbType string
dbType = Configs . DbConn
switch dbType {
2018-09-05 10:54:57 +00:00
case "sqlite" :
2018-09-06 05:28:35 +00:00
conn = utils . Directory + "/statup.db"
dbType = "sqlite3"
2018-09-05 10:54:57 +00:00
case "mysql" :
if Configs . DbPort == 0 {
Configs . DbPort = 3306
2018-06-30 00:57:05 +00:00
}
2018-09-05 10:54:57 +00:00
host := fmt . Sprintf ( "%v:%v" , Configs . DbHost , Configs . DbPort )
2018-09-16 07:48:34 +00:00
conn = fmt . Sprintf ( "%v:%v@tcp(%v)/%v?charset=utf8&parseTime=True&loc=UTC" , Configs . DbUser , Configs . DbPass , host , Configs . DbData )
2018-09-05 10:54:57 +00:00
case "postgres" :
if Configs . DbPort == 0 {
Configs . DbPort = 5432
}
2018-09-06 05:28:35 +00:00
conn = fmt . Sprintf ( "host=%v port=%v user=%v dbname=%v password=%v sslmode=disable" , Configs . DbHost , Configs . DbPort , Configs . DbUser , Configs . DbData , Configs . DbPass )
2018-09-05 10:54:57 +00:00
case "mssql" :
if Configs . DbPort == 0 {
Configs . DbPort = 1433
2018-06-30 00:57:05 +00:00
}
2018-09-05 10:54:57 +00:00
host := fmt . Sprintf ( "%v:%v" , Configs . DbHost , Configs . DbPort )
2018-09-06 05:28:35 +00:00
conn = fmt . Sprintf ( "sqlserver://%v:%v@%v?database=%v" , Configs . DbUser , Configs . DbPass , host , Configs . DbData )
}
DbSession , err = gorm . Open ( dbType , conn )
if err != nil {
if retry {
2018-09-28 03:36:23 +00:00
utils . Log ( 1 , fmt . Sprintf ( "Database connection to '%v' is not available, trying again in 5 seconds..." , conn ) )
2018-09-06 05:28:35 +00:00
return db . waitForDb ( )
} else {
fmt . Println ( "ERROR:" , err )
return err
2018-06-30 00:57:05 +00:00
}
}
2018-09-05 10:54:57 +00:00
err = DbSession . DB ( ) . Ping ( )
2018-07-06 03:01:03 +00:00
if err == nil {
2018-09-05 10:54:57 +00:00
utils . Log ( 1 , fmt . Sprintf ( "Database connection to '%v' was successful." , Configs . DbData ) )
2018-07-06 03:01:03 +00:00
}
2018-06-30 00:57:05 +00:00
return err
}
2018-09-05 10:54:57 +00:00
func ( db * DbConfig ) waitForDb ( ) error {
2018-07-20 05:26:41 +00:00
time . Sleep ( 5 * time . Second )
2018-09-05 10:54:57 +00:00
return db . Connect ( true , utils . Directory )
2018-07-20 05:26:41 +00:00
}
2018-09-06 05:28:35 +00:00
// DatabaseMaintence will automatically delete old records from 'failures' and 'hits'
// this function is currently set to delete records 7+ days old every 60 minutes
2018-06-30 00:57:05 +00:00
func DatabaseMaintence ( ) {
2018-07-28 16:44:52 +00:00
for range time . Tick ( 60 * time . Minute ) {
2018-09-27 03:20:02 +00:00
utils . Log ( 1 , "Checking for database records older than 3 months..." )
since := time . Now ( ) . AddDate ( 0 , - 3 , 0 ) . UTC ( )
2018-07-28 16:44:52 +00:00
DeleteAllSince ( "failures" , since )
DeleteAllSince ( "hits" , since )
}
2018-06-30 00:57:05 +00:00
}
2018-09-06 05:28:35 +00:00
// DeleteAllSince will delete a specific table's records based on a time.
2018-06-30 00:57:05 +00:00
func DeleteAllSince ( table string , date time . Time ) {
sql := fmt . Sprintf ( "DELETE FROM %v WHERE created_at < '%v';" , table , date . Format ( "2006-01-02" ) )
2018-09-05 10:54:57 +00:00
db := DbSession . Raw ( sql )
if db . Error != nil {
utils . Log ( 2 , db . Error )
2018-06-30 00:57:05 +00:00
}
}
2018-09-06 05:28:35 +00:00
// Update will save the config.yml file
2018-08-30 04:49:44 +00:00
func ( c * DbConfig ) Update ( ) error {
var err error
config , err := os . Create ( utils . Directory + "/config.yml" )
if err != nil {
utils . Log ( 4 , err )
return err
}
2018-09-27 03:20:02 +00:00
data , err := yaml . Marshal ( c )
2018-08-30 04:49:44 +00:00
if err != nil {
utils . Log ( 3 , err )
return err
}
config . WriteString ( string ( data ) )
config . Close ( )
return err
}
2018-09-06 05:28:35 +00:00
// Save will initially create the config.yml file
2018-09-05 10:54:57 +00:00
func ( c * DbConfig ) Save ( ) ( * DbConfig , error ) {
2018-06-30 00:57:05 +00:00
var err error
2018-08-20 03:48:28 +00:00
config , err := os . Create ( utils . Directory + "/config.yml" )
2018-06-30 00:57:05 +00:00
if err != nil {
2018-07-01 10:24:35 +00:00
utils . Log ( 4 , err )
2018-09-05 10:54:57 +00:00
return nil , err
2018-06-30 00:57:05 +00:00
}
2018-09-05 10:54:57 +00:00
c . ApiKey = utils . NewSHA1Hash ( 16 )
c . ApiSecret = utils . NewSHA1Hash ( 16 )
2018-09-27 03:20:02 +00:00
data , err := yaml . Marshal ( c )
2018-06-30 00:57:05 +00:00
if err != nil {
2018-07-01 10:24:35 +00:00
utils . Log ( 3 , err )
2018-09-05 10:54:57 +00:00
return nil , err
2018-06-30 00:57:05 +00:00
}
config . WriteString ( string ( data ) )
2018-09-05 10:54:57 +00:00
defer config . Close ( )
return c , err
}
2018-06-30 00:57:05 +00:00
2018-09-06 05:28:35 +00:00
// CreateCore will initialize the global variable 'CoreApp". This global variable contains most of Statup app.
2018-09-05 10:54:57 +00:00
func ( c * DbConfig ) CreateCore ( ) * Core {
2018-07-17 09:18:20 +00:00
newCore := & types . Core {
2018-06-30 00:57:05 +00:00
Name : c . Project ,
Description : c . Description ,
Config : "config.yml" ,
2018-09-05 10:54:57 +00:00
ApiKey : c . ApiKey ,
ApiSecret : c . ApiSecret ,
2018-06-30 00:57:05 +00:00
Domain : c . Domain ,
2018-07-06 03:01:03 +00:00
MigrationId : time . Now ( ) . Unix ( ) ,
2018-06-30 00:57:05 +00:00
}
2018-09-05 10:54:57 +00:00
db := coreDB ( ) . Create ( & newCore )
if db . Error == nil {
2018-07-17 09:18:20 +00:00
CoreApp = & Core { Core : newCore }
2018-07-01 10:24:35 +00:00
}
2018-09-05 10:54:57 +00:00
CoreApp , err := SelectCore ( )
2018-07-12 04:49:18 +00:00
if err != nil {
utils . Log ( 4 , err )
}
2018-09-05 10:54:57 +00:00
return CoreApp
2018-06-30 00:57:05 +00:00
}
2018-09-06 05:28:35 +00:00
// DropDatabase will DROP each table Statup created
2018-09-05 10:54:57 +00:00
func ( db * DbConfig ) DropDatabase ( ) error {
utils . Log ( 1 , "Dropping Database Tables..." )
err := DbSession . DropTableIfExists ( "checkins" )
2018-10-02 07:26:49 +00:00
err = DbSession . DropTableIfExists ( "checkins_hits" )
2018-09-12 04:14:22 +00:00
err = DbSession . DropTableIfExists ( "notifications" )
2018-09-05 10:54:57 +00:00
err = DbSession . DropTableIfExists ( "core" )
err = DbSession . DropTableIfExists ( "failures" )
err = DbSession . DropTableIfExists ( "hits" )
err = DbSession . DropTableIfExists ( "services" )
err = DbSession . DropTableIfExists ( "users" )
return err . Error
}
2018-09-06 05:28:35 +00:00
// CreateDatabase will CREATE TABLES for each of the Statup elements
2018-09-05 10:54:57 +00:00
func ( db * DbConfig ) CreateDatabase ( ) error {
2018-07-12 04:49:18 +00:00
utils . Log ( 1 , "Creating Database Tables..." )
2018-09-05 10:54:57 +00:00
err := DbSession . CreateTable ( & types . Checkin { } )
2018-10-03 08:17:25 +00:00
err = DbSession . CreateTable ( & types . CheckinHit { } )
2018-09-12 04:14:22 +00:00
err = DbSession . CreateTable ( & notifier . Notification { } )
2018-09-05 10:54:57 +00:00
err = DbSession . Table ( "core" ) . CreateTable ( & types . Core { } )
err = DbSession . CreateTable ( & types . Failure { } )
err = DbSession . CreateTable ( & types . Hit { } )
err = DbSession . CreateTable ( & types . Service { } )
err = DbSession . CreateTable ( & types . User { } )
utils . Log ( 1 , "Statup Database Created" )
return err . Error
}
2018-09-06 05:28:35 +00:00
// MigrateDatabase will migrate the database structure to current version.
// This function will NOT remove previous records, tables or columns from the database.
// If this function has an issue, it will ROLLBACK to the previous state.
2018-09-05 10:54:57 +00:00
func ( db * DbConfig ) MigrateDatabase ( ) error {
utils . Log ( 1 , "Migrating Database Tables..." )
2018-09-06 05:28:35 +00:00
tx := DbSession . Begin ( )
defer func ( ) {
if r := recover ( ) ; r != nil {
tx . Rollback ( )
}
} ( )
if tx . Error != nil {
return tx . Error
}
2018-10-03 08:17:25 +00:00
tx = tx . AutoMigrate ( & types . Service { } , & types . User { } , & types . Hit { } , & types . Failure { } , & types . Checkin { } , & types . CheckinHit { } , & notifier . Notification { } ) . Table ( "core" ) . AutoMigrate ( & types . Core { } )
2018-09-06 05:28:35 +00:00
if tx . Error != nil {
tx . Rollback ( )
utils . Log ( 3 , fmt . Sprintf ( "Statup Database could not be migrated: %v" , tx . Error ) )
return tx . Error
}
2018-09-05 10:54:57 +00:00
utils . Log ( 1 , "Statup Database Migrated" )
2018-09-06 05:28:35 +00:00
return tx . Commit ( ) . Error
2018-06-30 00:57:05 +00:00
}
func ( c * DbConfig ) Clean ( ) * DbConfig {
if os . Getenv ( "DB_PORT" ) != "" {
if c . DbConn == "postgres" {
c . DbHost = c . DbHost + ":" + os . Getenv ( "DB_PORT" )
}
}
return c
}