From 36e03c6dca7ff3ee6b8e3428d5a6631fc000a07e Mon Sep 17 00:00:00 2001 From: Bagas Aulia Rezki Date: Mon, 29 Apr 2024 11:09:19 +0700 Subject: [PATCH] Move user caching functionality to dedicated package --- cache/user.go | 86 ++++++++++++++++++++++++ db/model/user/user.go | 85 ----------------------- handler/forgotPassword/forgotPassword.go | 21 ++++-- handler/forgotPassword/verify/verify.go | 4 +- handler/signin/signin.go | 4 +- handler/signup/signup.go | 5 +- utils/utils.go | 2 +- 7 files changed, 109 insertions(+), 98 deletions(-) create mode 100644 cache/user.go delete mode 100644 db/model/user/user.go diff --git a/cache/user.go b/cache/user.go new file mode 100644 index 0000000..6d4b16f --- /dev/null +++ b/cache/user.go @@ -0,0 +1,86 @@ +package cache + +import ( + "fmt" + "github.com/fossyy/filekeeper/db" + "github.com/fossyy/filekeeper/logger" + "github.com/fossyy/filekeeper/utils" + "github.com/google/uuid" + "sync" + "time" +) + +type UserWithExpired struct { + UserID uuid.UUID + Username string + Email string + Password string + AccessAt time.Time +} + +type UserCache struct { + users map[string]*UserWithExpired + mu sync.Mutex +} + +var log *logger.AggregatedLogger +var userCache *UserCache + +func init() { + log = logger.Logger() + + userCache = &UserCache{users: make(map[string]*UserWithExpired)} + ticker := time.NewTicker(time.Hour * 8) + + go func() { + for { + <-ticker.C + currentTime := time.Now() + cacheClean := 0 + cleanID := utils.GenerateRandomString(10) + log.Info(fmt.Sprintf("Cache cleanup [user] [%s] initiated at %02d:%02d:%02d", cleanID, currentTime.Hour(), currentTime.Minute(), currentTime.Second())) + + userCache.mu.Lock() + for _, user := range userCache.users { + if currentTime.Sub(user.AccessAt) > time.Hour*8 { + DeleteUser(user.Email) + cacheClean++ + } + } + userCache.mu.Unlock() + + log.Info(fmt.Sprintf("Cache cleanup [user] [%s] completed: %d entries removed. Finished at %s", cleanID, cacheClean, time.Since(currentTime))) + } + }() +} + +func GetUser(email string) (*UserWithExpired, error) { + userCache.mu.Lock() + defer userCache.mu.Unlock() + + if user, ok := userCache.users[email]; ok { + return user, nil + } + + userData, err := db.DB.GetUser(email) + if err != nil { + return nil, err + } + + userCache.users[email] = &UserWithExpired{ + UserID: userData.UserID, + Username: userData.Username, + Email: userData.Email, + Password: userData.Password, + AccessAt: time.Now(), + } + + return userCache.users[email], nil +} + +func DeleteUser(email string) { + userCache.mu.Lock() + defer userCache.mu.Unlock() + + delete(userCache.users, email) +} diff --git a/db/model/user/user.go b/db/model/user/user.go deleted file mode 100644 index 16de14c..0000000 --- a/db/model/user/user.go +++ /dev/null @@ -1,85 +0,0 @@ -package user - -import ( - "fmt" - "sync" - "time" - - "github.com/fossyy/filekeeper/db" - "github.com/fossyy/filekeeper/logger" - "github.com/google/uuid" -) - -type Cache struct { - users map[string]*UserWithExpired - mu sync.Mutex -} - -type UserWithExpired struct { - UserID uuid.UUID - Username string - Email string - Password string - AccessAt time.Time -} - -var log *logger.AggregatedLogger -var UserCache *Cache - -func init() { - log = logger.Logger() - - UserCache = &Cache{users: make(map[string]*UserWithExpired)} - ticker := time.NewTicker(time.Hour * 8) - - go func() { - for { - <-ticker.C - currentTime := time.Now() - cacheClean := 0 - log.Info(fmt.Sprintf("Cache cleanup initiated at %02d:%02d:%02d", currentTime.Hour(), currentTime.Minute(), currentTime.Second())) - - UserCache.mu.Lock() - for _, user := range UserCache.users { - if currentTime.Sub(user.AccessAt) > time.Hour*8 { - delete(UserCache.users, user.Email) - cacheClean++ - } - } - UserCache.mu.Unlock() - - log.Info(fmt.Sprintf("Cache cleanup completed: %d entries removed. Finished at %s", cacheClean, time.Since(currentTime))) - } - }() -} - -func Get(email string) (*UserWithExpired, error) { - UserCache.mu.Lock() - defer UserCache.mu.Unlock() - - if user, ok := UserCache.users[email]; ok { - return user, nil - } - - userData, err := db.DB.GetUser(email) - if err != nil { - return nil, err - } - - UserCache.users[email] = &UserWithExpired{ - UserID: userData.UserID, - Username: userData.Username, - Email: userData.Email, - Password: userData.Password, - AccessAt: time.Now(), - } - - return UserCache.users[email], nil -} - -func DeleteCache(email string) { - UserCache.mu.Lock() - defer UserCache.mu.Unlock() - - delete(UserCache.users, email) -} diff --git a/handler/forgotPassword/forgotPassword.go b/handler/forgotPassword/forgotPassword.go index d93aadf..edc3edd 100644 --- a/handler/forgotPassword/forgotPassword.go +++ b/handler/forgotPassword/forgotPassword.go @@ -5,12 +5,13 @@ import ( "context" "errors" "fmt" + "github.com/fossyy/filekeeper/cache" + "github.com/google/uuid" "net/http" "strconv" "sync" "time" - "github.com/fossyy/filekeeper/db" "github.com/fossyy/filekeeper/email" "github.com/fossyy/filekeeper/logger" "github.com/fossyy/filekeeper/types" @@ -45,11 +46,12 @@ func init() { <-ticker.C currentTime := time.Now() cacheClean := 0 - log.Info(fmt.Sprintf("Cache cleanup initiated at %02d:%02d:%02d", currentTime.Hour(), currentTime.Minute(), currentTime.Second())) + cleanID := utils.GenerateRandomString(10) + log.Info(fmt.Sprintf("Cache cleanup [Forgot Password] [%s] initiated at %02d:%02d:%02d", cleanID, currentTime.Hour(), currentTime.Minute(), currentTime.Second())) for _, data := range ListForgotPassword { data.mu.Lock() - if currentTime.Sub(data.CreateTime) > time.Minute*1 { + if currentTime.Sub(data.CreateTime) > time.Minute*10 { delete(ListForgotPassword, data.User.Email) delete(UserForgotPassword, data.Code) cacheClean++ @@ -57,7 +59,7 @@ func init() { data.mu.Unlock() } - log.Info(fmt.Sprintf("Cache cleanup completed: %d entries removed. Finished at %s", cacheClean, time.Since(currentTime))) + log.Info(fmt.Sprintf("Cache cleanup [Forgot Password] [%s] completed: %d entries removed. Finished at %s", cleanID, cacheClean, time.Since(currentTime))) } }() } @@ -85,7 +87,7 @@ func POST(w http.ResponseWriter, r *http.Request) { emailForm := r.Form.Get("email") - user, err := db.DB.GetUser(emailForm) + user, err := cache.GetUser(emailForm) if errors.Is(err, gorm.ErrRecordNotFound) { component := forgotPasswordView.Main(fmt.Sprintf("Account with this email address %s is not found", emailForm), types.Message{ Code: 0, @@ -100,7 +102,14 @@ func POST(w http.ResponseWriter, r *http.Request) { return } - err = verifyForgot(user) + userData := &models.User{ + UserID: uuid.UUID{}, + Username: user.Username, + Email: user.Email, + Password: "", + } + + err = verifyForgot(userData) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Error(err.Error()) diff --git a/handler/forgotPassword/verify/verify.go b/handler/forgotPassword/verify/verify.go index 22a682d..128185e 100644 --- a/handler/forgotPassword/verify/verify.go +++ b/handler/forgotPassword/verify/verify.go @@ -1,8 +1,8 @@ package forgotPasswordVerifyHandler import ( + "github.com/fossyy/filekeeper/cache" "github.com/fossyy/filekeeper/db" - "github.com/fossyy/filekeeper/db/model/user" forgotPasswordHandler "github.com/fossyy/filekeeper/handler/forgotPassword" "github.com/fossyy/filekeeper/logger" "github.com/fossyy/filekeeper/session" @@ -98,7 +98,7 @@ func POST(w http.ResponseWriter, r *http.Request) { session.RemoveAllSessions(data.User.Email) - user.DeleteCache(data.User.Email) + cache.DeleteUser(data.User.Email) component := forgotPasswordView.ChangeSuccess("Forgot Password Page") err = component.Render(r.Context(), w) diff --git a/handler/signin/signin.go b/handler/signin/signin.go index ae932f0..9a090a3 100644 --- a/handler/signin/signin.go +++ b/handler/signin/signin.go @@ -2,10 +2,10 @@ package signinHandler import ( "errors" + "github.com/fossyy/filekeeper/cache" "net/http" "strings" - "github.com/fossyy/filekeeper/db/model/user" "github.com/fossyy/filekeeper/logger" "github.com/fossyy/filekeeper/session" "github.com/fossyy/filekeeper/types" @@ -41,7 +41,7 @@ func POST(w http.ResponseWriter, r *http.Request) { } email := r.Form.Get("email") password := r.Form.Get("password") - userData, err := user.Get(email) + userData, err := cache.GetUser(email) if err != nil { component := signinView.Main("Sign in Page", types.Message{ Code: 0, diff --git a/handler/signup/signup.go b/handler/signup/signup.go index 31375fb..804a2a5 100644 --- a/handler/signup/signup.go +++ b/handler/signup/signup.go @@ -45,7 +45,8 @@ func init() { <-ticker.C currentTime := time.Now() cacheClean := 0 - log.Info(fmt.Sprintf("Cache cleanup initiated at %02d:%02d:%02d", currentTime.Hour(), currentTime.Minute(), currentTime.Second())) + cleanID := utils.GenerateRandomString(10) + log.Info(fmt.Sprintf("Cache cleanup [signup] [%s] initiated at %02d:%02d:%02d", cleanID, currentTime.Hour(), currentTime.Minute(), currentTime.Second())) for _, data := range VerifyUser { data.mu.Lock() @@ -57,7 +58,7 @@ func init() { data.mu.Unlock() } - log.Info(fmt.Sprintf("Cache cleanup completed: %d entries removed. Finished at %s", cacheClean, time.Since(currentTime))) + log.Info(fmt.Sprintf("Cache cleanup [signup] [%s] completed: %d entries removed. Finished at %s", cleanID, cacheClean, time.Since(currentTime))) } }() } diff --git a/utils/utils.go b/utils/utils.go index fdd905b..f0b968b 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -125,7 +125,7 @@ func Getenv(key string) string { func GenerateRandomString(length int) string { const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" - seededRand := rand.New(rand.NewSource(time.Now().UnixNano())) + seededRand := rand.New(rand.NewSource(time.Now().UnixNano() + int64(rand.Intn(9999)))) var result strings.Builder for i := 0; i < length; i++ { randomIndex := seededRand.Intn(len(charset))