package users_service import ( "log" "sync" "time" "github.com/casdoor/casdoor-go-sdk/casdoorsdk" ) type user struct { DisplayName string Verified bool } // Cache atomically replaces snapshots and throttles refreshes, including misses. // Failed authentication lookups preserve the last usable display-name snapshot. type Cache struct { mu sync.RWMutex refresh sync.Mutex users map[string]user updated time.Time enabled bool } func New(enabled bool) *Cache { return &Cache{users: make(map[string]user), enabled: enabled} } func (c *Cache) Refresh() { if !c.enabled { return } c.refresh.Lock() defer c.refresh.Unlock() c.mu.RLock() recent := time.Since(c.updated) < time.Minute c.mu.RUnlock() if recent { return } users, err := casdoorsdk.GetUsers() c.mu.Lock() defer c.mu.Unlock() c.updated = time.Now() if err != nil { log.Printf("[W] cannot refresh author cache: %v", err) return } snapshot := make(map[string]user, len(users)) for _, u := range users { name := u.DisplayName if name == "" { name = u.Name } snapshot[u.Id] = user{DisplayName: name, Verified: u.IsAdmin || u.Tag == "verified"} } c.users = snapshot } func (c *Cache) lookup(id string) (user, bool) { c.mu.RLock() stale := time.Since(c.updated) >= time.Minute c.mu.RUnlock() if stale { c.Refresh() } c.mu.RLock() defer c.mu.RUnlock() u, ok := c.users[id] return u, ok } func (c *Cache) ResolveAuthor(id, fallback string) string { if u, ok := c.lookup(id); ok && u.DisplayName != "" { return u.DisplayName } return fallback } func (c *Cache) IsVerified(id string) bool { u, ok := c.lookup(id); return ok && u.Verified }