Files
YouduWiki/backend/repo/pg/user_access.go
2026-05-21 19:52:45 +08:00

152 lines
3.5 KiB
Go

package pg
import (
"fmt"
"sync"
"time"
"gorm.io/gorm"
"github.com/chaitin/panda-wiki/consts"
"github.com/chaitin/panda-wiki/domain"
"github.com/chaitin/panda-wiki/log"
"github.com/chaitin/panda-wiki/store/pg"
)
type UserAccessRepository struct {
db *pg.DB
logger *log.Logger
accessMap sync.Map
}
func NewUserAccessRepository(db *pg.DB, logger *log.Logger) *UserAccessRepository {
repo := &UserAccessRepository{
db: db,
logger: logger.WithModule("repo.pg.user_access"),
accessMap: sync.Map{},
}
// start sync task
go repo.startSyncTask()
return repo
}
// UpdateAccessTime update user access time
func (r *UserAccessRepository) UpdateAccessTime(userID string) {
r.accessMap.Store(userID, time.Now())
}
// GetAccessTime get user access time
func (r *UserAccessRepository) GetAccessTime(userID string) (time.Time, bool) {
if value, ok := r.accessMap.Load(userID); ok {
return value.(time.Time), true
}
return time.Time{}, false
}
// startSyncTask start sync task
func (r *UserAccessRepository) startSyncTask() {
ticker := time.NewTicker(1 * time.Minute)
defer ticker.Stop()
for range ticker.C {
r.syncToDatabase()
}
}
// syncToDatabase sync data to database
func (r *UserAccessRepository) syncToDatabase() {
// collect data to update
updates := make([]domain.UserAccessTime, 0)
r.accessMap.Range(func(key, value any) bool {
userID := key.(string)
timestamp := value.(time.Time)
updates = append(updates, domain.UserAccessTime{
UserID: userID,
Timestamp: timestamp,
})
return true
})
if len(updates) == 0 {
return
}
// batch update database
err := r.db.Transaction(func(tx *gorm.DB) error {
for _, update := range updates {
if err := tx.Model(&domain.User{}).
Where("id = ?", update.UserID).
Update("last_access", update.Timestamp).Error; err != nil {
return err
}
}
return nil
})
if err != nil {
r.logger.Error("failed to sync user access time to database",
log.Error(err),
log.Int("update_count", len(updates)))
return
}
// clear synced data
for _, update := range updates {
if currentTime, ok := r.GetAccessTime(update.UserID); ok {
// only delete old data
if !currentTime.After(update.Timestamp) {
r.accessMap.Delete(update.UserID)
}
}
}
r.logger.Info("synced user access time to database",
log.Int("update_count", len(updates)))
}
func (r *UserAccessRepository) ValidateRole(userID string, role consts.UserRole) (bool, error) {
var user domain.User
if err := r.db.Model(&domain.User{}).Where("id = ?", userID).First(&user).Error; err != nil {
return false, fmt.Errorf("get user failed")
}
if user.Role == consts.UserRoleAdmin {
return true, nil
}
if user.Role == role {
return true, nil
}
return false, nil
}
func (r *UserAccessRepository) ValidateKBPerm(kbId, userId string, perm consts.UserKBPermission) (bool, error) {
var user domain.User
if err := r.db.Model(&domain.User{}).Where("id = ?", userId).First(&user).Error; err != nil {
return false, fmt.Errorf("get user failed %s", err)
}
if user.Role == consts.UserRoleAdmin {
return true, nil
}
var kbUser domain.KBUsers
err := r.db.Model(&domain.KBUsers{}).
Where("kb_id = ? AND user_id = ?", kbId, userId).
First(&kbUser).Error
if err != nil {
return false, fmt.Errorf("get kb user failed %s", err)
}
if perm == consts.UserKBPermissionNotNull {
return kbUser.Perm != consts.UserKBPermissionNull, nil
}
if kbUser.Perm == perm || kbUser.Perm == consts.UserKBPermissionFullControl {
return true, nil
}
return false, nil
}