This commit is contained in:
156
server/user.go
Normal file
156
server/user.go
Normal file
@@ -0,0 +1,156 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
FitbitUserID string `json:"fitbit_user_id"`
|
||||
DisplayName string `json:"display_name"`
|
||||
AvatarURL string `json:"avatar_url"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
type UserRegistry struct {
|
||||
Users map[string]*User `json:"users"` // Map of FitbitUserID -> User
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
var userRegistry *UserRegistry
|
||||
|
||||
// InitUserRegistry loads or creates the user registry
|
||||
func InitUserRegistry() {
|
||||
userRegistry = &UserRegistry{
|
||||
Users: make(map[string]*User),
|
||||
}
|
||||
userRegistry.Load()
|
||||
}
|
||||
|
||||
// Load reads the user registry from disk
|
||||
func (ur *UserRegistry) Load() error {
|
||||
ur.mu.Lock()
|
||||
defer ur.mu.Unlock()
|
||||
|
||||
data, err := os.ReadFile("data/users.json")
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil // First run, no users yet
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return json.Unmarshal(data, ur)
|
||||
}
|
||||
|
||||
// Save writes the user registry to disk
|
||||
func (ur *UserRegistry) Save() error {
|
||||
ur.mu.RLock()
|
||||
defer ur.mu.RUnlock()
|
||||
return ur.saveUnlocked()
|
||||
}
|
||||
|
||||
// saveUnlocked writes the user registry to disk without locking (caller must hold lock)
|
||||
func (ur *UserRegistry) saveUnlocked() error {
|
||||
if err := os.MkdirAll("data", 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data, err := json.MarshalIndent(ur, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.WriteFile("data/users.json", data, 0644)
|
||||
}
|
||||
|
||||
// GetUser retrieves a user by Fitbit user ID
|
||||
func (ur *UserRegistry) GetUser(fitbitUserID string) (*User, bool) {
|
||||
ur.mu.RLock()
|
||||
defer ur.mu.RUnlock()
|
||||
|
||||
user, exists := ur.Users[fitbitUserID]
|
||||
return user, exists
|
||||
}
|
||||
|
||||
// CreateOrUpdateUser adds or updates a user in the registry
|
||||
func (ur *UserRegistry) CreateOrUpdateUser(fitbitUserID, displayName, avatarURL string) (*User, error) {
|
||||
ur.mu.Lock()
|
||||
defer ur.mu.Unlock()
|
||||
|
||||
user, exists := ur.Users[fitbitUserID]
|
||||
if exists {
|
||||
// Update existing user
|
||||
user.DisplayName = displayName
|
||||
user.AvatarURL = avatarURL
|
||||
} else {
|
||||
// Create new user
|
||||
user = &User{
|
||||
FitbitUserID: fitbitUserID,
|
||||
DisplayName: displayName,
|
||||
AvatarURL: avatarURL,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
ur.Users[fitbitUserID] = user
|
||||
}
|
||||
|
||||
// Save without locking (we already have the lock)
|
||||
if err := ur.saveUnlocked(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create user directory
|
||||
userDir := fmt.Sprintf("data/users/%s", fitbitUserID)
|
||||
if err := os.MkdirAll(userDir, 0755); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
kmlDir := fmt.Sprintf("data/users/%s/kml", fitbitUserID)
|
||||
if err := os.MkdirAll(kmlDir, 0755); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// FetchFitbitUserProfile fetches the user's profile from Fitbit API
|
||||
func FetchFitbitUserProfile(accessToken string) (userID, displayName, avatarURL string, err error) {
|
||||
apiURL := "https://api.fitbit.com/1/user/-/profile.json"
|
||||
|
||||
req, _ := http.NewRequest("GET", apiURL, nil)
|
||||
req.Header.Set("Authorization", "Bearer "+accessToken)
|
||||
|
||||
client := &http.Client{Timeout: 10 * time.Second}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return "", "", "", fmt.Errorf("fitbit profile api error: %s", resp.Status)
|
||||
}
|
||||
|
||||
var result struct {
|
||||
User struct {
|
||||
EncodedID string `json:"encodedId"`
|
||||
DisplayName string `json:"displayName"`
|
||||
Avatar string `json:"avatar"`
|
||||
Avatar150 string `json:"avatar150"`
|
||||
} `json:"user"`
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
|
||||
avatar := result.User.Avatar150
|
||||
if avatar == "" {
|
||||
avatar = result.User.Avatar
|
||||
}
|
||||
|
||||
return result.User.EncodedID, result.User.DisplayName, avatar, nil
|
||||
}
|
||||
Reference in New Issue
Block a user