iiitl / iiitl/Opensource_Compass

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

Open
#52 3 comments 0 reactions 0 assignees View on GitHub
backend hard
Dominant language
TypeScript
Stars
0
Forks
16
PR merge metrics
No merged PRs in 30d

Description

### ๐Ÿ“‹ Description

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

```go
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

```go
// 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`:**

```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:**

```go
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:
```bash
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!

Contributor guide

Open the contributing guide

Research direction

Start with backend/auth-service/internal/github/oauth.go and add the GitHubUser definition in backend/auth-service/internal/github/user.go, then use grep -rn "FetchUser" backend/auth-service/ to locate callers. Run cd backend/auth-service && go build ./... and verify the acceptance criteria: typed fields, handled JSON errors, removed DEBUG prints, and a working GitHub OAuth login flow.

Written by the indexing model from the issue text.

Assessment

Tech stack
github, go
Domain
api, authentication, backend
Issue type
Refactor
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
58/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.