commit 6bc66975db172603a58bc42833d1fb2e23b5741b Author: NiseVoid Date: Mon May 14 14:19:29 2018 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a831424 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +api +log +**/model/tables.go diff --git a/app/db.go b/app/db.go new file mode 100644 index 0000000..14c313a --- /dev/null +++ b/app/db.go @@ -0,0 +1,21 @@ +package app + +import ( + "database/sql" + + "git.ultraware.nl/NiseVoid/qb/driver/autoqb" + "git.ultraware.nl/NiseVoid/qb/qbdb" + _ "github.com/lib/pq" // PostgreSQL driver +) + +var db *qbdb.DB + +// InitDB initializes the database connection +func InitDB() { + d, err := sql.Open(`postgres`, `host=/tmp dbname=power_quality`) + if err != nil { + panic(err) + } + + db = autoqb.New(d) +} diff --git a/app/internal/model/db.json b/app/internal/model/db.json new file mode 100644 index 0000000..ba3c843 --- /dev/null +++ b/app/internal/model/db.json @@ -0,0 +1,175 @@ +[ + { + "name": "public.measurement", + "fields": [ + { + "name": "c_gem_1", + "type": "double precision", + "null": false, + "size": 8 + }, + { + "name": "c_gem_2", + "type": "double precision", + "null": false, + "size": 8 + }, + { + "name": "c_gem_3", + "type": "double precision", + "null": false, + "size": 8 + }, + { + "name": "ep_1", + "type": "double precision", + "null": false, + "size": 8 + }, + { + "name": "ep_2", + "type": "double precision", + "null": false, + "size": 8 + }, + { + "name": "ep_3", + "type": "double precision", + "null": false, + "size": 8 + }, + { + "name": "i_gem_1", + "type": "double precision", + "null": false, + "size": 8 + }, + { + "name": "i_gem_2", + "type": "double precision", + "null": false, + "size": 8 + }, + { + "name": "i_gem_3", + "type": "double precision", + "null": false, + "size": 8 + }, + { + "name": "i_max_1", + "type": "double precision", + "null": false, + "size": 8 + }, + { + "name": "i_max_2", + "type": "double precision", + "null": false, + "size": 8 + }, + { + "name": "i_max_3", + "type": "double precision", + "null": false, + "size": 8 + }, + { + "name": "p_gem_1", + "type": "double precision", + "null": false, + "size": 8 + }, + { + "name": "p_gem_2", + "type": "double precision", + "null": false, + "size": 8 + }, + { + "name": "p_gem_3", + "type": "double precision", + "null": false, + "size": 8 + }, + { + "name": "p_max_1", + "type": "double precision", + "null": false, + "size": 8 + }, + { + "name": "p_max_2", + "type": "double precision", + "null": false, + "size": 8 + }, + { + "name": "p_max_3", + "type": "double precision", + "null": false, + "size": 8 + }, + { + "name": "s_gem_1", + "type": "double precision", + "null": false, + "size": 8 + }, + { + "name": "s_gem_2", + "type": "double precision", + "null": false, + "size": 8 + }, + { + "name": "s_gem_3", + "type": "double precision", + "null": false, + "size": 8 + }, + { + "name": "s_max_1", + "type": "double precision", + "null": false, + "size": 8 + }, + { + "name": "s_max_2", + "type": "double precision", + "null": false, + "size": 8 + }, + { + "name": "s_max_3", + "type": "double precision", + "null": false, + "size": 8 + }, + { + "name": "time", + "type": "timestamp with time zone", + "null": false, + "size": 8 + }, + { + "name": "u_gem_1", + "type": "double precision", + "null": false, + "size": 8 + }, + { + "name": "u_gem_2", + "type": "double precision", + "null": false, + "size": 8 + }, + { + "name": "u_gem_3", + "type": "double precision", + "null": false, + "size": 8 + } + ] + } +] diff --git a/app/internal/model/model.go b/app/internal/model/model.go new file mode 100644 index 0000000..80e7ea9 --- /dev/null +++ b/app/internal/model/model.go @@ -0,0 +1,3 @@ +package model + +//go:generate qb-generator db.json tables.go diff --git a/app/stats.go b/app/stats.go new file mode 100644 index 0000000..1ad47f7 --- /dev/null +++ b/app/stats.go @@ -0,0 +1,71 @@ +package app + +import ( + "reflect" + "time" + + "git.fuyu.moe/5GPowerQuality/api/app/internal/model" + "git.ultraware.nl/NiseVoid/qb" + "git.ultraware.nl/NiseVoid/qb/qc" + "git.ultraware.nl/NiseVoid/qb/qf" +) + +// Count returns the number of measurements +func Count() int { + m := model.Measurement() + + q := m.Select(qf.CountAll()) + + var count *int + err := db.QueryRow(q).Scan(&count) + if err != nil { + panic(err) + } + if count == nil { + return 0 + } + + return *count +} + +// GetAttribute returns the specified attribute for all phases since the specified date +func GetAttribute(attr string, date time.Time) (data []Phases) { + m := model.Measurement() + + f := reflect.ValueOf(m).Elem().FieldByName(attr + `1`) + if !f.IsValid() || f.Type().Name() != `Field` { + return nil + } + f1 := f.Interface().(*qb.TableField) + f2 := reflect.ValueOf(m).Elem().FieldByName(attr + `2`).Interface().(*qb.TableField) + f3 := reflect.ValueOf(m).Elem().FieldByName(attr + `3`).Interface().(*qb.TableField) + + q := m.Select(m.Time, f1, f2, f3). + OrderBy(qb.Desc(m.Time)). + Where(qc.Gte(m.Time, date)) + + rows, err := db.Query(q) + if err != nil { + panic(err) + } + + for rows.Next() { + var phases Phases + err := rows.Scan(&phases.Time, &phases.P1, &phases.P2, &phases.P3) + if err != nil { + panic(err) + } + + data = append(data, phases) + } + + return +} + +// Phases contains data for all phases +type Phases struct { + Time time.Time `json:"time"` + P1 float64 `json:"phase_1"` + P2 float64 `json:"phase_2"` + P3 float64 `json:"phase_3"` +} diff --git a/server/logger.go b/server/logger.go new file mode 100644 index 0000000..fc68109 --- /dev/null +++ b/server/logger.go @@ -0,0 +1,19 @@ +package main + +import ( + "os" + "os/signal" + "syscall" + + "git.fuyu.moe/Fuyu/flog" +) + +func catchSignals() { + sc := make(chan os.Signal, 1) + signal.Notify(sc, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) + go func() { + s := <-sc + flog.Info(`Stopping, signal: `, s) + os.Exit(0) + }() +} diff --git a/server/logger_debug.go b/server/logger_debug.go new file mode 100644 index 0000000..53dcb16 --- /dev/null +++ b/server/logger_debug.go @@ -0,0 +1,12 @@ +// +build cgo + +package main + +import ( + "git.fuyu.moe/Fuyu/flog" +) + +func setLogger() { + flog.MinLevel = flog.LevelDebug + flog.MinStackLevel = flog.LevelWarning +} diff --git a/server/logger_release.go b/server/logger_release.go new file mode 100644 index 0000000..73b3323 --- /dev/null +++ b/server/logger_release.go @@ -0,0 +1,20 @@ +// +build !cgo + +package main + +import ( + "os" + + "git.fuyu.moe/Fuyu/flog" +) + +func setLogger() { + f, err := os.OpenFile(`log`, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) + if err != nil { + panic(err) + } + + flog.Output = f + flog.MinLevel = flog.LevelInfo + flog.MinStackLevel = flog.LevelWarning +} diff --git a/server/main.go b/server/main.go new file mode 100644 index 0000000..d2291f7 --- /dev/null +++ b/server/main.go @@ -0,0 +1,63 @@ +package main + +import ( + "strings" + "time" + + "git.fuyu.moe/5GPowerQuality/api/app" + "git.fuyu.moe/Fuyu/flog" + "github.com/labstack/echo" +) + +func main() { + defer func() { + v := recover() + if v != nil { + flog.Critical(`panic: `, v) + } + }() + + setLogger() + + flog.Info(`Starting`) + catchSignals() + + app.InitDB() + + e := echo.New() + e.HideBanner = true + e.Use(recoverMiddleware) + + e.GET(`/count`, count) + e.GET(`/:attr/:date`, pmax) + + panic(e.Start(`localhost:33333`)) +} + +func count(c echo.Context) error { + return c.JSON(200, app.Count()) +} + +func pmax(c echo.Context) error { + attr := c.Param(`attr`) + if len(attr) < 2 { + return c.NoContent(400) + } + if len(attr) == 2 { + attr = strings.ToUpper(attr[:2]) + } else { + attr = strings.ToUpper(attr[:2]) + strings.ToLower(attr[2:]) + } + + date, err := time.Parse(time.RFC3339, c.Param(`date`)) + if err != nil { + return c.NoContent(400) + } + + data := app.GetAttribute(attr, date) + if data == nil { + return c.NoContent(404) + } + + return c.JSON(200, data) +} diff --git a/server/panic.go b/server/panic.go new file mode 100644 index 0000000..42716e3 --- /dev/null +++ b/server/panic.go @@ -0,0 +1,20 @@ +package main + +import ( + "git.fuyu.moe/Fuyu/flog" + "github.com/labstack/echo" +) + +func recoverMiddleware(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + defer func() { + v := recover() + if v != nil { + flog.Critical(`panic: `, v) + _ = c.JSON(500, `Fatal error occurred`) + } + }() + + return next(c) + } +}