feat: backend service

This commit is contained in:
Ilia Denisov
2026-05-06 10:14:55 +03:00
committed by GitHub
parent 3e2622757e
commit f446c6a2ac
1486 changed files with 49720 additions and 266401 deletions
@@ -0,0 +1,70 @@
// Package userid extracts the calling user identifier from the trusted
// `X-User-ID` header injected by gateway and exposes it through the request
// context.
//
// Backend trusts the header value because the network segment between gateway
// and backend is the trust boundary (see `ARCHITECTURE.md` §15). The
// middleware therefore only validates the syntactic shape (UUID) and rejects
// malformed or absent values with the standard `400 invalid_request` envelope.
package userid
import (
"context"
"net/http"
"strings"
"galaxy/backend/internal/server/httperr"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
// Header is the canonical case-correct header name carrying the trusted user
// id forwarded by gateway.
const Header = "X-User-ID"
// userIDContextKey is the unexported context key used to store the parsed
// user id. The unexported value type prevents accidental collisions with
// keys defined in unrelated packages.
type userIDContextKey struct{}
// Middleware returns the gin middleware that requires a syntactically valid
// `X-User-ID` header on every authenticated user request and stores the
// parsed UUID on the request context.
func Middleware() gin.HandlerFunc {
return func(c *gin.Context) {
raw := strings.TrimSpace(c.GetHeader(Header))
if raw == "" {
httperr.Abort(c, http.StatusBadRequest, httperr.CodeInvalidRequest, Header+" header is required")
return
}
userID, err := uuid.Parse(raw)
if err != nil {
httperr.Abort(c, http.StatusBadRequest, httperr.CodeInvalidRequest, Header+" header must be a valid UUID")
return
}
c.Request = c.Request.WithContext(WithValue(c.Request.Context(), userID))
c.Next()
}
}
// FromContext returns the user id stored on ctx by Middleware. The boolean
// reports whether a value was found.
func FromContext(ctx context.Context) (uuid.UUID, bool) {
if ctx == nil {
return uuid.Nil, false
}
value, ok := ctx.Value(userIDContextKey{}).(uuid.UUID)
if !ok {
return uuid.Nil, false
}
return value, true
}
// WithValue stores userID on ctx under the package-private context key.
// Exposed for tests that need to build a context outside the middleware.
func WithValue(ctx context.Context, userID uuid.UUID) context.Context {
return context.WithValue(ctx, userIDContextKey{}, userID)
}