docs: reorder & testing
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"galaxy/gateway/internal/downstream"
|
||||
@@ -59,6 +60,22 @@ func (c *RESTClient) ExecuteUserCommand(ctx context.Context, command downstream.
|
||||
return downstream.UnaryResult{}, fmt.Errorf("backendclient: execute user command %q: %w", command.MessageType, err)
|
||||
}
|
||||
return c.executeUserAccountUpdateSettings(ctx, command.UserID, req)
|
||||
case usermodel.MessageTypeListMySessions:
|
||||
if _, err := transcoder.PayloadToListMySessionsRequest(command.PayloadBytes); err != nil {
|
||||
return downstream.UnaryResult{}, fmt.Errorf("backendclient: execute user command %q: %w", command.MessageType, err)
|
||||
}
|
||||
return c.executeUserSessionsList(ctx, command.UserID)
|
||||
case usermodel.MessageTypeRevokeMySession:
|
||||
req, err := transcoder.PayloadToRevokeMySessionRequest(command.PayloadBytes)
|
||||
if err != nil {
|
||||
return downstream.UnaryResult{}, fmt.Errorf("backendclient: execute user command %q: %w", command.MessageType, err)
|
||||
}
|
||||
return c.executeUserSessionsRevoke(ctx, command.UserID, req)
|
||||
case usermodel.MessageTypeRevokeAllMySessions:
|
||||
if _, err := transcoder.PayloadToRevokeAllMySessionsRequest(command.PayloadBytes); err != nil {
|
||||
return downstream.UnaryResult{}, fmt.Errorf("backendclient: execute user command %q: %w", command.MessageType, err)
|
||||
}
|
||||
return c.executeUserSessionsRevokeAll(ctx, command.UserID)
|
||||
default:
|
||||
return downstream.UnaryResult{}, fmt.Errorf("backendclient: execute user command: unsupported message type %q", command.MessageType)
|
||||
}
|
||||
@@ -88,6 +105,124 @@ func (c *RESTClient) executeUserAccountUpdateSettings(ctx context.Context, userI
|
||||
return projectUserResponse(status, body)
|
||||
}
|
||||
|
||||
func (c *RESTClient) executeUserSessionsList(ctx context.Context, userID string) (downstream.UnaryResult, error) {
|
||||
body, status, err := c.do(ctx, http.MethodGet, c.baseURL+"/api/v1/user/sessions", userID, nil)
|
||||
if err != nil {
|
||||
return downstream.UnaryResult{}, fmt.Errorf("execute user.sessions.list: %w", err)
|
||||
}
|
||||
return projectUserSessionsListResponse(status, body)
|
||||
}
|
||||
|
||||
func (c *RESTClient) executeUserSessionsRevoke(ctx context.Context, userID string, req *usermodel.RevokeMySessionRequest) (downstream.UnaryResult, error) {
|
||||
if strings.TrimSpace(req.DeviceSessionID) == "" {
|
||||
return downstream.UnaryResult{}, errors.New("execute user.sessions.revoke: device_session_id must not be empty")
|
||||
}
|
||||
target := c.baseURL + "/api/v1/user/sessions/" + url.PathEscape(req.DeviceSessionID) + "/revoke"
|
||||
body, status, err := c.do(ctx, http.MethodPost, target, userID, nil)
|
||||
if err != nil {
|
||||
return downstream.UnaryResult{}, fmt.Errorf("execute user.sessions.revoke: %w", err)
|
||||
}
|
||||
return projectUserSessionRevokeResponse(status, body)
|
||||
}
|
||||
|
||||
func (c *RESTClient) executeUserSessionsRevokeAll(ctx context.Context, userID string) (downstream.UnaryResult, error) {
|
||||
body, status, err := c.do(ctx, http.MethodPost, c.baseURL+"/api/v1/user/sessions/revoke-all", userID, nil)
|
||||
if err != nil {
|
||||
return downstream.UnaryResult{}, fmt.Errorf("execute user.sessions.revoke_all: %w", err)
|
||||
}
|
||||
return projectUserSessionsRevokeAllResponse(status, body)
|
||||
}
|
||||
|
||||
func projectUserSessionsListResponse(statusCode int, payload []byte) (downstream.UnaryResult, error) {
|
||||
switch {
|
||||
case statusCode == http.StatusOK:
|
||||
var response usermodel.ListMySessionsResponse
|
||||
if err := decodeStrictJSON(payload, &response); err != nil {
|
||||
return downstream.UnaryResult{}, fmt.Errorf("decode success response: %w", err)
|
||||
}
|
||||
payloadBytes, err := transcoder.ListMySessionsResponseToPayload(&response)
|
||||
if err != nil {
|
||||
return downstream.UnaryResult{}, fmt.Errorf("encode success response payload: %w", err)
|
||||
}
|
||||
return downstream.UnaryResult{
|
||||
ResultCode: userCommandResultCodeOK,
|
||||
PayloadBytes: payloadBytes,
|
||||
}, nil
|
||||
case statusCode == http.StatusServiceUnavailable:
|
||||
return downstream.UnaryResult{}, downstream.ErrDownstreamUnavailable
|
||||
case statusCode >= 400 && statusCode <= 599:
|
||||
return projectUserBackendError(statusCode, payload)
|
||||
default:
|
||||
return downstream.UnaryResult{}, fmt.Errorf("unexpected HTTP status %d", statusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func projectUserSessionRevokeResponse(statusCode int, payload []byte) (downstream.UnaryResult, error) {
|
||||
switch {
|
||||
case statusCode == http.StatusOK:
|
||||
var session usermodel.DeviceSession
|
||||
if err := decodeStrictJSON(payload, &session); err != nil {
|
||||
return downstream.UnaryResult{}, fmt.Errorf("decode success response: %w", err)
|
||||
}
|
||||
payloadBytes, err := transcoder.RevokeMySessionResponseToPayload(&usermodel.RevokeMySessionResponse{Session: session})
|
||||
if err != nil {
|
||||
return downstream.UnaryResult{}, fmt.Errorf("encode success response payload: %w", err)
|
||||
}
|
||||
return downstream.UnaryResult{
|
||||
ResultCode: userCommandResultCodeOK,
|
||||
PayloadBytes: payloadBytes,
|
||||
}, nil
|
||||
case statusCode == http.StatusServiceUnavailable:
|
||||
return downstream.UnaryResult{}, downstream.ErrDownstreamUnavailable
|
||||
case statusCode >= 400 && statusCode <= 599:
|
||||
return projectUserBackendError(statusCode, payload)
|
||||
default:
|
||||
return downstream.UnaryResult{}, fmt.Errorf("unexpected HTTP status %d", statusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func projectUserSessionsRevokeAllResponse(statusCode int, payload []byte) (downstream.UnaryResult, error) {
|
||||
switch {
|
||||
case statusCode == http.StatusOK:
|
||||
var summary usermodel.DeviceSessionRevocationSummary
|
||||
if err := decodeStrictJSON(payload, &summary); err != nil {
|
||||
return downstream.UnaryResult{}, fmt.Errorf("decode success response: %w", err)
|
||||
}
|
||||
payloadBytes, err := transcoder.RevokeAllMySessionsResponseToPayload(&usermodel.RevokeAllMySessionsResponse{Summary: summary})
|
||||
if err != nil {
|
||||
return downstream.UnaryResult{}, fmt.Errorf("encode success response payload: %w", err)
|
||||
}
|
||||
return downstream.UnaryResult{
|
||||
ResultCode: userCommandResultCodeOK,
|
||||
PayloadBytes: payloadBytes,
|
||||
}, nil
|
||||
case statusCode == http.StatusServiceUnavailable:
|
||||
return downstream.UnaryResult{}, downstream.ErrDownstreamUnavailable
|
||||
case statusCode >= 400 && statusCode <= 599:
|
||||
return projectUserBackendError(statusCode, payload)
|
||||
default:
|
||||
return downstream.UnaryResult{}, fmt.Errorf("unexpected HTTP status %d", statusCode)
|
||||
}
|
||||
}
|
||||
|
||||
// projectUserBackendError shares the error-projection path between every
|
||||
// user-command projector. The error envelope is identical regardless of
|
||||
// the success-path payload shape.
|
||||
func projectUserBackendError(statusCode int, payload []byte) (downstream.UnaryResult, error) {
|
||||
errResp, err := decodeUserError(statusCode, payload)
|
||||
if err != nil {
|
||||
return downstream.UnaryResult{}, fmt.Errorf("decode error response: %w", err)
|
||||
}
|
||||
payloadBytes, err := transcoder.ErrorResponseToPayload(errResp)
|
||||
if err != nil {
|
||||
return downstream.UnaryResult{}, fmt.Errorf("encode error response payload: %w", err)
|
||||
}
|
||||
return downstream.UnaryResult{
|
||||
ResultCode: errResp.Error.Code,
|
||||
PayloadBytes: payloadBytes,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func projectUserResponse(statusCode int, payload []byte) (downstream.UnaryResult, error) {
|
||||
switch {
|
||||
case statusCode == http.StatusOK:
|
||||
|
||||
Reference in New Issue
Block a user