package main import ( "database/sql" "encoding/json" "fmt" "net/http" "os" "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"` } // GetUser retrieves a user by Fitbit user ID from the database func GetUser(fitbitUserID string) (*User, error) { var user User err := db.QueryRow("SELECT fitbit_user_id, display_name, avatar_url, created_at FROM users WHERE fitbit_user_id = ?", fitbitUserID). Scan(&user.FitbitUserID, &user.DisplayName, &user.AvatarURL, &user.CreatedAt) if err != nil { if err == sql.ErrNoRows { return nil, nil } return nil, err } return &user, nil } // GetPublicUser retrieves public user info by Fitbit user ID func GetPublicUser(fitbitUserID string) (*User, error) { var user User err := db.QueryRow("SELECT fitbit_user_id, display_name, avatar_url, created_at FROM users WHERE fitbit_user_id = ?", fitbitUserID). Scan(&user.FitbitUserID, &user.DisplayName, &user.AvatarURL, &user.CreatedAt) if err != nil { if err == sql.ErrNoRows { return nil, nil } return nil, err } // Ensure we don't return sensitive info if struct expands later, though current struct is safe for public return &user, nil } // CreateOrUpdateUser adds or updates a user in the database func CreateOrUpdateUser(fitbitUserID, displayName, avatarURL string) (*User, error) { _, err := db.Exec(` INSERT INTO users (fitbit_user_id, display_name, avatar_url, created_at) VALUES (?, ?, ?, NOW()) ON DUPLICATE KEY UPDATE display_name = ?, avatar_url = ? `, fitbitUserID, displayName, avatarURL, displayName, avatarURL) if err != nil { return nil, err } // Create user directory for KML files userDir := fmt.Sprintf("data/users/%s/kml", fitbitUserID) if err := os.MkdirAll(userDir, 0755); err != nil { return nil, err } return GetUser(fitbitUserID) } // 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 }