All checks were successful
pedestrian-simulator / build (push) Successful in 53s
157 lines
3.6 KiB
Go
157 lines
3.6 KiB
Go
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
|
|
}
|