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
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)
|