Skip to content

Refactor Auth Service: Replace map[string]interface{} with a Typed GitHubUser Struct #52

@Vedant1703

Description

@Vedant1703

📋 Description

In backend/auth-service/internal/github/oauth.go, the FetchUser() function returns map[string]interface{}:

func FetchUser(token string) (map[string]interface{}, error) {
    // ...
    var user map[string]interface{}
    json.Unmarshal(bodyBytes, &user)
    // ...
    return user, nil
}

This is a significant type-safety problem in Go:

  1. Every caller must perform repetitive, brittle type assertions like user["login"].(string) — if GitHub ever changes a field type, this panics at runtime (not compile time)
  2. The linter and compiler cannot catch typos in field names — user["loginn"] compiles fine but returns nil silently
  3. It's impossible to tell which GitHub API fields the code actually depends on without tracing through all callers
  4. There are multiple fmt.Printf("DEBUG: ...") debug statements still in production code that leak internal info to logs

The refactor requires introducing a clean GitHubUser struct, updating JSON unmarshaling, removing all debug fmt.Printf calls, updating callers to use the typed struct, and ensuring nothing breaks.


📍 Files to Change

  • backend/auth-service/internal/github/oauth.go — define GitHubUser, rewrite FetchUser
  • Any caller in backend/auth-service/ that uses the return value of FetchUser (find them with grep -r "FetchUser" backend/auth-service/)

🔍 Problems in the Current Code

// oauth.go - many issues in one function:

var user map[string]interface{}          // ❌ untyped map
json.Unmarshal(bodyBytes, &user)         // ❌ silently drops unmarshal errors

fmt.Printf("DEBUG: GitHub User Response: %s\n", string(bodyBytes))  // ❌ leaks raw API response to logs

if errMsg, ok := user["message"].(string); ok {    // ❌ type assertion, can panic
    fmt.Printf("DEBUG: GitHub API Error: %s\n", errMsg)  // ❌ debug print in production
}

if user["email"] == nil || user["email"] == "" {   // ❌ comparing interface{} to "" won't work
    // ...
}

✅ What To Do

Step 1 — Define a GitHubUser struct in a new file user.go:

// backend/auth-service/internal/github/user.go
package github

// GitHubUser represents the fields we use from the GitHub /user API response.
// Reference: https://docs.github.com/en/rest/users/users#get-the-authenticated-user
type GitHubUser struct {
    ID        int64  `json:"id"`
    Login     string `json:"login"`      // GitHub username
    Name      string `json:"name"`       // Display name (may be empty)
    Email     string `json:"email"`      // Public email (may be empty)
    AvatarURL string `json:"avatar_url"`
    HTMLURL   string `json:"html_url"`
    // Error fields (returned by GitHub on failure)
    Message   string `json:"message,omitempty"`
}

Step 2 — Rewrite FetchUser to use the struct:

func FetchUser(token string) (*GitHubUser, error) {
    req, err := http.NewRequest("GET", "https://api.github.com/user", nil)
    if err != nil {
        return nil, fmt.Errorf("creating request: %w", err)
    }
    req.Header.Set("Authorization", "token "+token)

    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return nil, fmt.Errorf("calling github API: %w", err)
    }
    defer resp.Body.Close()

    var user GitHubUser
    if err := json.NewDecoder(resp.Body).Decode(&user); err != nil {
        return nil, fmt.Errorf("decoding response: %w", err)
    }

    // GitHub returns a "message" field in the JSON body on error (e.g., bad token)
    if user.Message != "" {
        return nil, fmt.Errorf("github API error: %s", user.Message)
    }

    // Fetch primary email if not public
    if user.Email == "" {
        email, err := FetchUserEmail(token)
        if err != nil {
            log.Printf("FetchUser: could not fetch email for %s: %v", user.Login, err)
        } else {
            user.Email = email
        }
    }

    return &user, nil
}

Step 3 — Update all callers.

Run this to find them:

grep -rn "FetchUser" backend/auth-service/

Callers will be accessing map fields like user["id"], user["login"]. Replace each with struct field access: user.ID, user.Login, etc.

Step 4 — Remove all fmt.Printf("DEBUG: ...") lines from oauth.go.

Replace useful debug logging with proper log.Printf calls that don't dump raw API responses.


🏁 Acceptance Criteria

  • A GitHubUser struct is defined with at minimum: ID, Login, Name, Email, AvatarURL fields
  • FetchUser returns (*GitHubUser, error) — not (map[string]interface{}, error)
  • All callers of FetchUser across the auth-service are updated to use struct fields
  • All fmt.Printf("DEBUG: ...") lines are removed from oauth.go
  • JSON unmarshal errors are properly handled (not silently swallowed)
  • Auth service compiles: cd backend/auth-service && go build ./...
  • GitHub OAuth login still works end-to-end (test by logging in with your GitHub account)

💡 Technical Hints

  • The GitHub API returns an "id" field as a JSON number. In Go, unmarshal it into int64 (not int) since GitHub user IDs exceed the range of 32-bit integers
  • user["email"] == "" does NOT work when user is a map[string]interface{} — you're comparing interface{} to a string, which is always false. With the typed struct, user.Email == "" works correctly. This is one of the real bugs you're fixing
  • Check if the auth-service handler that calls FetchUser passes fields through to upsert/CreateOrGet in the core service — those field names need to match your new struct fields

🚀 Getting Started

  1. Fork the repository
  2. Create a branch: git checkout -b refactor/issue-31-typed-github-user
  3. Create backend/auth-service/internal/github/user.go with the new struct
  4. Rewrite FetchUser in oauth.go
  5. Find and update all callers: grep -rn "FetchUser" backend/auth-service/
  6. Remove DEBUG prints
  7. Run: cd backend/auth-service && go build ./...
  8. Do a full login flow test
  9. Open a PR!

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions