mirror of
https://github.com/muety/wakapi.git
synced 2023-08-10 21:12:56 +03:00
Introduce GORM.
This commit is contained in:
parent
7df01fe9c4
commit
b4c8e6ecb6
22
db.sql
22
db.sql
@ -1,22 +0,0 @@
|
|||||||
CREATE TABLE `heartbeat` (
|
|
||||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
|
||||||
`user` varchar(255) NOT NULL,
|
|
||||||
`time` datetime NOT NULL,
|
|
||||||
`entity` varchar(1024) DEFAULT NULL,
|
|
||||||
`type` varchar(255) NOT NULL,
|
|
||||||
`category` varchar(255) DEFAULT NULL,
|
|
||||||
`is_write` tinyint(4) NOT NULL,
|
|
||||||
`branch` varchar(255) DEFAULT NULL,
|
|
||||||
`language` varchar(255) DEFAULT NULL,
|
|
||||||
`project` varchar(255) DEFAULT NULL,
|
|
||||||
`operating_system` varchar(45) DEFAULT NULL,
|
|
||||||
`editor` varchar(45) DEFAULT NULL,
|
|
||||||
PRIMARY KEY (`id`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
|
||||||
|
|
||||||
CREATE TABLE `user` (
|
|
||||||
`user_id` varchar(255) NOT NULL,
|
|
||||||
`api_key` varchar(255) NOT NULL,
|
|
||||||
PRIMARY KEY (`user_id`),
|
|
||||||
KEY `IDX_API_KEY` (`api_key`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
|
30
main.go
30
main.go
@ -1,7 +1,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@ -10,13 +9,16 @@ import (
|
|||||||
|
|
||||||
"github.com/codegangsta/negroni"
|
"github.com/codegangsta/negroni"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/jinzhu/gorm"
|
||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
|
|
||||||
"github.com/go-sql-driver/mysql"
|
|
||||||
"github.com/n1try/wakapi/middlewares"
|
"github.com/n1try/wakapi/middlewares"
|
||||||
"github.com/n1try/wakapi/models"
|
"github.com/n1try/wakapi/models"
|
||||||
"github.com/n1try/wakapi/routes"
|
"github.com/n1try/wakapi/routes"
|
||||||
"github.com/n1try/wakapi/services"
|
"github.com/n1try/wakapi/services"
|
||||||
|
"github.com/n1try/wakapi/utils"
|
||||||
|
|
||||||
|
_ "github.com/jinzhu/gorm/dialects/mysql"
|
||||||
)
|
)
|
||||||
|
|
||||||
func readConfig() models.Config {
|
func readConfig() models.Config {
|
||||||
@ -44,6 +46,7 @@ func readConfig() models.Config {
|
|||||||
DbUser: dbUser,
|
DbUser: dbUser,
|
||||||
DbPassword: dbPassword,
|
DbPassword: dbPassword,
|
||||||
DbName: dbName,
|
DbName: dbName,
|
||||||
|
DbDialect: "mysql",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,22 +54,17 @@ func main() {
|
|||||||
// Read Config
|
// Read Config
|
||||||
config := readConfig()
|
config := readConfig()
|
||||||
|
|
||||||
// Connect Database
|
// Connect to database
|
||||||
dbConfig := mysql.Config{
|
db, err := gorm.Open(config.DbDialect, utils.MakeConnectionString(&config))
|
||||||
User: config.DbUser,
|
|
||||||
Passwd: config.DbPassword,
|
|
||||||
Net: "tcp",
|
|
||||||
Addr: config.DbHost,
|
|
||||||
DBName: config.DbName,
|
|
||||||
AllowNativePasswords: true,
|
|
||||||
ParseTime: true,
|
|
||||||
}
|
|
||||||
db, _ := sql.Open("mysql", dbConfig.FormatDSN())
|
|
||||||
defer db.Close()
|
|
||||||
err := db.Ping()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Could not connect to database.")
|
// log.Fatal("Could not connect to database.")
|
||||||
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
// Migrate database schema
|
||||||
|
db.AutoMigrate(&models.User{})
|
||||||
|
db.AutoMigrate(&models.Heartbeat{}).AddForeignKey("user_id", "users(id)", "RESTRICT", "RESTRICT")
|
||||||
|
|
||||||
// Services
|
// Services
|
||||||
heartbeatSrvc := &services.HeartbeatService{db}
|
heartbeatSrvc := &services.HeartbeatService{db}
|
||||||
|
@ -33,6 +33,6 @@ func (m *AuthenticateMiddleware) Handle(w http.ResponseWriter, r *http.Request,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := context.WithValue(r.Context(), models.UserKey, &user)
|
ctx := context.WithValue(r.Context(), models.UserKey, user)
|
||||||
next(w, r.WithContext(ctx))
|
next(w, r.WithContext(ctx))
|
||||||
}
|
}
|
||||||
|
@ -6,4 +6,5 @@ type Config struct {
|
|||||||
DbUser string
|
DbUser string
|
||||||
DbPassword string
|
DbPassword string
|
||||||
DbName string
|
DbName string
|
||||||
|
DbDialect string
|
||||||
}
|
}
|
||||||
|
@ -1,25 +1,36 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql/driver"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/jinzhu/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type HeartbeatReqTime time.Time
|
type HeartbeatReqTime time.Time
|
||||||
|
|
||||||
type Heartbeat struct {
|
type Heartbeat struct {
|
||||||
User string `json:"user"`
|
gorm.Model
|
||||||
Entity string `json:"entity"`
|
User *User `json:"user" gorm:"not_null; association_foreignkey:ID"`
|
||||||
Type string `json:"type"`
|
UserID string `json:"-" gorm:"not_null"`
|
||||||
Category string `json:"category"`
|
Entity string `json:"entity" gorm:"not_null"`
|
||||||
Project string `json:"project"`
|
Type string `json:"type"`
|
||||||
Branch string `json:"branch"`
|
Category string `json:"category"`
|
||||||
Language string `json:"language"`
|
Project string `json:"project; index:idx_project"`
|
||||||
IsWrite bool `json:"is_write"`
|
Branch string `json:"branch"`
|
||||||
Editor string `json:"editor"`
|
Language string `json:"language" gorm:"not_null; index:idx_language"`
|
||||||
OperatingSystem string `json:"operating_system"`
|
IsWrite bool `json:"is_write"`
|
||||||
Time HeartbeatReqTime `json:"time"`
|
Editor string `json:"editor" gorm:"not_null; index:idx_editor"`
|
||||||
|
OperatingSystem string `json:"operating_system" gorm:"not_null; index:idx_os"`
|
||||||
|
Time *HeartbeatReqTime `json:"time" gorm:"type:timestamp; default:now(); index:idx_time"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Heartbeat) Valid() bool {
|
||||||
|
return h.User != nil && h.UserID != "" && h.Entity != "" && h.Language != "" && h.Editor != "" && h.OperatingSystem != "" && h.Time != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *HeartbeatReqTime) UnmarshalJSON(b []byte) error {
|
func (j *HeartbeatReqTime) UnmarshalJSON(b []byte) error {
|
||||||
@ -33,6 +44,25 @@ func (j *HeartbeatReqTime) UnmarshalJSON(b []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (j *HeartbeatReqTime) Scan(value interface{}) error {
|
||||||
|
fmt.Printf("%T", value)
|
||||||
|
switch value.(type) {
|
||||||
|
case int64:
|
||||||
|
*j = HeartbeatReqTime(time.Unix(123456, 0))
|
||||||
|
break
|
||||||
|
case time.Time:
|
||||||
|
*j = HeartbeatReqTime(value.(time.Time))
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
return errors.New(fmt.Sprintf("Unsupported type"))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j HeartbeatReqTime) Value() (driver.Value, error) {
|
||||||
|
return time.Time(j), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (j HeartbeatReqTime) String() string {
|
func (j HeartbeatReqTime) String() string {
|
||||||
t := time.Time(j)
|
t := time.Time(j)
|
||||||
return t.Format("2006-01-02 15:04:05")
|
return t.Format("2006-01-02 15:04:05")
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
UserId string `json:"id"`
|
ID string `json:"id" gorm:"primary_key"`
|
||||||
ApiKey string `json:"api_key"`
|
ApiKey string `json:"api_key" gorm:"unique"`
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,6 @@ import (
|
|||||||
"github.com/n1try/wakapi/services"
|
"github.com/n1try/wakapi/services"
|
||||||
"github.com/n1try/wakapi/utils"
|
"github.com/n1try/wakapi/utils"
|
||||||
|
|
||||||
_ "github.com/go-sql-driver/mysql"
|
|
||||||
"github.com/n1try/wakapi/models"
|
"github.com/n1try/wakapi/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -22,24 +21,32 @@ func (h *HeartbeatHandler) Post(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var heartbeats []models.Heartbeat
|
||||||
|
user := r.Context().Value(models.UserKey).(*models.User)
|
||||||
opSys, editor, _ := utils.ParseUserAgent(r.Header.Get("User-Agent"))
|
opSys, editor, _ := utils.ParseUserAgent(r.Header.Get("User-Agent"))
|
||||||
|
|
||||||
dec := json.NewDecoder(r.Body)
|
dec := json.NewDecoder(r.Body)
|
||||||
var heartbeats []*models.Heartbeat
|
if err := dec.Decode(&heartbeats); err != nil {
|
||||||
err := dec.Decode(&heartbeats)
|
|
||||||
if err != nil {
|
|
||||||
w.WriteHeader(400)
|
w.WriteHeader(400)
|
||||||
w.Write([]byte(err.Error()))
|
w.Write([]byte(err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, h := range heartbeats {
|
|
||||||
|
for i := 0; i < len(heartbeats); i++ {
|
||||||
|
h := &heartbeats[i]
|
||||||
h.OperatingSystem = opSys
|
h.OperatingSystem = opSys
|
||||||
h.Editor = editor
|
h.Editor = editor
|
||||||
|
h.User = user
|
||||||
|
h.UserID = user.ID
|
||||||
|
|
||||||
|
if !h.Valid() {
|
||||||
|
w.WriteHeader(400)
|
||||||
|
w.Write([]byte("Invalid heartbeat object."))
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
user := r.Context().Value(models.UserKey).(*models.User)
|
if err := h.HeartbeatSrvc.InsertBatch(&heartbeats); err != nil {
|
||||||
err = h.HeartbeatSrvc.InsertBatch(heartbeats, user)
|
|
||||||
if err != nil {
|
|
||||||
w.WriteHeader(500)
|
w.WriteHeader(500)
|
||||||
os.Stderr.WriteString(err.Error())
|
os.Stderr.WriteString(err.Error())
|
||||||
return
|
return
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
package services
|
package services
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/jinzhu/gorm"
|
||||||
"github.com/n1try/wakapi/models"
|
"github.com/n1try/wakapi/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AggregationService struct {
|
type AggregationService struct {
|
||||||
Db *sql.DB
|
Db *gorm.DB
|
||||||
HeartbeatService *HeartbeatService
|
HeartbeatService *HeartbeatService
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -19,7 +19,7 @@ func (srv *AggregationService) Aggregate(from time.Time, to time.Time, user *mod
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
for _, h := range heartbeats {
|
for _, h := range *heartbeats {
|
||||||
fmt.Printf("%+v\n", h)
|
fmt.Printf("%+v\n", h)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,78 +1,38 @@
|
|||||||
package services
|
package services
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/jinzhu/gorm"
|
||||||
"github.com/n1try/wakapi/models"
|
"github.com/n1try/wakapi/models"
|
||||||
|
gormbulk "github.com/t-tiger/gorm-bulk-insert"
|
||||||
)
|
)
|
||||||
|
|
||||||
const TableHeartbeat = "heartbeat"
|
const TableHeartbeat = "heartbeat"
|
||||||
|
|
||||||
type HeartbeatService struct {
|
type HeartbeatService struct {
|
||||||
Db *sql.DB
|
Db *gorm.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
func (srv *HeartbeatService) InsertBatch(heartbeats []*models.Heartbeat, user *models.User) error {
|
func (srv *HeartbeatService) InsertBatch(heartbeats *[]models.Heartbeat) error {
|
||||||
qTpl := "INSERT INTO %+s (user, time, entity, type, category, is_write, project, branch, language, operating_system, editor) VALUES %+s;"
|
var batch []interface{}
|
||||||
qFill := ""
|
for _, h := range *heartbeats {
|
||||||
vals := []interface{}{}
|
batch = append(batch, h)
|
||||||
|
|
||||||
for _, h := range heartbeats {
|
|
||||||
qFill = qFill + "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?),"
|
|
||||||
vals = append(vals, user.UserId, h.Time.String(), h.Entity, h.Type, h.Category, h.IsWrite, h.Project, h.Branch, h.Language, h.OperatingSystem, h.Editor)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
q := fmt.Sprintf(qTpl, TableHeartbeat, qFill[0:len(qFill)-1])
|
if err := gormbulk.BulkInsert(srv.Db, batch, 3000); err != nil {
|
||||||
stmt, _ := srv.Db.Prepare(q)
|
|
||||||
result, err := stmt.Exec(vals...)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
n, err := result.RowsAffected()
|
|
||||||
if err != nil || n != int64(len(heartbeats)) {
|
|
||||||
return errors.New(fmt.Sprintf("Failed to insert %+v rows.", len(heartbeats)))
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (srv *HeartbeatService) GetAllFrom(date time.Time, user *models.User) ([]models.Heartbeat, error) {
|
func (srv *HeartbeatService) GetAllFrom(date time.Time, user *models.User) (*[]models.Heartbeat, error) {
|
||||||
q := fmt.Sprintf("SELECT user, time, language, project, operating_system, editor FROM %+s WHERE time >= ? AND user = ?", TableHeartbeat)
|
|
||||||
rows, err := srv.Db.Query(q, date.String(), user.UserId)
|
|
||||||
defer rows.Close()
|
|
||||||
if err != nil {
|
|
||||||
return make([]models.Heartbeat, 0), err
|
|
||||||
}
|
|
||||||
|
|
||||||
var heartbeats []models.Heartbeat
|
var heartbeats []models.Heartbeat
|
||||||
for rows.Next() {
|
if err := srv.Db.
|
||||||
var h models.Heartbeat
|
Where(&models.Heartbeat{UserID: user.ID}).
|
||||||
var language sql.NullString
|
Where("time > ?", date).
|
||||||
var project sql.NullString
|
Find(&heartbeats).Error; err != nil {
|
||||||
var operatingSystem sql.NullString
|
return nil, err
|
||||||
var editor sql.NullString
|
|
||||||
|
|
||||||
err := rows.Scan(&h.User, &h.Time, &language, &project, &operatingSystem, &editor)
|
|
||||||
|
|
||||||
if language.Valid {
|
|
||||||
h.Language = language.String
|
|
||||||
}
|
|
||||||
if project.Valid {
|
|
||||||
h.Project = project.String
|
|
||||||
}
|
|
||||||
if operatingSystem.Valid {
|
|
||||||
h.OperatingSystem = operatingSystem.String
|
|
||||||
}
|
|
||||||
if editor.Valid {
|
|
||||||
h.Editor = editor.String
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return make([]models.Heartbeat, 0), err
|
|
||||||
}
|
|
||||||
heartbeats = append(heartbeats, h)
|
|
||||||
}
|
}
|
||||||
return heartbeats, nil
|
return &heartbeats, nil
|
||||||
}
|
}
|
||||||
|
@ -1,33 +1,27 @@
|
|||||||
package services
|
package services
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"github.com/jinzhu/gorm"
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/n1try/wakapi/models"
|
"github.com/n1try/wakapi/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
const TableUser = "user"
|
const TableUser = "user"
|
||||||
|
|
||||||
type UserService struct {
|
type UserService struct {
|
||||||
Db *sql.DB
|
Db *gorm.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
func (srv *UserService) GetUserById(userId string) (models.User, error) {
|
func (srv *UserService) GetUserById(userId string) (*models.User, error) {
|
||||||
q := fmt.Sprintf("SELECT user_id, api_key FROM %+s WHERE user_id = ?;", TableUser)
|
u := &models.User{}
|
||||||
u := models.User{}
|
if err := srv.Db.Where(&models.User{ID: userId}).First(u).Error; err != nil {
|
||||||
err := srv.Db.QueryRow(q, userId).Scan(&u.UserId, &u.ApiKey)
|
|
||||||
if err != nil {
|
|
||||||
return u, err
|
return u, err
|
||||||
}
|
}
|
||||||
return u, nil
|
return u, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (srv *UserService) GetUserByKey(key string) (models.User, error) {
|
func (srv *UserService) GetUserByKey(key string) (*models.User, error) {
|
||||||
q := fmt.Sprintf("SELECT user_id, api_key FROM %+s WHERE api_key = ?;", TableUser)
|
u := &models.User{}
|
||||||
var u models.User
|
if err := srv.Db.Where(&models.User{ApiKey: key}).First(u).Error; err != nil {
|
||||||
err := srv.Db.QueryRow(q, key).Scan(&u.UserId, &u.ApiKey)
|
|
||||||
if err != nil {
|
|
||||||
return u, err
|
return u, err
|
||||||
}
|
}
|
||||||
return u, nil
|
return u, nil
|
||||||
|
@ -3,7 +3,10 @@ package utils
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/n1try/wakapi/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ParseDate(date string) (time.Time, error) {
|
func ParseDate(date string) (time.Time, error) {
|
||||||
@ -22,3 +25,16 @@ func ParseUserAgent(ua string) (string, string, error) {
|
|||||||
}
|
}
|
||||||
return groups[0][1], groups[0][2], nil
|
return groups[0][1], groups[0][2], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func MakeConnectionString(config *models.Config) string {
|
||||||
|
str := strings.Builder{}
|
||||||
|
str.WriteString(config.DbUser)
|
||||||
|
str.WriteString(":")
|
||||||
|
str.WriteString(config.DbPassword)
|
||||||
|
str.WriteString("@tcp(")
|
||||||
|
str.WriteString(config.DbHost)
|
||||||
|
str.WriteString(")/")
|
||||||
|
str.WriteString(config.DbName)
|
||||||
|
str.WriteString("?charset=utf8&parseTime=true")
|
||||||
|
return str.String()
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user