From 35cdc7b48566ef190f671a9990d4302cfd933170 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferdinand=20M=C3=BCtsch?= Date: Sun, 8 Nov 2020 10:12:49 +0100 Subject: [PATCH] refactor: define interface types for all services and repositories --- config/config.go | 13 ++---- main.go | 26 ++++++------ middlewares/authenticate.go | 10 ++--- repositories/repositories.go | 46 ++++++++++++++++++++ routes/compat/shields/v1/badge.go | 13 +++--- routes/compat/wakatime/v1/all_time.go | 10 ++--- routes/compat/wakatime/v1/summaries.go | 10 ++--- routes/heartbeat.go | 6 +-- routes/home.go | 10 ++--- routes/imprint.go | 4 +- routes/settings.go | 13 +++--- routes/summary.go | 4 +- services/aggregation.go | 8 ++-- services/alias.go | 4 +- services/heartbeat.go | 6 +-- services/key_value.go | 4 +- services/language_mapping.go | 4 +- services/services.go | 58 ++++++++++++++++++++++++++ services/summary.go | 8 ++-- services/user.go | 4 +- 20 files changed, 178 insertions(+), 83 deletions(-) create mode 100644 repositories/repositories.go create mode 100644 services/services.go diff --git a/config/config.go b/config/config.go index 1a5e1b0..6418ebf 100644 --- a/config/config.go +++ b/config/config.go @@ -28,10 +28,8 @@ const ( SQLDialectSqlite = "sqlite3" ) -var ( - cfg *Config - cFlag *string -) +var cfg *Config +var cFlag = flag.String("config", defaultConfigPath, "config file location") type appConfig struct { AggregationTime string `yaml:"aggregation_time" default:"02:15" env:"WAKAPI_AGGREGATION_TIME"` @@ -71,11 +69,6 @@ type Config struct { Server serverConfig } -func init() { - cFlag = flag.String("c", defaultConfigPath, "config file location") - flag.Parse() -} - func (c *Config) IsDev() bool { return IsDev(c.Env) } @@ -220,6 +213,8 @@ func Get() *Config { func Load() *Config { config := &Config{} + flag.Parse() + maybeMigrateLegacyConfig() if err := configor.New(&configor.Config{}).Load(config, mustReadConfigLocation()); err != nil { diff --git a/main.go b/main.go index 66dbae3..dd02bd0 100644 --- a/main.go +++ b/main.go @@ -28,22 +28,22 @@ var ( ) var ( - aliasRepository *repositories.AliasRepository - heartbeatRepository *repositories.HeartbeatRepository - userRepository *repositories.UserRepository - languageMappingRepository *repositories.LanguageMappingRepository - summaryRepository *repositories.SummaryRepository - keyValueRepository *repositories.KeyValueRepository + aliasRepository repositories.IAliasRepository + heartbeatRepository repositories.IHeartbeatRepository + userRepository repositories.IUserRepository + languageMappingRepository repositories.ILanguageMappingRepository + summaryRepository repositories.ISummaryRepository + keyValueRepository repositories.IKeyValueRepository ) var ( - aliasService *services.AliasService - heartbeatService *services.HeartbeatService - userService *services.UserService - languageMappingService *services.LanguageMappingService - summaryService *services.SummaryService - aggregationService *services.AggregationService - keyValueService *services.KeyValueService + aliasService services.IAliasService + heartbeatService services.IHeartbeatService + userService services.IUserService + languageMappingService services.ILanguageMappingService + summaryService services.ISummaryService + aggregationService services.IAggregationService + keyValueService services.IKeyValueService ) // TODO: Refactor entire project to be structured after business domains diff --git a/middlewares/authenticate.go b/middlewares/authenticate.go index 05d4ff7..b7a4882 100644 --- a/middlewares/authenticate.go +++ b/middlewares/authenticate.go @@ -19,12 +19,12 @@ import ( type AuthenticateMiddleware struct { config *conf.Config - userSrvc *services.UserService cache *cache.Cache + userSrvc services.IUserService whitelistPaths []string } -func NewAuthenticateMiddleware(userService *services.UserService, whitelistPaths []string) *AuthenticateMiddleware { +func NewAuthenticateMiddleware(userService services.IUserService, whitelistPaths []string) *AuthenticateMiddleware { return &AuthenticateMiddleware{ config: conf.Get(), userSrvc: userService, @@ -107,7 +107,7 @@ func (m *AuthenticateMiddleware) tryGetUserByCookie(r *http.Request) (*models.Us return nil, err } - if !CheckAndMigratePassword(user, login, m.config.Security.PasswordSalt, m.userSrvc) { + if !CheckAndMigratePassword(user, login, m.config.Security.PasswordSalt, &m.userSrvc) { return nil, errors.New("invalid password") } @@ -115,11 +115,11 @@ func (m *AuthenticateMiddleware) tryGetUserByCookie(r *http.Request) (*models.Us } // migrate old md5-hashed passwords to new salted bcrypt hashes for backwards compatibility -func CheckAndMigratePassword(user *models.User, login *models.Login, salt string, userServiceRef *services.UserService) bool { +func CheckAndMigratePassword(user *models.User, login *models.Login, salt string, userServiceRef *services.IUserService) bool { if utils.IsMd5(user.Password) { if utils.CompareMd5(user.Password, login.Password, "") { log.Printf("migrating old md5 password to new bcrypt format for user '%s'", user.ID) - userServiceRef.MigrateMd5Password(user, login) + (*userServiceRef).MigrateMd5Password(user, login) return true } return false diff --git a/repositories/repositories.go b/repositories/repositories.go new file mode 100644 index 0000000..1564763 --- /dev/null +++ b/repositories/repositories.go @@ -0,0 +1,46 @@ +package repositories + +import ( + "github.com/muety/wakapi/models" + "time" +) + +type IAliasRepository interface { + GetByUser(string) ([]*models.Alias, error) +} + +type IHeartbeatRepository interface { + InsertBatch([]*models.Heartbeat) error + GetAllWithin(time.Time, time.Time, *models.User) ([]*models.Heartbeat, error) + GetFirstByUsers() ([]*models.TimeByUser, error) + DeleteBefore(time.Time) error +} + +type IKeyValueRepository interface { + GetString(string) (*models.KeyStringValue, error) + PutString(*models.KeyStringValue) error + DeleteString(string) error +} + +type ILanguageMappingRepository interface { + GetById(uint) (*models.LanguageMapping, error) + GetByUser(string) ([]*models.LanguageMapping, error) + Insert(*models.LanguageMapping) (*models.LanguageMapping, error) + Delete(uint) error +} + +type ISummaryRepository interface { + Insert(*models.Summary) error + GetByUserWithin(*models.User, time.Time, time.Time) ([]*models.Summary, error) + GetLastByUser() ([]*models.TimeByUser, error) + DeleteByUser(string) error +} + +type IUserRepository interface { + GetById(string) (*models.User, error) + GetByApiKey(string) (*models.User, error) + GetAll() ([]*models.User, error) + InsertOrGet(*models.User) (*models.User, bool, error) + Update(*models.User) (*models.User, error) + UpdateField(*models.User, string, interface{}) (*models.User, error) +} diff --git a/routes/compat/shields/v1/badge.go b/routes/compat/shields/v1/badge.go index 868e9c3..6450315 100644 --- a/routes/compat/shields/v1/badge.go +++ b/routes/compat/shields/v1/badge.go @@ -2,7 +2,7 @@ package v1 import ( "github.com/gorilla/mux" - config2 "github.com/muety/wakapi/config" + conf "github.com/muety/wakapi/config" "github.com/muety/wakapi/models" v1 "github.com/muety/wakapi/models/compat/shields/v1" "github.com/muety/wakapi/services" @@ -18,17 +18,16 @@ const ( ) type BadgeHandler struct { - userSrvc *services.UserService - summarySrvc *services.SummaryService - aliasSrvc *services.AliasService - config *config2.Config + config *conf.Config + userSrvc services.IUserService + summarySrvc services.ISummaryService } -func NewBadgeHandler(summaryService *services.SummaryService, userService *services.UserService) *BadgeHandler { +func NewBadgeHandler(summaryService services.ISummaryService, userService services.IUserService) *BadgeHandler { return &BadgeHandler{ summarySrvc: summaryService, userSrvc: userService, - config: config2.Get(), + config: conf.Get(), } } diff --git a/routes/compat/wakatime/v1/all_time.go b/routes/compat/wakatime/v1/all_time.go index c7e4433..485da4b 100644 --- a/routes/compat/wakatime/v1/all_time.go +++ b/routes/compat/wakatime/v1/all_time.go @@ -2,7 +2,7 @@ package v1 import ( "github.com/gorilla/mux" - config2 "github.com/muety/wakapi/config" + conf "github.com/muety/wakapi/config" "github.com/muety/wakapi/models" v1 "github.com/muety/wakapi/models/compat/wakatime/v1" "github.com/muety/wakapi/services" @@ -13,14 +13,14 @@ import ( ) type AllTimeHandler struct { - summarySrvc *services.SummaryService - config *config2.Config + config *conf.Config + summarySrvc services.ISummaryService } -func NewAllTimeHandler(summaryService *services.SummaryService) *AllTimeHandler { +func NewAllTimeHandler(summaryService services.ISummaryService) *AllTimeHandler { return &AllTimeHandler{ summarySrvc: summaryService, - config: config2.Get(), + config: conf.Get(), } } diff --git a/routes/compat/wakatime/v1/summaries.go b/routes/compat/wakatime/v1/summaries.go index 77f5971..61324a6 100644 --- a/routes/compat/wakatime/v1/summaries.go +++ b/routes/compat/wakatime/v1/summaries.go @@ -3,7 +3,7 @@ package v1 import ( "errors" "github.com/gorilla/mux" - config2 "github.com/muety/wakapi/config" + conf "github.com/muety/wakapi/config" "github.com/muety/wakapi/models" v1 "github.com/muety/wakapi/models/compat/wakatime/v1" "github.com/muety/wakapi/services" @@ -14,14 +14,14 @@ import ( ) type SummariesHandler struct { - summarySrvc *services.SummaryService - config *config2.Config + config *conf.Config + summarySrvc services.ISummaryService } -func NewSummariesHandler(summaryService *services.SummaryService) *SummariesHandler { +func NewSummariesHandler(summaryService services.ISummaryService) *SummariesHandler { return &SummariesHandler{ summarySrvc: summaryService, - config: config2.Get(), + config: conf.Get(), } } diff --git a/routes/heartbeat.go b/routes/heartbeat.go index 1686ba3..6a58af6 100644 --- a/routes/heartbeat.go +++ b/routes/heartbeat.go @@ -14,11 +14,11 @@ import ( type HeartbeatHandler struct { config *conf.Config - heartbeatSrvc *services.HeartbeatService - languageMappingSrvc *services.LanguageMappingService + heartbeatSrvc services.IHeartbeatService + languageMappingSrvc services.ILanguageMappingService } -func NewHeartbeatHandler(heartbeatService *services.HeartbeatService, languageMappingService *services.LanguageMappingService) *HeartbeatHandler { +func NewHeartbeatHandler(heartbeatService services.IHeartbeatService, languageMappingService services.ILanguageMappingService) *HeartbeatHandler { return &HeartbeatHandler{ config: conf.Get(), heartbeatSrvc: heartbeatService, diff --git a/routes/home.go b/routes/home.go index b9c3769..9d30578 100644 --- a/routes/home.go +++ b/routes/home.go @@ -10,19 +10,18 @@ import ( "github.com/muety/wakapi/services" "github.com/muety/wakapi/utils" "net/http" - "net/url" "time" ) type HomeHandler struct { config *conf.Config - userSrvc *services.UserService + userSrvc services.IUserService } var loginDecoder = schema.NewDecoder() var signupDecoder = schema.NewDecoder() -func NewHomeHandler(userService *services.UserService) *HomeHandler { +func NewHomeHandler(userService services.IUserService) *HomeHandler { return &HomeHandler{ config: conf.Get(), userSrvc: userService, @@ -72,7 +71,7 @@ func (h *HomeHandler) PostLogin(w http.ResponseWriter, r *http.Request) { } // TODO: depending on middleware package here is a hack - if !middlewares.CheckAndMigratePassword(user, &login, h.config.Security.PasswordSalt, h.userSrvc) { + if !middlewares.CheckAndMigratePassword(user, &login, h.config.Security.PasswordSalt, &h.userSrvc) { w.WriteHeader(http.StatusUnauthorized) templates[conf.IndexTemplate].Execute(w, h.buildViewModel(r).WithError("invalid credentials")) return @@ -161,8 +160,7 @@ func (h *HomeHandler) PostSignup(w http.ResponseWriter, r *http.Request) { return } - msg := url.QueryEscape("account created successfully") - http.Redirect(w, r, fmt.Sprintf("%s/?success=%s", h.config.Server.BasePath, msg), http.StatusFound) + http.Redirect(w, r, fmt.Sprintf("%s/?success=%s", h.config.Server.BasePath, "account created successfully"), http.StatusFound) } func (h *HomeHandler) buildViewModel(r *http.Request) *view.HomeViewModel { diff --git a/routes/imprint.go b/routes/imprint.go index 554ef46..d72db16 100644 --- a/routes/imprint.go +++ b/routes/imprint.go @@ -10,10 +10,10 @@ import ( type ImprintHandler struct { config *conf.Config - keyValueSrvc *services.KeyValueService + keyValueSrvc services.IKeyValueService } -func NewImprintHandler(keyValueService *services.KeyValueService) *ImprintHandler { +func NewImprintHandler(keyValueService services.IKeyValueService) *ImprintHandler { return &ImprintHandler{ config: conf.Get(), keyValueSrvc: keyValueService, diff --git a/routes/settings.go b/routes/settings.go index 87b6061..68e7fa7 100644 --- a/routes/settings.go +++ b/routes/settings.go @@ -10,21 +10,20 @@ import ( "github.com/muety/wakapi/utils" "log" "net/http" - "net/url" "strconv" ) type SettingsHandler struct { config *conf.Config - userSrvc *services.UserService - summarySrvc *services.SummaryService - aggregationSrvc *services.AggregationService - languageMappingSrvc *services.LanguageMappingService + userSrvc services.IUserService + summarySrvc services.ISummaryService + aggregationSrvc services.IAggregationService + languageMappingSrvc services.ILanguageMappingService } var credentialsDecoder = schema.NewDecoder() -func NewSettingsHandler(userService *services.UserService, summaryService *services.SummaryService, aggregationService *services.AggregationService, languageMappingService *services.LanguageMappingService) *SettingsHandler { +func NewSettingsHandler(userService services.IUserService, summaryService services.ISummaryService, aggregationService services.IAggregationService, languageMappingService services.ILanguageMappingService) *SettingsHandler { return &SettingsHandler{ config: conf.Get(), summarySrvc: summaryService, @@ -178,7 +177,7 @@ func (h *SettingsHandler) PostResetApiKey(w http.ResponseWriter, r *http.Request return } - msg := url.QueryEscape(fmt.Sprintf("your new api key is: %s", user.ApiKey)) + msg := fmt.Sprintf("your new api key is: %s", user.ApiKey) templates[conf.SettingsTemplate].Execute(w, h.buildViewModel(r).WithSuccess(msg)) } diff --git a/routes/summary.go b/routes/summary.go index 0a8de3e..21478c8 100644 --- a/routes/summary.go +++ b/routes/summary.go @@ -10,11 +10,11 @@ import ( ) type SummaryHandler struct { - summarySrvc *services.SummaryService config *conf.Config + summarySrvc services.ISummaryService } -func NewSummaryHandler(summaryService *services.SummaryService) *SummaryHandler { +func NewSummaryHandler(summaryService services.ISummaryService) *SummaryHandler { return &SummaryHandler{ summarySrvc: summaryService, config: conf.Get(), diff --git a/services/aggregation.go b/services/aggregation.go index dc384c8..8df0c67 100644 --- a/services/aggregation.go +++ b/services/aggregation.go @@ -16,12 +16,12 @@ const ( type AggregationService struct { config *config.Config - userService *UserService - summaryService *SummaryService - heartbeatService *HeartbeatService + userService IUserService + summaryService ISummaryService + heartbeatService IHeartbeatService } -func NewAggregationService(userService *UserService, summaryService *SummaryService, heartbeatService *HeartbeatService) *AggregationService { +func NewAggregationService(userService IUserService, summaryService ISummaryService, heartbeatService IHeartbeatService) *AggregationService { return &AggregationService{ config: config.Get(), userService: userService, diff --git a/services/alias.go b/services/alias.go index d2317be..3ee0797 100644 --- a/services/alias.go +++ b/services/alias.go @@ -10,10 +10,10 @@ import ( type AliasService struct { config *config.Config - repository *repositories.AliasRepository + repository repositories.IAliasRepository } -func NewAliasService(aliasRepo *repositories.AliasRepository) *AliasService { +func NewAliasService(aliasRepo repositories.IAliasRepository) *AliasService { return &AliasService{ config: config.Get(), repository: aliasRepo, diff --git a/services/heartbeat.go b/services/heartbeat.go index 644e814..da8dd53 100644 --- a/services/heartbeat.go +++ b/services/heartbeat.go @@ -10,11 +10,11 @@ import ( type HeartbeatService struct { config *config.Config - repository *repositories.HeartbeatRepository - languageMappingSrvc *LanguageMappingService + repository repositories.IHeartbeatRepository + languageMappingSrvc ILanguageMappingService } -func NewHeartbeatService(heartbeatRepo *repositories.HeartbeatRepository, languageMappingService *LanguageMappingService) *HeartbeatService { +func NewHeartbeatService(heartbeatRepo repositories.IHeartbeatRepository, languageMappingService ILanguageMappingService) *HeartbeatService { return &HeartbeatService{ config: config.Get(), repository: heartbeatRepo, diff --git a/services/key_value.go b/services/key_value.go index 1b839b2..11c7e67 100644 --- a/services/key_value.go +++ b/services/key_value.go @@ -8,10 +8,10 @@ import ( type KeyValueService struct { config *config.Config - repository *repositories.KeyValueRepository + repository repositories.IKeyValueRepository } -func NewKeyValueService(keyValueRepo *repositories.KeyValueRepository) *KeyValueService { +func NewKeyValueService(keyValueRepo repositories.IKeyValueRepository) *KeyValueService { return &KeyValueService{ config: config.Get(), repository: keyValueRepo, diff --git a/services/language_mapping.go b/services/language_mapping.go index 40fc732..20f364e 100644 --- a/services/language_mapping.go +++ b/services/language_mapping.go @@ -10,11 +10,11 @@ import ( type LanguageMappingService struct { config *config.Config - repository *repositories.LanguageMappingRepository cache *cache.Cache + repository repositories.ILanguageMappingRepository } -func NewLanguageMappingService(languageMappingsRepo *repositories.LanguageMappingRepository) *LanguageMappingService { +func NewLanguageMappingService(languageMappingsRepo repositories.ILanguageMappingRepository) *LanguageMappingService { return &LanguageMappingService{ config: config.Get(), repository: languageMappingsRepo, diff --git a/services/services.go b/services/services.go new file mode 100644 index 0000000..ecd08d3 --- /dev/null +++ b/services/services.go @@ -0,0 +1,58 @@ +package services + +import ( + "github.com/muety/wakapi/models" + "time" +) + +type IAggregationService interface { + Schedule() + Run(map[string]bool) error +} + +type IAliasService interface { + LoadUserAliases(string) error + GetAliasOrDefault(string, uint8, string) (string, error) + IsInitialized(string) bool +} + +type IHeartbeatService interface { + InsertBatch([]*models.Heartbeat) error + GetAllWithin(time.Time, time.Time, *models.User) ([]*models.Heartbeat, error) + GetFirstByUsers() ([]*models.TimeByUser, error) + DeleteBefore(time.Time) error +} + +type IKeyValueService interface { + GetString(string) (*models.KeyStringValue, error) + PutString(*models.KeyStringValue) error + DeleteString(string) error +} + +type ILanguageMappingService interface { + GetById(uint) (*models.LanguageMapping, error) + GetByUser(string) ([]*models.LanguageMapping, error) + ResolveByUser(string) (map[string]string, error) + Create(*models.LanguageMapping) (*models.LanguageMapping, error) + Delete(mapping *models.LanguageMapping) error +} + +type ISummaryService interface { + Aliased(time.Time, time.Time, *models.User, SummaryRetriever) (*models.Summary, error) + Retrieve(time.Time, time.Time, *models.User) (*models.Summary, error) + Summarize(time.Time, time.Time, *models.User) (*models.Summary, error) + GetLatestByUser() ([]*models.TimeByUser, error) + DeleteByUser(string) error + Insert(*models.Summary) error +} + +type IUserService interface { + GetUserById(string) (*models.User, error) + GetUserByKey(string) (*models.User, error) + GetAll() ([]*models.User, error) + CreateOrGet(*models.Signup) (*models.User, bool, error) + Update(*models.User) (*models.User, error) + ResetApiKey(*models.User) (*models.User, error) + ToggleBadges(*models.User) (*models.User, error) + MigrateMd5Password(*models.User, *models.Login) (*models.User, error) +} diff --git a/services/summary.go b/services/summary.go index e8b148e..9a71689 100644 --- a/services/summary.go +++ b/services/summary.go @@ -17,14 +17,14 @@ const HeartbeatDiffThreshold = 2 * time.Minute type SummaryService struct { config *config.Config cache *cache.Cache - repository *repositories.SummaryRepository - heartbeatService *HeartbeatService - aliasService *AliasService + repository repositories.ISummaryRepository + heartbeatService IHeartbeatService + aliasService IAliasService } type SummaryRetriever func(f, t time.Time, u *models.User) (*models.Summary, error) -func NewSummaryService(summaryRepo *repositories.SummaryRepository, heartbeatService *HeartbeatService, aliasService *AliasService) *SummaryService { +func NewSummaryService(summaryRepo repositories.ISummaryRepository, heartbeatService IHeartbeatService, aliasService IAliasService) *SummaryService { return &SummaryService{ config: config.Get(), cache: cache.New(24*time.Hour, 24*time.Hour), diff --git a/services/user.go b/services/user.go index 710b10c..bdb0ee5 100644 --- a/services/user.go +++ b/services/user.go @@ -10,10 +10,10 @@ import ( type UserService struct { Config *config.Config - repository *repositories.UserRepository + repository repositories.IUserRepository } -func NewUserService(userRepo *repositories.UserRepository) *UserService { +func NewUserService(userRepo repositories.IUserRepository) *UserService { return &UserService{ Config: config.Get(), repository: userRepo,