2019-05-05 23:36:49 +03:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2019-05-22 00:09:47 +03:00
|
|
|
"crypto/md5"
|
|
|
|
"encoding/hex"
|
2019-05-16 23:51:11 +03:00
|
|
|
"fmt"
|
2019-05-06 01:58:01 +03:00
|
|
|
"log"
|
2019-05-05 23:36:49 +03:00
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
"strconv"
|
|
|
|
"time"
|
|
|
|
|
2019-05-06 00:23:54 +03:00
|
|
|
"github.com/codegangsta/negroni"
|
|
|
|
"github.com/gorilla/mux"
|
2019-05-11 18:49:56 +03:00
|
|
|
"github.com/jinzhu/gorm"
|
2019-05-06 01:58:01 +03:00
|
|
|
"github.com/joho/godotenv"
|
2019-05-20 21:44:53 +03:00
|
|
|
"github.com/rs/cors"
|
2019-05-22 00:09:47 +03:00
|
|
|
uuid "github.com/satori/go.uuid"
|
2019-05-16 23:51:11 +03:00
|
|
|
ini "gopkg.in/ini.v1"
|
2019-05-06 00:23:54 +03:00
|
|
|
|
2019-05-06 01:40:41 +03:00
|
|
|
"github.com/n1try/wakapi/middlewares"
|
2019-05-05 23:36:49 +03:00
|
|
|
"github.com/n1try/wakapi/models"
|
2019-05-06 01:40:41 +03:00
|
|
|
"github.com/n1try/wakapi/routes"
|
2019-05-05 23:36:49 +03:00
|
|
|
"github.com/n1try/wakapi/services"
|
2019-05-11 18:49:56 +03:00
|
|
|
"github.com/n1try/wakapi/utils"
|
|
|
|
|
|
|
|
_ "github.com/jinzhu/gorm/dialects/mysql"
|
2019-05-05 23:36:49 +03:00
|
|
|
)
|
|
|
|
|
2019-05-16 23:53:03 +03:00
|
|
|
func readConfig() *models.Config {
|
2019-05-11 18:57:58 +03:00
|
|
|
if err := godotenv.Load(); err != nil {
|
2019-05-06 01:58:01 +03:00
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
dbUser, valid := os.LookupEnv("WAKAPI_DB_USER")
|
|
|
|
dbPassword, valid := os.LookupEnv("WAKAPI_DB_PASSWORD")
|
|
|
|
dbHost, valid := os.LookupEnv("WAKAPI_DB_HOST")
|
|
|
|
dbName, valid := os.LookupEnv("WAKAPI_DB_NAME")
|
2019-05-06 01:47:38 +03:00
|
|
|
|
2019-05-06 01:58:01 +03:00
|
|
|
if !valid {
|
2019-05-16 23:51:11 +03:00
|
|
|
log.Fatal("Environment variables missing.")
|
2019-05-06 01:58:01 +03:00
|
|
|
}
|
2019-05-06 01:47:38 +03:00
|
|
|
|
2019-05-16 23:51:11 +03:00
|
|
|
cfg, err := ini.Load("config.ini")
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(fmt.Sprintf("Fail to read file: %v", err))
|
|
|
|
}
|
|
|
|
|
2019-05-21 15:02:04 +03:00
|
|
|
addr := cfg.Section("server").Key("listen").MustString("127.0.0.1")
|
2019-05-19 20:49:27 +03:00
|
|
|
port, err := strconv.Atoi(os.Getenv("PORT"))
|
|
|
|
if err != nil {
|
|
|
|
port = cfg.Section("server").Key("port").MustInt()
|
|
|
|
}
|
2019-05-16 23:51:11 +03:00
|
|
|
|
2019-05-21 18:16:46 +03:00
|
|
|
// Read custom languages
|
|
|
|
customLangs := make(map[string]string)
|
|
|
|
languageKeys := cfg.Section("languages").Keys()
|
|
|
|
for _, k := range languageKeys {
|
|
|
|
customLangs[k.Name()] = k.MustString("unknown")
|
|
|
|
}
|
|
|
|
|
2019-05-16 23:53:03 +03:00
|
|
|
return &models.Config{
|
2019-05-21 18:16:46 +03:00
|
|
|
Port: port,
|
|
|
|
Addr: addr,
|
|
|
|
DbHost: dbHost,
|
|
|
|
DbUser: dbUser,
|
|
|
|
DbPassword: dbPassword,
|
|
|
|
DbName: dbName,
|
|
|
|
DbDialect: "mysql",
|
|
|
|
CustomLanguages: customLangs,
|
2019-05-05 23:36:49 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
// Read Config
|
2019-05-06 01:47:38 +03:00
|
|
|
config := readConfig()
|
2019-05-05 23:36:49 +03:00
|
|
|
|
2019-05-11 18:49:56 +03:00
|
|
|
// Connect to database
|
2019-05-16 23:53:03 +03:00
|
|
|
db, err := gorm.Open(config.DbDialect, utils.MakeConnectionString(config))
|
2019-05-19 22:00:19 +03:00
|
|
|
db.LogMode(false)
|
2019-05-21 15:02:04 +03:00
|
|
|
db.DB().SetMaxIdleConns(1)
|
|
|
|
db.DB().SetMaxOpenConns(10)
|
2019-05-05 23:36:49 +03:00
|
|
|
if err != nil {
|
2019-05-11 18:49:56 +03:00
|
|
|
// log.Fatal("Could not connect to database.")
|
|
|
|
log.Fatal(err)
|
2019-05-05 23:36:49 +03:00
|
|
|
}
|
2019-05-11 18:49:56 +03:00
|
|
|
defer db.Close()
|
|
|
|
|
|
|
|
// Migrate database schema
|
|
|
|
db.AutoMigrate(&models.User{})
|
|
|
|
db.AutoMigrate(&models.Heartbeat{}).AddForeignKey("user_id", "users(id)", "RESTRICT", "RESTRICT")
|
2019-05-05 23:36:49 +03:00
|
|
|
|
2019-05-22 00:09:47 +03:00
|
|
|
// Custom migrations and initial data
|
|
|
|
addDefaultUser(db, config)
|
2019-05-21 23:01:14 +03:00
|
|
|
migrateLanguages(db, config)
|
|
|
|
|
2019-05-06 01:40:41 +03:00
|
|
|
// Services
|
2019-05-16 23:53:03 +03:00
|
|
|
heartbeatSrvc := &services.HeartbeatService{config, db}
|
|
|
|
userSrvc := &services.UserService{config, db}
|
2019-05-19 20:49:27 +03:00
|
|
|
summarySrvc := &services.SummaryService{config, db, heartbeatSrvc}
|
2019-05-06 01:40:41 +03:00
|
|
|
|
|
|
|
// Handlers
|
|
|
|
heartbeatHandler := &routes.HeartbeatHandler{HeartbeatSrvc: heartbeatSrvc}
|
2019-05-19 20:49:27 +03:00
|
|
|
summaryHandler := &routes.SummaryHandler{SummarySrvc: summarySrvc}
|
2019-05-06 01:40:41 +03:00
|
|
|
|
|
|
|
// Middlewares
|
|
|
|
authenticate := &middlewares.AuthenticateMiddleware{UserSrvc: userSrvc}
|
2019-05-20 21:44:53 +03:00
|
|
|
cors := cors.New(cors.Options{
|
|
|
|
AllowedOrigins: []string{"*"},
|
|
|
|
AllowedHeaders: []string{"*"},
|
|
|
|
Debug: false,
|
|
|
|
})
|
2019-05-05 23:36:49 +03:00
|
|
|
|
2019-05-06 00:23:54 +03:00
|
|
|
// Setup Routing
|
|
|
|
router := mux.NewRouter()
|
|
|
|
apiRouter := mux.NewRouter().PathPrefix("/api").Subrouter()
|
|
|
|
|
|
|
|
// API Routes
|
|
|
|
heartbeats := apiRouter.Path("/heartbeat").Subrouter()
|
2019-05-21 15:02:04 +03:00
|
|
|
heartbeats.Methods(http.MethodPost).HandlerFunc(heartbeatHandler.Post)
|
2019-05-06 00:23:54 +03:00
|
|
|
|
2019-05-19 20:49:27 +03:00
|
|
|
aggreagations := apiRouter.Path("/summary").Subrouter()
|
2019-05-21 15:02:04 +03:00
|
|
|
aggreagations.Methods(http.MethodGet).HandlerFunc(summaryHandler.Get)
|
2019-05-09 01:07:38 +03:00
|
|
|
|
2019-05-06 00:23:54 +03:00
|
|
|
// Sub-Routes Setup
|
2019-05-20 21:44:53 +03:00
|
|
|
router.PathPrefix("/api").Handler(negroni.Classic().
|
|
|
|
With(cors).
|
|
|
|
With(
|
|
|
|
negroni.HandlerFunc(authenticate.Handle),
|
|
|
|
negroni.Wrap(apiRouter),
|
|
|
|
))
|
|
|
|
router.PathPrefix("/").Handler(negroni.Classic().With(negroni.Wrap(http.FileServer(http.Dir("./static")))))
|
2019-05-05 23:36:49 +03:00
|
|
|
|
|
|
|
// Listen HTTP
|
2019-05-21 15:02:04 +03:00
|
|
|
portString := config.Addr + ":" + strconv.Itoa(config.Port)
|
2019-05-05 23:36:49 +03:00
|
|
|
s := &http.Server{
|
2019-05-06 00:23:54 +03:00
|
|
|
Handler: router,
|
2019-05-05 23:36:49 +03:00
|
|
|
Addr: portString,
|
|
|
|
ReadTimeout: 10 * time.Second,
|
|
|
|
WriteTimeout: 10 * time.Second,
|
|
|
|
}
|
2019-05-06 01:58:01 +03:00
|
|
|
log.Printf("Listening on %+s\n", portString)
|
2019-05-05 23:36:49 +03:00
|
|
|
s.ListenAndServe()
|
|
|
|
}
|
2019-05-21 23:01:14 +03:00
|
|
|
|
|
|
|
func migrateLanguages(db *gorm.DB, cfg *models.Config) {
|
|
|
|
for k, v := range cfg.CustomLanguages {
|
|
|
|
result := db.Model(models.Heartbeat{}).
|
|
|
|
Where("language = ?", "").
|
|
|
|
Where("entity LIKE ?", "%."+k).
|
|
|
|
Updates(models.Heartbeat{Language: v})
|
|
|
|
if result.Error != nil {
|
|
|
|
log.Fatal(result.Error)
|
|
|
|
}
|
2019-05-22 00:09:47 +03:00
|
|
|
if result.RowsAffected > 0 {
|
|
|
|
log.Printf("Migrated %+v rows for custom language %+s.\n", result.RowsAffected, k)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func addDefaultUser(db *gorm.DB, cfg *models.Config) {
|
|
|
|
pw := md5.Sum([]byte(models.DefaultPassword))
|
|
|
|
pwString := hex.EncodeToString(pw[:])
|
|
|
|
apiKey := uuid.Must(uuid.NewV4()).String()
|
|
|
|
u := &models.User{ID: models.DefaultUser, Password: pwString, ApiKey: apiKey}
|
|
|
|
result := db.FirstOrCreate(u, &models.User{ID: u.ID})
|
|
|
|
if result.Error != nil {
|
|
|
|
log.Println("Unable to create default user.")
|
|
|
|
log.Fatal(result.Error)
|
|
|
|
}
|
|
|
|
if result.RowsAffected > 0 {
|
|
|
|
log.Printf("Created default user '%s' with password '%s' and API key '%s'.\n", u.ID, models.DefaultPassword, u.ApiKey)
|
2019-05-21 23:01:14 +03:00
|
|
|
}
|
|
|
|
}
|