mirror of
https://github.com/muety/wakapi.git
synced 2023-08-10 21:12:56 +03:00
refactor: introduce repositories as an additional layer of abstraction to allow for better testability
This commit is contained in:
parent
96ff490d8d
commit
755cabb5f4
32
main.go
32
main.go
@ -4,6 +4,7 @@ import (
|
||||
"github.com/gorilla/handlers"
|
||||
conf "github.com/muety/wakapi/config"
|
||||
"github.com/muety/wakapi/migrations/common"
|
||||
"github.com/muety/wakapi/repositories"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
@ -27,6 +28,15 @@ var (
|
||||
config *conf.Config
|
||||
)
|
||||
|
||||
var (
|
||||
aliasRepository *repositories.AliasRepository
|
||||
heartbeatRepository *repositories.HeartbeatRepository
|
||||
userRepository *repositories.UserRepository
|
||||
customRuleRepository *repositories.CustomRuleRepository
|
||||
summaryRepository *repositories.SummaryRepository
|
||||
keyValueRepository *repositories.KeyValueRepository
|
||||
)
|
||||
|
||||
var (
|
||||
aliasService *services.AliasService
|
||||
heartbeatService *services.HeartbeatService
|
||||
@ -71,14 +81,22 @@ func main() {
|
||||
runDatabaseMigrations()
|
||||
runCustomMigrations()
|
||||
|
||||
// Repositories
|
||||
aliasRepository = repositories.NewAliasRepository(db)
|
||||
heartbeatRepository = repositories.NewHeartbeatRepository(db)
|
||||
userRepository = repositories.NewUserRepository(db)
|
||||
customRuleRepository = repositories.NewCustomRuleRepository(db)
|
||||
summaryRepository = repositories.NewSummaryRepository(db)
|
||||
keyValueRepository = repositories.NewKeyValueRepository(db)
|
||||
|
||||
// Services
|
||||
aliasService = services.NewAliasService(db)
|
||||
heartbeatService = services.NewHeartbeatService(db)
|
||||
userService = services.NewUserService(db)
|
||||
customRuleService = services.NewCustomRuleService(db)
|
||||
summaryService = services.NewSummaryService(db, heartbeatService, aliasService, customRuleService)
|
||||
aggregationService = services.NewAggregationService(db, userService, summaryService, heartbeatService)
|
||||
keyValueService = services.NewKeyValueService(db)
|
||||
aliasService = services.NewAliasService(aliasRepository)
|
||||
heartbeatService = services.NewHeartbeatService(heartbeatRepository)
|
||||
userService = services.NewUserService(userRepository)
|
||||
customRuleService = services.NewCustomRuleService(customRuleRepository)
|
||||
summaryService = services.NewSummaryService(summaryRepository, heartbeatService, aliasService, customRuleService)
|
||||
aggregationService = services.NewAggregationService(userService, summaryService, heartbeatService)
|
||||
keyValueService = services.NewKeyValueService(keyValueRepository)
|
||||
|
||||
// Aggregate heartbeats to summaries and persist them
|
||||
go aggregationService.Schedule()
|
||||
|
24
repositories/alias.go
Normal file
24
repositories/alias.go
Normal file
@ -0,0 +1,24 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/muety/wakapi/models"
|
||||
)
|
||||
|
||||
type AliasRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewAliasRepository(db *gorm.DB) *AliasRepository {
|
||||
return &AliasRepository{db: db}
|
||||
}
|
||||
|
||||
func (r *AliasRepository) GetByUser(userId string) ([]*models.Alias, error) {
|
||||
var aliases []*models.Alias
|
||||
if err := r.db.
|
||||
Where(&models.Alias{UserID: userId}).
|
||||
Find(&aliases).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return aliases, nil
|
||||
}
|
46
repositories/custom_rule.go
Normal file
46
repositories/custom_rule.go
Normal file
@ -0,0 +1,46 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/muety/wakapi/models"
|
||||
)
|
||||
|
||||
type CustomRuleRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewCustomRuleRepository(db *gorm.DB) *CustomRuleRepository {
|
||||
return &CustomRuleRepository{db: db}
|
||||
}
|
||||
|
||||
func (r *CustomRuleRepository) GetById(id uint) (*models.CustomRule, error) {
|
||||
rule := &models.CustomRule{}
|
||||
if err := r.db.Where(&models.CustomRule{ID: id}).First(rule).Error; err != nil {
|
||||
return rule, err
|
||||
}
|
||||
return rule, nil
|
||||
}
|
||||
|
||||
func (r *CustomRuleRepository) GetByUser(userId string) ([]*models.CustomRule, error) {
|
||||
var rules []*models.CustomRule
|
||||
if err := r.db.
|
||||
Where(&models.CustomRule{UserID: userId}).
|
||||
Find(&rules).Error; err != nil {
|
||||
return rules, err
|
||||
}
|
||||
return rules, nil
|
||||
}
|
||||
|
||||
func (r *CustomRuleRepository) Insert(rule *models.CustomRule) (*models.CustomRule, error) {
|
||||
result := r.db.Create(rule)
|
||||
if err := result.Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rule, nil
|
||||
}
|
||||
|
||||
func (r *CustomRuleRepository) Delete(id uint) error {
|
||||
return r.db.
|
||||
Where("id = ?", id).
|
||||
Delete(models.CustomRule{}).Error
|
||||
}
|
64
repositories/heartbeart.go
Normal file
64
repositories/heartbeart.go
Normal file
@ -0,0 +1,64 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/muety/wakapi/models"
|
||||
gormbulk "github.com/t-tiger/gorm-bulk-insert"
|
||||
"time"
|
||||
)
|
||||
|
||||
type HeartbeatRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewHeartbeatRepository(db *gorm.DB) *HeartbeatRepository {
|
||||
return &HeartbeatRepository{db: db}
|
||||
}
|
||||
|
||||
func (r *HeartbeatRepository) InsertBatch(heartbeats []*models.Heartbeat) error {
|
||||
var batch []interface{}
|
||||
for _, h := range heartbeats {
|
||||
batch = append(batch, *h)
|
||||
}
|
||||
|
||||
if err := gormbulk.BulkInsert(r.db, batch, 3000); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *HeartbeatRepository) GetAllWithin(from, to time.Time, user *models.User) ([]*models.Heartbeat, error) {
|
||||
var heartbeats []*models.Heartbeat
|
||||
if err := r.db.
|
||||
Where(&models.Heartbeat{UserID: user.ID}).
|
||||
Where("time >= ?", from).
|
||||
Where("time < ?", to).
|
||||
Order("time asc").
|
||||
Find(&heartbeats).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return heartbeats, nil
|
||||
}
|
||||
|
||||
// Will return *models.Heartbeat object with only user_id and time fields filled
|
||||
func (r *HeartbeatRepository) GetFirstByUsers(userIds []string) ([]*models.Heartbeat, error) {
|
||||
var heartbeats []*models.Heartbeat
|
||||
if err := r.db.
|
||||
Table("heartbeats").
|
||||
Select("user_id, min(time) as time").
|
||||
Where("user_id IN (?)", userIds).
|
||||
Group("user_id").
|
||||
Scan(&heartbeats).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return heartbeats, nil
|
||||
}
|
||||
|
||||
func (r *HeartbeatRepository) DeleteBefore(t time.Time) error {
|
||||
if err := r.db.
|
||||
Where("time <= ?", t).
|
||||
Delete(models.Heartbeat{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
58
repositories/key_value.go
Normal file
58
repositories/key_value.go
Normal file
@ -0,0 +1,58 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/muety/wakapi/models"
|
||||
)
|
||||
|
||||
type KeyValueRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewKeyValueRepository(db *gorm.DB) *KeyValueRepository {
|
||||
return &KeyValueRepository{db: db}
|
||||
}
|
||||
|
||||
func (r *KeyValueRepository) GetString(key string) (*models.KeyStringValue, error) {
|
||||
kv := &models.KeyStringValue{}
|
||||
if err := r.db.
|
||||
Where(&models.KeyStringValue{Key: key}).
|
||||
First(&kv).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return kv, nil
|
||||
}
|
||||
|
||||
func (r *KeyValueRepository) PutString(kv *models.KeyStringValue) error {
|
||||
result := r.db.
|
||||
Where(&models.KeyStringValue{Key: kv.Key}).
|
||||
Assign(kv).
|
||||
FirstOrCreate(kv)
|
||||
|
||||
if err := result.Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if result.RowsAffected != 1 {
|
||||
return errors.New("nothing updated")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *KeyValueRepository) DeleteString(key string) error {
|
||||
result := r.db.
|
||||
Delete(&models.KeyStringValue{}, &models.KeyStringValue{Key: key})
|
||||
|
||||
if err := result.Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if result.RowsAffected != 1 {
|
||||
return errors.New("nothing deleted")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
53
repositories/summary.go
Normal file
53
repositories/summary.go
Normal file
@ -0,0 +1,53 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/muety/wakapi/models"
|
||||
"time"
|
||||
)
|
||||
|
||||
type SummaryRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewSummaryRepository(db *gorm.DB) *SummaryRepository {
|
||||
return &SummaryRepository{db: db}
|
||||
}
|
||||
|
||||
func (r *SummaryRepository) Insert(summary *models.Summary) error {
|
||||
if err := r.db.Create(summary).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *SummaryRepository) GetByUserWithin(user *models.User, from, to time.Time) ([]*models.Summary, error) {
|
||||
var summaries []*models.Summary
|
||||
if err := r.db.
|
||||
Where(&models.Summary{UserID: user.ID}).
|
||||
Where("from_time >= ?", from).
|
||||
Where("to_time <= ?", to).
|
||||
Order("from_time asc").
|
||||
Preload("Projects", "type = ?", models.SummaryProject).
|
||||
Preload("Languages", "type = ?", models.SummaryLanguage).
|
||||
Preload("Editors", "type = ?", models.SummaryEditor).
|
||||
Preload("OperatingSystems", "type = ?", models.SummaryOS).
|
||||
Preload("Machines", "type = ?", models.SummaryMachine).
|
||||
Find(&summaries).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return summaries, nil
|
||||
}
|
||||
|
||||
// Will return *models.Index objects with only user_id and to_time filled
|
||||
func (r *SummaryRepository) GetLatestByUser() ([]*models.Summary, error) {
|
||||
var summaries []*models.Summary
|
||||
if err := r.db.
|
||||
Table("summaries").
|
||||
Select("user_id, max(to_time) as to_time").
|
||||
Group("user_id").
|
||||
Scan(&summaries).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return summaries, nil
|
||||
}
|
80
repositories/user.go
Normal file
80
repositories/user.go
Normal file
@ -0,0 +1,80 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/muety/wakapi/models"
|
||||
)
|
||||
|
||||
type UserRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewUserRepository(db *gorm.DB) *UserRepository {
|
||||
return &UserRepository{db: db}
|
||||
}
|
||||
|
||||
func (r *UserRepository) GetById(userId string) (*models.User, error) {
|
||||
u := &models.User{}
|
||||
if err := r.db.Where(&models.User{ID: userId}).First(u).Error; err != nil {
|
||||
return u, err
|
||||
}
|
||||
return u, nil
|
||||
}
|
||||
|
||||
func (r *UserRepository) GetByApiKey(key string) (*models.User, error) {
|
||||
u := &models.User{}
|
||||
if err := r.db.Where(&models.User{ApiKey: key}).First(u).Error; err != nil {
|
||||
return u, err
|
||||
}
|
||||
return u, nil
|
||||
}
|
||||
|
||||
func (r *UserRepository) GetAll() ([]*models.User, error) {
|
||||
var users []*models.User
|
||||
if err := r.db.
|
||||
Table("users").
|
||||
Find(&users).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (r *UserRepository) InsertOrGet(user *models.User) (*models.User, bool, error) {
|
||||
result := r.db.FirstOrCreate(user, &models.User{ID: user.ID})
|
||||
if err := result.Error; err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
if result.RowsAffected == 1 {
|
||||
return user, true, nil
|
||||
}
|
||||
|
||||
return user, false, nil
|
||||
}
|
||||
|
||||
func (r *UserRepository) Update(user *models.User) (*models.User, error) {
|
||||
result := r.db.Model(&models.User{}).Updates(user)
|
||||
if err := result.Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if result.RowsAffected != 1 {
|
||||
return nil, errors.New("nothing updated")
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (r *UserRepository) UpdateField(user *models.User, key string, value interface{}) (*models.User, error) {
|
||||
result := r.db.Model(user).Update(key, value)
|
||||
if err := result.Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if result.RowsAffected != 1 {
|
||||
return nil, errors.New("nothing updated")
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
@ -124,7 +124,13 @@ func (h *SettingsHandler) DeleteCustomRule(w http.ResponseWriter, r *http.Reques
|
||||
ID: uint(ruleId),
|
||||
UserID: user.ID,
|
||||
}
|
||||
h.customRuleSrvc.Delete(rule)
|
||||
|
||||
err = h.customRuleSrvc.Delete(rule)
|
||||
if err != nil {
|
||||
respondAlert(w, "internal server error", "", conf.SettingsTemplate, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
msg := url.QueryEscape("Custom rule deleted successfully.")
|
||||
|
||||
http.Redirect(w, r, fmt.Sprintf("%s/settings?success=%s", h.config.Server.BasePath, msg), http.StatusFound)
|
||||
|
@ -7,7 +7,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/jasonlvhit/gocron"
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/muety/wakapi/models"
|
||||
)
|
||||
|
||||
@ -16,20 +15,18 @@ const (
|
||||
)
|
||||
|
||||
type AggregationService struct {
|
||||
Config *config.Config
|
||||
Db *gorm.DB
|
||||
UserService *UserService
|
||||
SummaryService *SummaryService
|
||||
HeartbeatService *HeartbeatService
|
||||
config *config.Config
|
||||
userService *UserService
|
||||
summaryService *SummaryService
|
||||
heartbeatService *HeartbeatService
|
||||
}
|
||||
|
||||
func NewAggregationService(db *gorm.DB, userService *UserService, summaryService *SummaryService, heartbeatService *HeartbeatService) *AggregationService {
|
||||
func NewAggregationService(userService *UserService, summaryService *SummaryService, heartbeatService *HeartbeatService) *AggregationService {
|
||||
return &AggregationService{
|
||||
Config: config.Get(),
|
||||
Db: db,
|
||||
UserService: userService,
|
||||
SummaryService: summaryService,
|
||||
HeartbeatService: heartbeatService,
|
||||
config: config.Get(),
|
||||
userService: userService,
|
||||
summaryService: summaryService,
|
||||
heartbeatService: heartbeatService,
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,20 +47,20 @@ func (srv *AggregationService) Schedule() {
|
||||
go srv.summaryWorker(jobs, summaries)
|
||||
}
|
||||
|
||||
for i := 0; i < int(srv.Config.Db.MaxConn); i++ {
|
||||
for i := 0; i < int(srv.config.Db.MaxConn); i++ {
|
||||
go srv.persistWorker(summaries)
|
||||
}
|
||||
|
||||
// Run once initially
|
||||
srv.trigger(jobs)
|
||||
|
||||
gocron.Every(1).Day().At(srv.Config.App.AggregationTime).Do(srv.trigger, jobs)
|
||||
gocron.Every(1).Day().At(srv.config.App.AggregationTime).Do(srv.trigger, jobs)
|
||||
<-gocron.Start()
|
||||
}
|
||||
|
||||
func (srv *AggregationService) summaryWorker(jobs <-chan *AggregationJob, summaries chan<- *models.Summary) {
|
||||
for job := range jobs {
|
||||
if summary, err := srv.SummaryService.Construct(job.From, job.To, &models.User{ID: job.UserID}, true); err != nil {
|
||||
if summary, err := srv.summaryService.Construct(job.From, job.To, &models.User{ID: job.UserID}, true); err != nil {
|
||||
log.Printf("Failed to generate summary (%v, %v, %s) – %v.\n", job.From, job.To, job.UserID, err)
|
||||
} else {
|
||||
log.Printf("Successfully generated summary (%v, %v, %s).\n", job.From, job.To, job.UserID)
|
||||
@ -74,7 +71,7 @@ func (srv *AggregationService) summaryWorker(jobs <-chan *AggregationJob, summar
|
||||
|
||||
func (srv *AggregationService) persistWorker(summaries <-chan *models.Summary) {
|
||||
for summary := range summaries {
|
||||
if err := srv.SummaryService.Insert(summary); err != nil {
|
||||
if err := srv.summaryService.Insert(summary); err != nil {
|
||||
log.Printf("Failed to save summary (%v, %v, %s) – %v.\n", summary.UserID, summary.FromTime, summary.ToTime, err)
|
||||
}
|
||||
}
|
||||
@ -83,13 +80,13 @@ func (srv *AggregationService) persistWorker(summaries <-chan *models.Summary) {
|
||||
func (srv *AggregationService) trigger(jobs chan<- *AggregationJob) error {
|
||||
log.Println("Generating summaries.")
|
||||
|
||||
users, err := srv.UserService.GetAll()
|
||||
users, err := srv.userService.GetAll()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return err
|
||||
}
|
||||
|
||||
latestSummaries, err := srv.SummaryService.GetLatestByUser()
|
||||
latestSummaries, err := srv.summaryService.GetLatestByUser()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return err
|
||||
@ -107,7 +104,7 @@ func (srv *AggregationService) trigger(jobs chan<- *AggregationJob) error {
|
||||
}
|
||||
}
|
||||
|
||||
firstHeartbeats, err := srv.HeartbeatService.GetFirstUserHeartbeats(missingUserIDs)
|
||||
firstHeartbeats, err := srv.heartbeatService.GetFirstUserHeartbeats(missingUserIDs)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return err
|
||||
|
@ -2,36 +2,32 @@ package services
|
||||
|
||||
import (
|
||||
"github.com/muety/wakapi/config"
|
||||
"github.com/muety/wakapi/repositories"
|
||||
"sync"
|
||||
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/muety/wakapi/models"
|
||||
)
|
||||
|
||||
type AliasService struct {
|
||||
Config *config.Config
|
||||
Db *gorm.DB
|
||||
config *config.Config
|
||||
repository *repositories.AliasRepository
|
||||
}
|
||||
|
||||
func NewAliasService(db *gorm.DB) *AliasService {
|
||||
func NewAliasService(aliasRepo *repositories.AliasRepository) *AliasService {
|
||||
return &AliasService{
|
||||
Config: config.Get(),
|
||||
Db: db,
|
||||
config: config.Get(),
|
||||
repository: aliasRepo,
|
||||
}
|
||||
}
|
||||
|
||||
var userAliases sync.Map
|
||||
|
||||
func (srv *AliasService) LoadUserAliases(userId string) error {
|
||||
var aliases []*models.Alias
|
||||
if err := srv.Db.
|
||||
Where(&models.Alias{UserID: userId}).
|
||||
Find(&aliases).Error; err != nil {
|
||||
return err
|
||||
aliases, err := srv.repository.GetByUser(userId)
|
||||
if err == nil {
|
||||
userAliases.Store(userId, aliases)
|
||||
}
|
||||
|
||||
userAliases.Store(userId, aliases)
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
func (srv *AliasService) GetAliasOrDefault(userId string, summaryType uint8, value string) (string, error) {
|
||||
|
@ -1,64 +1,56 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/muety/wakapi/config"
|
||||
"github.com/muety/wakapi/models"
|
||||
"github.com/muety/wakapi/repositories"
|
||||
"github.com/patrickmn/go-cache"
|
||||
"time"
|
||||
)
|
||||
|
||||
type CustomRuleService struct {
|
||||
Config *config.Config
|
||||
Db *gorm.DB
|
||||
cache *cache.Cache
|
||||
config *config.Config
|
||||
repository *repositories.CustomRuleRepository
|
||||
cache *cache.Cache
|
||||
}
|
||||
|
||||
func NewCustomRuleService(db *gorm.DB) *CustomRuleService {
|
||||
func NewCustomRuleService(customRuleRepo *repositories.CustomRuleRepository) *CustomRuleService {
|
||||
return &CustomRuleService{
|
||||
Config: config.Get(),
|
||||
Db: db,
|
||||
cache: cache.New(1*time.Hour, 2*time.Hour),
|
||||
config: config.Get(),
|
||||
repository: customRuleRepo,
|
||||
cache: cache.New(1*time.Hour, 2*time.Hour),
|
||||
}
|
||||
}
|
||||
|
||||
func (srv *CustomRuleService) GetCustomRuleById(customRuleId uint) (*models.CustomRule, error) {
|
||||
r := &models.CustomRule{}
|
||||
if err := srv.Db.Where(&models.CustomRule{ID: customRuleId}).First(r).Error; err != nil {
|
||||
return r, err
|
||||
}
|
||||
return r, nil
|
||||
func (srv *CustomRuleService) GetCustomRuleById(id uint) (*models.CustomRule, error) {
|
||||
return srv.repository.GetById(id)
|
||||
}
|
||||
|
||||
func (srv *CustomRuleService) GetCustomRuleForUser(userId string) ([]*models.CustomRule, error) {
|
||||
var rules []*models.CustomRule
|
||||
if rules, found := srv.cache.Get(userId); found {
|
||||
return rules.([]*models.CustomRule), nil
|
||||
}
|
||||
|
||||
if err := srv.Db.
|
||||
Where(&models.CustomRule{UserID: userId}).
|
||||
Find(&rules).Error; err != nil {
|
||||
return rules, err
|
||||
rules, err := srv.repository.GetByUser(userId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
srv.cache.Set(userId, rules, cache.DefaultExpiration)
|
||||
return rules, nil
|
||||
}
|
||||
|
||||
func (srv *CustomRuleService) Create(rule *models.CustomRule) (*models.CustomRule, error) {
|
||||
result := srv.Db.Create(rule)
|
||||
if err := result.Error; err != nil {
|
||||
result, err := srv.repository.Insert(rule)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
srv.cache.Delete(rule.UserID)
|
||||
|
||||
return rule, nil
|
||||
srv.cache.Delete(result.UserID)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (srv *CustomRuleService) Delete(rule *models.CustomRule) {
|
||||
srv.Db.
|
||||
Where("id = ?", rule.ID).
|
||||
Where("user_id = ?", rule.UserID).
|
||||
Delete(models.CustomRule{})
|
||||
func (srv *CustomRuleService) Delete(rule *models.CustomRule) error {
|
||||
err := srv.repository.Delete(rule.ID)
|
||||
srv.cache.Delete(rule.UserID)
|
||||
return err
|
||||
}
|
||||
|
@ -3,78 +3,44 @@ package services
|
||||
import (
|
||||
"github.com/jasonlvhit/gocron"
|
||||
"github.com/muety/wakapi/config"
|
||||
"github.com/muety/wakapi/repositories"
|
||||
"github.com/muety/wakapi/utils"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/muety/wakapi/models"
|
||||
gormbulk "github.com/t-tiger/gorm-bulk-insert"
|
||||
)
|
||||
|
||||
const (
|
||||
TableHeartbeat = "heartbeat"
|
||||
cleanUpInterval = time.Duration(aggregateIntervalDays) * 2 * 24 * time.Hour
|
||||
)
|
||||
|
||||
type HeartbeatService struct {
|
||||
Config *config.Config
|
||||
Db *gorm.DB
|
||||
config *config.Config
|
||||
repository *repositories.HeartbeatRepository
|
||||
}
|
||||
|
||||
func NewHeartbeatService(db *gorm.DB) *HeartbeatService {
|
||||
func NewHeartbeatService(heartbeatRepo *repositories.HeartbeatRepository) *HeartbeatService {
|
||||
return &HeartbeatService{
|
||||
Config: config.Get(),
|
||||
Db: db,
|
||||
config: config.Get(),
|
||||
repository: heartbeatRepo,
|
||||
}
|
||||
}
|
||||
|
||||
func (srv *HeartbeatService) InsertBatch(heartbeats []*models.Heartbeat) error {
|
||||
var batch []interface{}
|
||||
for _, h := range heartbeats {
|
||||
batch = append(batch, *h)
|
||||
}
|
||||
|
||||
if err := gormbulk.BulkInsert(srv.Db, batch, 3000); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return srv.repository.InsertBatch(heartbeats)
|
||||
}
|
||||
|
||||
func (srv *HeartbeatService) GetAllWithin(from, to time.Time, user *models.User) ([]*models.Heartbeat, error) {
|
||||
var heartbeats []*models.Heartbeat
|
||||
if err := srv.Db.
|
||||
Where(&models.Heartbeat{UserID: user.ID}).
|
||||
Where("time >= ?", from).
|
||||
Where("time < ?", to).
|
||||
Order("time asc").
|
||||
Find(&heartbeats).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return heartbeats, nil
|
||||
return srv.repository.GetAllWithin(from, to, user)
|
||||
}
|
||||
|
||||
// Will return *models.Heartbeat object with only user_id and time fields filled
|
||||
func (srv *HeartbeatService) GetFirstUserHeartbeats(userIds []string) ([]*models.Heartbeat, error) {
|
||||
var heartbeats []*models.Heartbeat
|
||||
if err := srv.Db.
|
||||
Table("heartbeats").
|
||||
Select("user_id, min(time) as time").
|
||||
Where("user_id IN (?)", userIds).
|
||||
Group("user_id").
|
||||
Scan(&heartbeats).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return heartbeats, nil
|
||||
return srv.repository.GetFirstByUsers(userIds)
|
||||
}
|
||||
|
||||
func (srv *HeartbeatService) DeleteBefore(t time.Time) error {
|
||||
if err := srv.Db.
|
||||
Where("time <= ?", t).
|
||||
Delete(models.Heartbeat{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return srv.repository.DeleteBefore(t)
|
||||
}
|
||||
|
||||
func (srv *HeartbeatService) CleanUp() error {
|
||||
|
@ -1,63 +1,31 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/muety/wakapi/config"
|
||||
"github.com/muety/wakapi/models"
|
||||
"github.com/muety/wakapi/repositories"
|
||||
)
|
||||
|
||||
type KeyValueService struct {
|
||||
Config *config.Config
|
||||
Db *gorm.DB
|
||||
config *config.Config
|
||||
repository *repositories.KeyValueRepository
|
||||
}
|
||||
|
||||
func NewKeyValueService(db *gorm.DB) *KeyValueService {
|
||||
func NewKeyValueService(keyValueRepo *repositories.KeyValueRepository) *KeyValueService {
|
||||
return &KeyValueService{
|
||||
Config: config.Get(),
|
||||
Db: db,
|
||||
config: config.Get(),
|
||||
repository: keyValueRepo,
|
||||
}
|
||||
}
|
||||
|
||||
func (srv *KeyValueService) GetString(key string) (*models.KeyStringValue, error) {
|
||||
kv := &models.KeyStringValue{}
|
||||
if err := srv.Db.
|
||||
Where(&models.KeyStringValue{Key: key}).
|
||||
First(&kv).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return kv, nil
|
||||
return srv.repository.GetString(key)
|
||||
}
|
||||
|
||||
func (srv *KeyValueService) PutString(kv *models.KeyStringValue) error {
|
||||
result := srv.Db.
|
||||
Where(&models.KeyStringValue{Key: kv.Key}).
|
||||
Assign(kv).
|
||||
FirstOrCreate(kv)
|
||||
|
||||
if err := result.Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if result.RowsAffected != 1 {
|
||||
return errors.New("nothing updated")
|
||||
}
|
||||
|
||||
return nil
|
||||
return srv.repository.PutString(kv)
|
||||
}
|
||||
|
||||
func (srv *KeyValueService) DeleteString(key string) error {
|
||||
result := srv.Db.
|
||||
Delete(&models.KeyStringValue{}, &models.KeyStringValue{Key: key})
|
||||
|
||||
if err := result.Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if result.RowsAffected != 1 {
|
||||
return errors.New("nothing deleted")
|
||||
}
|
||||
|
||||
return nil
|
||||
return srv.repository.DeleteString(key)
|
||||
}
|
||||
|
@ -4,35 +4,35 @@ import (
|
||||
"crypto/md5"
|
||||
"errors"
|
||||
"github.com/muety/wakapi/config"
|
||||
"github.com/muety/wakapi/repositories"
|
||||
"github.com/patrickmn/go-cache"
|
||||
"math"
|
||||
"sort"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/muety/wakapi/models"
|
||||
)
|
||||
|
||||
const HeartbeatDiffThreshold = 2 * time.Minute
|
||||
|
||||
type SummaryService struct {
|
||||
Config *config.Config
|
||||
Cache *cache.Cache
|
||||
Db *gorm.DB
|
||||
HeartbeatService *HeartbeatService
|
||||
AliasService *AliasService
|
||||
CustomRuleService *CustomRuleService
|
||||
config *config.Config
|
||||
cache *cache.Cache
|
||||
repository *repositories.SummaryRepository
|
||||
heartbeatService *HeartbeatService
|
||||
aliasService *AliasService
|
||||
customRuleService *CustomRuleService
|
||||
}
|
||||
|
||||
func NewSummaryService(db *gorm.DB, heartbeatService *HeartbeatService, aliasService *AliasService, customRuleService *CustomRuleService) *SummaryService {
|
||||
func NewSummaryService(summaryRepo *repositories.SummaryRepository, heartbeatService *HeartbeatService, aliasService *AliasService, customRuleService *CustomRuleService) *SummaryService {
|
||||
return &SummaryService{
|
||||
Config: config.Get(),
|
||||
Cache: cache.New(24*time.Hour, 24*time.Hour),
|
||||
Db: db,
|
||||
HeartbeatService: heartbeatService,
|
||||
AliasService: aliasService,
|
||||
CustomRuleService: customRuleService,
|
||||
config: config.Get(),
|
||||
cache: cache.New(24*time.Hour, 24*time.Hour),
|
||||
repository: summaryRepo,
|
||||
heartbeatService: heartbeatService,
|
||||
aliasService: aliasService,
|
||||
customRuleService: customRuleService,
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,7 +50,7 @@ func (srv *SummaryService) Construct(from, to time.Time, user *models.User, reco
|
||||
existingSummaries = make([]*models.Summary, 0)
|
||||
} else {
|
||||
cacheKey = getHash([]time.Time{from, to}, user)
|
||||
if result, ok := srv.Cache.Get(cacheKey); ok {
|
||||
if result, ok := srv.cache.Get(cacheKey); ok {
|
||||
return result.(*models.Summary), nil
|
||||
}
|
||||
summaries, err := srv.GetByUserWithin(user, from, to)
|
||||
@ -64,7 +64,7 @@ func (srv *SummaryService) Construct(from, to time.Time, user *models.User, reco
|
||||
|
||||
heartbeats := make([]*models.Heartbeat, 0)
|
||||
for _, interval := range missingIntervals {
|
||||
hb, err := srv.HeartbeatService.GetAllWithin(interval.Start, interval.End, user)
|
||||
hb, err := srv.heartbeatService.GetAllWithin(interval.Start, interval.End, user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -79,7 +79,7 @@ func (srv *SummaryService) Construct(from, to time.Time, user *models.User, reco
|
||||
var osItems []*models.SummaryItem
|
||||
var machineItems []*models.SummaryItem
|
||||
|
||||
if err := srv.AliasService.LoadUserAliases(user.ID); err != nil {
|
||||
if err := srv.aliasService.LoadUserAliases(user.ID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -144,7 +144,7 @@ func (srv *SummaryService) Construct(from, to time.Time, user *models.User, reco
|
||||
}
|
||||
|
||||
if cacheKey != "" {
|
||||
srv.Cache.SetDefault(cacheKey, summary)
|
||||
srv.cache.SetDefault(cacheKey, summary)
|
||||
}
|
||||
|
||||
return summary, nil
|
||||
@ -179,14 +179,14 @@ func (srv *SummaryService) PostProcess(summary *models.Summary) *models.Summary
|
||||
|
||||
for _, item := range origin {
|
||||
// Add all "top-level" items, i.e. such without aliases
|
||||
if key, _ := srv.AliasService.GetAliasOrDefault(summary.UserID, item.Type, item.Key); key == item.Key {
|
||||
if key, _ := srv.aliasService.GetAliasOrDefault(summary.UserID, item.Type, item.Key); key == item.Key {
|
||||
target = append(target, item)
|
||||
}
|
||||
}
|
||||
|
||||
for _, item := range origin {
|
||||
// Add all remaining projects and merge with their alias
|
||||
if key, _ := srv.AliasService.GetAliasOrDefault(summary.UserID, item.Type, item.Key); key != item.Key {
|
||||
if key, _ := srv.aliasService.GetAliasOrDefault(summary.UserID, item.Type, item.Key); key != item.Key {
|
||||
if targetItem := findItem(key); targetItem != nil {
|
||||
targetItem.Total += item.Total
|
||||
} else {
|
||||
@ -215,41 +215,16 @@ func (srv *SummaryService) PostProcess(summary *models.Summary) *models.Summary
|
||||
}
|
||||
|
||||
func (srv *SummaryService) Insert(summary *models.Summary) error {
|
||||
if err := srv.Db.Create(summary).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return srv.repository.Insert(summary)
|
||||
}
|
||||
|
||||
func (srv *SummaryService) GetByUserWithin(user *models.User, from, to time.Time) ([]*models.Summary, error) {
|
||||
var summaries []*models.Summary
|
||||
if err := srv.Db.
|
||||
Where(&models.Summary{UserID: user.ID}).
|
||||
Where("from_time >= ?", from).
|
||||
Where("to_time <= ?", to).
|
||||
Order("from_time asc").
|
||||
Preload("Projects", "type = ?", models.SummaryProject).
|
||||
Preload("Languages", "type = ?", models.SummaryLanguage).
|
||||
Preload("Editors", "type = ?", models.SummaryEditor).
|
||||
Preload("OperatingSystems", "type = ?", models.SummaryOS).
|
||||
Preload("Machines", "type = ?", models.SummaryMachine).
|
||||
Find(&summaries).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return summaries, nil
|
||||
return srv.repository.GetByUserWithin(user, from, to)
|
||||
}
|
||||
|
||||
// Will return *models.Index objects with only user_id and to_time filled
|
||||
func (srv *SummaryService) GetLatestByUser() ([]*models.Summary, error) {
|
||||
var summaries []*models.Summary
|
||||
if err := srv.Db.
|
||||
Table("summaries").
|
||||
Select("user_id, max(to_time) as to_time").
|
||||
Group("user_id").
|
||||
Scan(&summaries).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return summaries, nil
|
||||
return srv.repository.GetLatestByUser()
|
||||
}
|
||||
|
||||
func (srv *SummaryService) aggregateBy(heartbeats []*models.Heartbeat, summaryType uint8, user *models.User, c chan models.SummaryItemContainer) {
|
||||
|
@ -1,50 +1,35 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/muety/wakapi/config"
|
||||
"github.com/muety/wakapi/models"
|
||||
"github.com/muety/wakapi/repositories"
|
||||
"github.com/muety/wakapi/utils"
|
||||
uuid "github.com/satori/go.uuid"
|
||||
)
|
||||
|
||||
type UserService struct {
|
||||
Config *config.Config
|
||||
Db *gorm.DB
|
||||
Config *config.Config
|
||||
repository *repositories.UserRepository
|
||||
}
|
||||
|
||||
func NewUserService(db *gorm.DB) *UserService {
|
||||
func NewUserService(userRepo *repositories.UserRepository) *UserService {
|
||||
return &UserService{
|
||||
Config: config.Get(),
|
||||
Db: db,
|
||||
Config: config.Get(),
|
||||
repository: userRepo,
|
||||
}
|
||||
}
|
||||
|
||||
func (srv *UserService) GetUserById(userId string) (*models.User, error) {
|
||||
u := &models.User{}
|
||||
if err := srv.Db.Where(&models.User{ID: userId}).First(u).Error; err != nil {
|
||||
return u, err
|
||||
}
|
||||
return u, nil
|
||||
return srv.repository.GetById(userId)
|
||||
}
|
||||
|
||||
func (srv *UserService) GetUserByKey(key string) (*models.User, error) {
|
||||
u := &models.User{}
|
||||
if err := srv.Db.Where(&models.User{ApiKey: key}).First(u).Error; err != nil {
|
||||
return u, err
|
||||
}
|
||||
return u, nil
|
||||
return srv.repository.GetByApiKey(key)
|
||||
}
|
||||
|
||||
func (srv *UserService) GetAll() ([]*models.User, error) {
|
||||
var users []*models.User
|
||||
if err := srv.Db.
|
||||
Table("users").
|
||||
Find(&users).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return users, nil
|
||||
return srv.repository.GetAll()
|
||||
}
|
||||
|
||||
func (srv *UserService) CreateOrGet(signup *models.Signup) (*models.User, bool, error) {
|
||||
@ -60,29 +45,11 @@ func (srv *UserService) CreateOrGet(signup *models.Signup) (*models.User, bool,
|
||||
u.Password = hash
|
||||
}
|
||||
|
||||
result := srv.Db.FirstOrCreate(u, &models.User{ID: u.ID})
|
||||
if err := result.Error; err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
if result.RowsAffected == 1 {
|
||||
return u, true, nil
|
||||
}
|
||||
|
||||
return u, false, nil
|
||||
return srv.repository.InsertOrGet(u)
|
||||
}
|
||||
|
||||
func (srv *UserService) Update(user *models.User) (*models.User, error) {
|
||||
result := srv.Db.Model(&models.User{}).Updates(user)
|
||||
if err := result.Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if result.RowsAffected != 1 {
|
||||
return nil, errors.New("nothing updated")
|
||||
}
|
||||
|
||||
return user, nil
|
||||
return srv.repository.Update(user)
|
||||
}
|
||||
|
||||
func (srv *UserService) ResetApiKey(user *models.User) (*models.User, error) {
|
||||
@ -91,16 +58,7 @@ func (srv *UserService) ResetApiKey(user *models.User) (*models.User, error) {
|
||||
}
|
||||
|
||||
func (srv *UserService) ToggleBadges(user *models.User) (*models.User, error) {
|
||||
result := srv.Db.Model(user).Update("badges_enabled", !user.BadgesEnabled)
|
||||
if err := result.Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if result.RowsAffected != 1 {
|
||||
return nil, errors.New("nothing updated")
|
||||
}
|
||||
|
||||
return user, nil
|
||||
return srv.repository.UpdateField(user, "badges_enabled", !user.BadgesEnabled)
|
||||
}
|
||||
|
||||
func (srv *UserService) MigrateMd5Password(user *models.User, login *models.Login) (*models.User, error) {
|
||||
@ -110,13 +68,5 @@ func (srv *UserService) MigrateMd5Password(user *models.User, login *models.Logi
|
||||
} else {
|
||||
user.Password = hash
|
||||
}
|
||||
|
||||
result := srv.Db.Model(user).Update("password", user.Password)
|
||||
if err := result.Error; err != nil {
|
||||
return nil, err
|
||||
} else if result.RowsAffected < 1 {
|
||||
return nil, errors.New("nothing changes")
|
||||
}
|
||||
|
||||
return user, nil
|
||||
return srv.repository.UpdateField(user, "password", user.Password)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user