You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

106 lines
3.0 KiB
Go

package usersvc
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"time"
app "knowfoolery/backend/services/game-session-service/internal/application/session"
sharederrors "knowfoolery/backend/shared/domain/errors"
)
// Client implements User Service HTTP integration.
type Client struct {
baseURL string
httpClient *http.Client
}
// NewClient creates a user-service client.
func NewClient(baseURL string, timeout time.Duration) *Client {
if timeout <= 0 {
timeout = 3 * time.Second
}
return &Client{
baseURL: strings.TrimRight(baseURL, "/"),
httpClient: &http.Client{
Timeout: timeout,
},
}
}
type responseEnvelope[T any] struct {
Success bool `json:"success"`
Data T `json:"data"`
}
type errorEnvelope struct {
Error bool `json:"error"`
Code string `json:"code"`
Message string `json:"message"`
Details string `json:"details"`
}
type userPayload struct {
ID string `json:"id"`
DisplayName string `json:"display_name"`
EmailVerified bool `json:"email_verified"`
}
// GetUserProfile fetches a user profile and validates required fields.
func (c *Client) GetUserProfile(
ctx context.Context,
userID, bearerToken string,
) (*app.UserProfile, error) {
path := fmt.Sprintf("/users/%s", strings.TrimSpace(userID))
req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.baseURL+path, nil)
if err != nil {
return nil, sharederrors.Wrap(sharederrors.CodeInternal, "build upstream request", err)
}
if token := strings.TrimSpace(bearerToken); token != "" {
req.Header.Set("Authorization", "Bearer "+token)
}
resp, err := c.httpClient.Do(req)
if err != nil {
return nil, sharederrors.Wrap(sharederrors.CodeInternal, "user-service request failed", err)
}
defer func() { _ = resp.Body.Close() }()
raw, err := io.ReadAll(resp.Body)
if err != nil {
return nil, sharederrors.Wrap(sharederrors.CodeInternal, "read user-service response", err)
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
var e errorEnvelope
if jsonErr := json.Unmarshal(raw, &e); jsonErr == nil && strings.TrimSpace(e.Code) != "" {
return nil, sharederrors.New(sharederrors.ErrorCode(e.Code), e.Message)
}
if resp.StatusCode == http.StatusNotFound {
return nil, sharederrors.New(sharederrors.CodeUserNotFound, "user not found")
}
if resp.StatusCode == http.StatusUnauthorized {
return nil, sharederrors.New(sharederrors.CodeUnauthorized, "unauthorized")
}
if resp.StatusCode == http.StatusForbidden {
return nil, sharederrors.New(sharederrors.CodeForbidden, "forbidden")
}
return nil, sharederrors.New(sharederrors.CodeInternal, "user-service upstream error")
}
var out responseEnvelope[userPayload]
if err := json.Unmarshal(raw, &out); err != nil {
return nil, sharederrors.Wrap(sharederrors.CodeInternal, "decode user-service response", err)
}
return &app.UserProfile{
ID: out.Data.ID,
DisplayName: out.Data.DisplayName,
EmailVerified: out.Data.EmailVerified,
}, nil
}
var _ app.UserClient = (*Client)(nil)