1
0
mirror of https://github.com/muety/wakapi.git synced 2023-08-10 21:12:56 +03:00

refactor: define interface types for all services and repositories

This commit is contained in:
Ferdinand Mütsch 2020-11-08 10:12:49 +01:00
parent 664714de8f
commit 35cdc7b485
20 changed files with 178 additions and 83 deletions

View File

@ -28,10 +28,8 @@ const (
SQLDialectSqlite = "sqlite3" SQLDialectSqlite = "sqlite3"
) )
var ( var cfg *Config
cfg *Config var cFlag = flag.String("config", defaultConfigPath, "config file location")
cFlag *string
)
type appConfig struct { type appConfig struct {
AggregationTime string `yaml:"aggregation_time" default:"02:15" env:"WAKAPI_AGGREGATION_TIME"` AggregationTime string `yaml:"aggregation_time" default:"02:15" env:"WAKAPI_AGGREGATION_TIME"`
@ -71,11 +69,6 @@ type Config struct {
Server serverConfig Server serverConfig
} }
func init() {
cFlag = flag.String("c", defaultConfigPath, "config file location")
flag.Parse()
}
func (c *Config) IsDev() bool { func (c *Config) IsDev() bool {
return IsDev(c.Env) return IsDev(c.Env)
} }
@ -220,6 +213,8 @@ func Get() *Config {
func Load() *Config { func Load() *Config {
config := &Config{} config := &Config{}
flag.Parse()
maybeMigrateLegacyConfig() maybeMigrateLegacyConfig()
if err := configor.New(&configor.Config{}).Load(config, mustReadConfigLocation()); err != nil { if err := configor.New(&configor.Config{}).Load(config, mustReadConfigLocation()); err != nil {

26
main.go
View File

@ -28,22 +28,22 @@ var (
) )
var ( var (
aliasRepository *repositories.AliasRepository aliasRepository repositories.IAliasRepository
heartbeatRepository *repositories.HeartbeatRepository heartbeatRepository repositories.IHeartbeatRepository
userRepository *repositories.UserRepository userRepository repositories.IUserRepository
languageMappingRepository *repositories.LanguageMappingRepository languageMappingRepository repositories.ILanguageMappingRepository
summaryRepository *repositories.SummaryRepository summaryRepository repositories.ISummaryRepository
keyValueRepository *repositories.KeyValueRepository keyValueRepository repositories.IKeyValueRepository
) )
var ( var (
aliasService *services.AliasService aliasService services.IAliasService
heartbeatService *services.HeartbeatService heartbeatService services.IHeartbeatService
userService *services.UserService userService services.IUserService
languageMappingService *services.LanguageMappingService languageMappingService services.ILanguageMappingService
summaryService *services.SummaryService summaryService services.ISummaryService
aggregationService *services.AggregationService aggregationService services.IAggregationService
keyValueService *services.KeyValueService keyValueService services.IKeyValueService
) )
// TODO: Refactor entire project to be structured after business domains // TODO: Refactor entire project to be structured after business domains

View File

@ -19,12 +19,12 @@ import (
type AuthenticateMiddleware struct { type AuthenticateMiddleware struct {
config *conf.Config config *conf.Config
userSrvc *services.UserService
cache *cache.Cache cache *cache.Cache
userSrvc services.IUserService
whitelistPaths []string whitelistPaths []string
} }
func NewAuthenticateMiddleware(userService *services.UserService, whitelistPaths []string) *AuthenticateMiddleware { func NewAuthenticateMiddleware(userService services.IUserService, whitelistPaths []string) *AuthenticateMiddleware {
return &AuthenticateMiddleware{ return &AuthenticateMiddleware{
config: conf.Get(), config: conf.Get(),
userSrvc: userService, userSrvc: userService,
@ -107,7 +107,7 @@ func (m *AuthenticateMiddleware) tryGetUserByCookie(r *http.Request) (*models.Us
return nil, err 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") 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 // 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.IsMd5(user.Password) {
if utils.CompareMd5(user.Password, login.Password, "") { if utils.CompareMd5(user.Password, login.Password, "") {
log.Printf("migrating old md5 password to new bcrypt format for user '%s'", user.ID) 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 true
} }
return false return false

View File

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

View File

@ -2,7 +2,7 @@ package v1
import ( import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
config2 "github.com/muety/wakapi/config" conf "github.com/muety/wakapi/config"
"github.com/muety/wakapi/models" "github.com/muety/wakapi/models"
v1 "github.com/muety/wakapi/models/compat/shields/v1" v1 "github.com/muety/wakapi/models/compat/shields/v1"
"github.com/muety/wakapi/services" "github.com/muety/wakapi/services"
@ -18,17 +18,16 @@ const (
) )
type BadgeHandler struct { type BadgeHandler struct {
userSrvc *services.UserService config *conf.Config
summarySrvc *services.SummaryService userSrvc services.IUserService
aliasSrvc *services.AliasService summarySrvc services.ISummaryService
config *config2.Config
} }
func NewBadgeHandler(summaryService *services.SummaryService, userService *services.UserService) *BadgeHandler { func NewBadgeHandler(summaryService services.ISummaryService, userService services.IUserService) *BadgeHandler {
return &BadgeHandler{ return &BadgeHandler{
summarySrvc: summaryService, summarySrvc: summaryService,
userSrvc: userService, userSrvc: userService,
config: config2.Get(), config: conf.Get(),
} }
} }

View File

@ -2,7 +2,7 @@ package v1
import ( import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
config2 "github.com/muety/wakapi/config" conf "github.com/muety/wakapi/config"
"github.com/muety/wakapi/models" "github.com/muety/wakapi/models"
v1 "github.com/muety/wakapi/models/compat/wakatime/v1" v1 "github.com/muety/wakapi/models/compat/wakatime/v1"
"github.com/muety/wakapi/services" "github.com/muety/wakapi/services"
@ -13,14 +13,14 @@ import (
) )
type AllTimeHandler struct { type AllTimeHandler struct {
summarySrvc *services.SummaryService config *conf.Config
config *config2.Config summarySrvc services.ISummaryService
} }
func NewAllTimeHandler(summaryService *services.SummaryService) *AllTimeHandler { func NewAllTimeHandler(summaryService services.ISummaryService) *AllTimeHandler {
return &AllTimeHandler{ return &AllTimeHandler{
summarySrvc: summaryService, summarySrvc: summaryService,
config: config2.Get(), config: conf.Get(),
} }
} }

View File

@ -3,7 +3,7 @@ package v1
import ( import (
"errors" "errors"
"github.com/gorilla/mux" "github.com/gorilla/mux"
config2 "github.com/muety/wakapi/config" conf "github.com/muety/wakapi/config"
"github.com/muety/wakapi/models" "github.com/muety/wakapi/models"
v1 "github.com/muety/wakapi/models/compat/wakatime/v1" v1 "github.com/muety/wakapi/models/compat/wakatime/v1"
"github.com/muety/wakapi/services" "github.com/muety/wakapi/services"
@ -14,14 +14,14 @@ import (
) )
type SummariesHandler struct { type SummariesHandler struct {
summarySrvc *services.SummaryService config *conf.Config
config *config2.Config summarySrvc services.ISummaryService
} }
func NewSummariesHandler(summaryService *services.SummaryService) *SummariesHandler { func NewSummariesHandler(summaryService services.ISummaryService) *SummariesHandler {
return &SummariesHandler{ return &SummariesHandler{
summarySrvc: summaryService, summarySrvc: summaryService,
config: config2.Get(), config: conf.Get(),
} }
} }

View File

@ -14,11 +14,11 @@ import (
type HeartbeatHandler struct { type HeartbeatHandler struct {
config *conf.Config config *conf.Config
heartbeatSrvc *services.HeartbeatService heartbeatSrvc services.IHeartbeatService
languageMappingSrvc *services.LanguageMappingService languageMappingSrvc services.ILanguageMappingService
} }
func NewHeartbeatHandler(heartbeatService *services.HeartbeatService, languageMappingService *services.LanguageMappingService) *HeartbeatHandler { func NewHeartbeatHandler(heartbeatService services.IHeartbeatService, languageMappingService services.ILanguageMappingService) *HeartbeatHandler {
return &HeartbeatHandler{ return &HeartbeatHandler{
config: conf.Get(), config: conf.Get(),
heartbeatSrvc: heartbeatService, heartbeatSrvc: heartbeatService,

View File

@ -10,19 +10,18 @@ import (
"github.com/muety/wakapi/services" "github.com/muety/wakapi/services"
"github.com/muety/wakapi/utils" "github.com/muety/wakapi/utils"
"net/http" "net/http"
"net/url"
"time" "time"
) )
type HomeHandler struct { type HomeHandler struct {
config *conf.Config config *conf.Config
userSrvc *services.UserService userSrvc services.IUserService
} }
var loginDecoder = schema.NewDecoder() var loginDecoder = schema.NewDecoder()
var signupDecoder = schema.NewDecoder() var signupDecoder = schema.NewDecoder()
func NewHomeHandler(userService *services.UserService) *HomeHandler { func NewHomeHandler(userService services.IUserService) *HomeHandler {
return &HomeHandler{ return &HomeHandler{
config: conf.Get(), config: conf.Get(),
userSrvc: userService, 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 // 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) w.WriteHeader(http.StatusUnauthorized)
templates[conf.IndexTemplate].Execute(w, h.buildViewModel(r).WithError("invalid credentials")) templates[conf.IndexTemplate].Execute(w, h.buildViewModel(r).WithError("invalid credentials"))
return return
@ -161,8 +160,7 @@ func (h *HomeHandler) PostSignup(w http.ResponseWriter, r *http.Request) {
return return
} }
msg := url.QueryEscape("account created successfully") http.Redirect(w, r, fmt.Sprintf("%s/?success=%s", h.config.Server.BasePath, "account created successfully"), http.StatusFound)
http.Redirect(w, r, fmt.Sprintf("%s/?success=%s", h.config.Server.BasePath, msg), http.StatusFound)
} }
func (h *HomeHandler) buildViewModel(r *http.Request) *view.HomeViewModel { func (h *HomeHandler) buildViewModel(r *http.Request) *view.HomeViewModel {

View File

@ -10,10 +10,10 @@ import (
type ImprintHandler struct { type ImprintHandler struct {
config *conf.Config config *conf.Config
keyValueSrvc *services.KeyValueService keyValueSrvc services.IKeyValueService
} }
func NewImprintHandler(keyValueService *services.KeyValueService) *ImprintHandler { func NewImprintHandler(keyValueService services.IKeyValueService) *ImprintHandler {
return &ImprintHandler{ return &ImprintHandler{
config: conf.Get(), config: conf.Get(),
keyValueSrvc: keyValueService, keyValueSrvc: keyValueService,

View File

@ -10,21 +10,20 @@ import (
"github.com/muety/wakapi/utils" "github.com/muety/wakapi/utils"
"log" "log"
"net/http" "net/http"
"net/url"
"strconv" "strconv"
) )
type SettingsHandler struct { type SettingsHandler struct {
config *conf.Config config *conf.Config
userSrvc *services.UserService userSrvc services.IUserService
summarySrvc *services.SummaryService summarySrvc services.ISummaryService
aggregationSrvc *services.AggregationService aggregationSrvc services.IAggregationService
languageMappingSrvc *services.LanguageMappingService languageMappingSrvc services.ILanguageMappingService
} }
var credentialsDecoder = schema.NewDecoder() 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{ return &SettingsHandler{
config: conf.Get(), config: conf.Get(),
summarySrvc: summaryService, summarySrvc: summaryService,
@ -178,7 +177,7 @@ func (h *SettingsHandler) PostResetApiKey(w http.ResponseWriter, r *http.Request
return 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)) templates[conf.SettingsTemplate].Execute(w, h.buildViewModel(r).WithSuccess(msg))
} }

View File

@ -10,11 +10,11 @@ import (
) )
type SummaryHandler struct { type SummaryHandler struct {
summarySrvc *services.SummaryService
config *conf.Config config *conf.Config
summarySrvc services.ISummaryService
} }
func NewSummaryHandler(summaryService *services.SummaryService) *SummaryHandler { func NewSummaryHandler(summaryService services.ISummaryService) *SummaryHandler {
return &SummaryHandler{ return &SummaryHandler{
summarySrvc: summaryService, summarySrvc: summaryService,
config: conf.Get(), config: conf.Get(),

View File

@ -16,12 +16,12 @@ const (
type AggregationService struct { type AggregationService struct {
config *config.Config config *config.Config
userService *UserService userService IUserService
summaryService *SummaryService summaryService ISummaryService
heartbeatService *HeartbeatService heartbeatService IHeartbeatService
} }
func NewAggregationService(userService *UserService, summaryService *SummaryService, heartbeatService *HeartbeatService) *AggregationService { func NewAggregationService(userService IUserService, summaryService ISummaryService, heartbeatService IHeartbeatService) *AggregationService {
return &AggregationService{ return &AggregationService{
config: config.Get(), config: config.Get(),
userService: userService, userService: userService,

View File

@ -10,10 +10,10 @@ import (
type AliasService struct { type AliasService struct {
config *config.Config config *config.Config
repository *repositories.AliasRepository repository repositories.IAliasRepository
} }
func NewAliasService(aliasRepo *repositories.AliasRepository) *AliasService { func NewAliasService(aliasRepo repositories.IAliasRepository) *AliasService {
return &AliasService{ return &AliasService{
config: config.Get(), config: config.Get(),
repository: aliasRepo, repository: aliasRepo,

View File

@ -10,11 +10,11 @@ import (
type HeartbeatService struct { type HeartbeatService struct {
config *config.Config config *config.Config
repository *repositories.HeartbeatRepository repository repositories.IHeartbeatRepository
languageMappingSrvc *LanguageMappingService languageMappingSrvc ILanguageMappingService
} }
func NewHeartbeatService(heartbeatRepo *repositories.HeartbeatRepository, languageMappingService *LanguageMappingService) *HeartbeatService { func NewHeartbeatService(heartbeatRepo repositories.IHeartbeatRepository, languageMappingService ILanguageMappingService) *HeartbeatService {
return &HeartbeatService{ return &HeartbeatService{
config: config.Get(), config: config.Get(),
repository: heartbeatRepo, repository: heartbeatRepo,

View File

@ -8,10 +8,10 @@ import (
type KeyValueService struct { type KeyValueService struct {
config *config.Config config *config.Config
repository *repositories.KeyValueRepository repository repositories.IKeyValueRepository
} }
func NewKeyValueService(keyValueRepo *repositories.KeyValueRepository) *KeyValueService { func NewKeyValueService(keyValueRepo repositories.IKeyValueRepository) *KeyValueService {
return &KeyValueService{ return &KeyValueService{
config: config.Get(), config: config.Get(),
repository: keyValueRepo, repository: keyValueRepo,

View File

@ -10,11 +10,11 @@ import (
type LanguageMappingService struct { type LanguageMappingService struct {
config *config.Config config *config.Config
repository *repositories.LanguageMappingRepository
cache *cache.Cache cache *cache.Cache
repository repositories.ILanguageMappingRepository
} }
func NewLanguageMappingService(languageMappingsRepo *repositories.LanguageMappingRepository) *LanguageMappingService { func NewLanguageMappingService(languageMappingsRepo repositories.ILanguageMappingRepository) *LanguageMappingService {
return &LanguageMappingService{ return &LanguageMappingService{
config: config.Get(), config: config.Get(),
repository: languageMappingsRepo, repository: languageMappingsRepo,

58
services/services.go Normal file
View File

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

View File

@ -17,14 +17,14 @@ const HeartbeatDiffThreshold = 2 * time.Minute
type SummaryService struct { type SummaryService struct {
config *config.Config config *config.Config
cache *cache.Cache cache *cache.Cache
repository *repositories.SummaryRepository repository repositories.ISummaryRepository
heartbeatService *HeartbeatService heartbeatService IHeartbeatService
aliasService *AliasService aliasService IAliasService
} }
type SummaryRetriever func(f, t time.Time, u *models.User) (*models.Summary, error) 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{ return &SummaryService{
config: config.Get(), config: config.Get(),
cache: cache.New(24*time.Hour, 24*time.Hour), cache: cache.New(24*time.Hour, 24*time.Hour),

View File

@ -10,10 +10,10 @@ import (
type UserService struct { type UserService struct {
Config *config.Config Config *config.Config
repository *repositories.UserRepository repository repositories.IUserRepository
} }
func NewUserService(userRepo *repositories.UserRepository) *UserService { func NewUserService(userRepo repositories.IUserRepository) *UserService {
return &UserService{ return &UserService{
Config: config.Get(), Config: config.Get(),
repository: userRepo, repository: userRepo,