mirror of
https://github.com/truewhile/MeBox.git
synced 2026-09-28 11:16:37 +08:00
b0fe40142a
* Rebrand MMTL to MeBox across codebase and assets Rename the project display name, Go module path, environment variable prefix (MEBOX_*), Docker image references, and UI branding from MMTL/mmtl to MeBox/mebox. Replace logo assets with the new MeBox icon and keep legacy SQLite migration support for existing mmtl.db deployments. Co-authored-by: truewhile <truewhile@users.noreply.github.com> * Fix logo icons: use cube-only crop without truncated text Previous icon generation cropped too much of the source image, including partial MeBox wordmark text that was cut off in square icon containers. Regenerate logo-64/192/512, favicon, and SVG from cube-only region. Co-authored-by: truewhile <truewhile@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: truewhile <truewhile@users.noreply.github.com>
85 lines
2.3 KiB
Go
85 lines
2.3 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/truewhile/MeBox/internal/model"
|
|
)
|
|
|
|
func (s *SessionTrackerService) ApplyToUsers(ctx context.Context, users []model.User) {
|
|
if s == nil || len(users) == 0 {
|
|
return
|
|
}
|
|
activity := s.activityByUser(ctx)
|
|
for i := range users {
|
|
a, ok := activity[users[i].ID]
|
|
if !ok {
|
|
continue
|
|
}
|
|
if a.LastActivityAt != nil && (users[i].LastLoginAt == nil || a.LastActivityAt.After(*users[i].LastLoginAt)) {
|
|
t := *a.LastActivityAt
|
|
users[i].LastLoginAt = &t
|
|
}
|
|
users[i].RealtimeOnline = a.Online
|
|
users[i].RealtimeDeviceCount = a.ActiveDeviceCount
|
|
}
|
|
}
|
|
|
|
func (s *SessionTrackerService) UserRecentlyActive(ctx context.Context, userID string, within time.Duration) bool {
|
|
if s == nil || within <= 0 {
|
|
return false
|
|
}
|
|
activity := s.activityByUser(ctx)[strings.TrimSpace(userID)]
|
|
return activity.LastActivityAt != nil && activity.LastActivityAt.After(s.now().Add(-within))
|
|
}
|
|
|
|
func (s *SessionTrackerService) activityByUser(ctx context.Context) map[string]userRealtimeActivity {
|
|
sessions := s.List(ctx)
|
|
now := s.now()
|
|
out := make(map[string]userRealtimeActivity)
|
|
for userID, lastActivity := range s.userActivitySnapshot() {
|
|
if strings.TrimSpace(userID) == "" {
|
|
continue
|
|
}
|
|
a := out[userID]
|
|
if a.LastActivityAt == nil || lastActivity.After(*a.LastActivityAt) {
|
|
t := lastActivity
|
|
a.LastActivityAt = &t
|
|
}
|
|
out[userID] = a
|
|
}
|
|
seenDevices := make(map[string]map[string]struct{})
|
|
for _, sess := range sessions {
|
|
if strings.TrimSpace(sess.UserID) == "" {
|
|
continue
|
|
}
|
|
a := out[sess.UserID]
|
|
if a.LastActivityAt == nil || sess.LastActivityAt.After(*a.LastActivityAt) {
|
|
t := sess.LastActivityAt
|
|
a.LastActivityAt = &t
|
|
}
|
|
if sess.LastActivityAt.After(now.Add(-realtimeSessionOnlineTTL)) {
|
|
a.Online = true
|
|
}
|
|
if seenDevices[sess.UserID] == nil {
|
|
seenDevices[sess.UserID] = map[string]struct{}{}
|
|
}
|
|
seenDevices[sess.UserID][sessionDeviceKey(sess)] = struct{}{}
|
|
a.ActiveDeviceCount = len(seenDevices[sess.UserID])
|
|
out[sess.UserID] = a
|
|
}
|
|
return out
|
|
}
|
|
|
|
func (s *SessionTrackerService) userActivitySnapshot() map[string]time.Time {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
out := make(map[string]time.Time, len(s.activity))
|
|
for userID, lastActivity := range s.activity {
|
|
out[userID] = lastActivity
|
|
}
|
|
return out
|
|
}
|