1
0
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:
Ferdinand Mütsch 2020-11-01 16:56:36 +01:00
parent 96ff490d8d
commit 755cabb5f4
15 changed files with 458 additions and 265 deletions

32
main.go
View File

@ -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
View 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
}

View 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
}

View 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
View 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
View 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
View 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
}

View File

@ -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)

View File

@ -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

View File

@ -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) {

View File

@ -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
}

View File

@ -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 {

View File

@ -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)
}

View File

@ -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) {

View File

@ -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)
}