📋 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:
- 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)
- The linter and compiler cannot catch typos in field names —
user["loginn"] compiles fine but returns nil silently
- It's impossible to tell which GitHub API fields the code actually depends on without tracing through all callers
- 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
💡 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
- Fork the repository
- Create a branch:
git checkout -b refactor/issue-31-typed-github-user
- Create
backend/auth-service/internal/github/user.go with the new struct
- Rewrite
FetchUser in oauth.go
- Find and update all callers:
grep -rn "FetchUser" backend/auth-service/
- Remove DEBUG prints
- Run:
cd backend/auth-service && go build ./...
- Do a full login flow test
- Open a PR!
📋 Description
In
backend/auth-service/internal/github/oauth.go, theFetchUser()function returnsmap[string]interface{}:This is a significant type-safety problem in Go:
user["login"].(string)— if GitHub ever changes a field type, this panics at runtime (not compile time)user["loginn"]compiles fine but returnsnilsilentlyfmt.Printf("DEBUG: ...")debug statements still in production code that leak internal info to logsThe refactor requires introducing a clean
GitHubUserstruct, updating JSON unmarshaling, removing all debugfmt.Printfcalls, updating callers to use the typed struct, and ensuring nothing breaks.📍 Files to Change
backend/auth-service/internal/github/oauth.go— defineGitHubUser, rewriteFetchUserbackend/auth-service/that uses the return value ofFetchUser(find them withgrep -r "FetchUser" backend/auth-service/)🔍 Problems in the Current Code
✅ What To Do
Step 1 — Define a
GitHubUserstruct in a new fileuser.go:Step 2 — Rewrite
FetchUserto use the struct: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 fromoauth.go.Replace useful debug logging with proper
log.Printfcalls that don't dump raw API responses.🏁 Acceptance Criteria
GitHubUserstruct is defined with at minimum:ID,Login,Name,Email,AvatarURLfieldsFetchUserreturns(*GitHubUser, error)— not(map[string]interface{}, error)FetchUseracross the auth-service are updated to use struct fieldsfmt.Printf("DEBUG: ...")lines are removed fromoauth.gocd backend/auth-service && go build ./...💡 Technical Hints
"id"field as a JSON number. In Go, unmarshal it intoint64(notint) since GitHub user IDs exceed the range of 32-bit integersuser["email"] == ""does NOT work whenuseris amap[string]interface{}— you're comparinginterface{}to a string, which is alwaysfalse. With the typed struct,user.Email == ""works correctly. This is one of the real bugs you're fixingFetchUserpasses fields through toupsert/CreateOrGetin the core service — those field names need to match your new struct fields🚀 Getting Started
git checkout -b refactor/issue-31-typed-github-userbackend/auth-service/internal/github/user.gowith the new structFetchUserinoauth.gogrep -rn "FetchUser" backend/auth-service/cd backend/auth-service && go build ./...