add wrapper for server handlers

This commit is contained in:
Sergey Chubaryan 2025-02-07 12:09:23 +03:00
parent 6a4607364c
commit cf01b1e36f
7 changed files with 173 additions and 158 deletions

View File

@ -1,5 +1,5 @@
port: 8080 port: 8080
postgres_url: "postgres://postgres:postgres@localhost:5432/postgres" postgres_url: "postgres://postgres:postgres@localhost:5432/postgres"
jwt_signing_key: "./jwt_signing_key" jwt_signing_key: "./config_defaults/jwt_signing_key"
kafka_url: "localhost:9092" kafka_url: "localhost:9092"
kafka_topic: "backend_events" kafka_topic: "backend_events"

View File

@ -2,8 +2,9 @@ package handlers
import ( import (
"backend/internal/core/services" "backend/internal/core/services"
httpserver "backend/internal/http_server"
"backend/pkg/logger" "backend/pkg/logger"
"encoding/json" "context"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@ -20,54 +21,27 @@ type createUserOutput struct {
Name string `json:"name"` Name string `json:"name"`
} }
func NewUserCreateHandler(logger logger.Logger, userService services.UserService) gin.HandlerFunc { func NewUserCreateHandler(log logger.Logger, userService services.UserService) gin.HandlerFunc {
return func(c *gin.Context) { return httpserver.WrapGin(log,
ctxLogger := logger.WithContext(c) func(ctx context.Context, input createUserInput) (createUserOutput, error) {
user, err := userService.CreateUser(ctx,
params := createUserInput{}
if err := c.ShouldBindJSON(&params); err != nil {
ctxLogger.Error().Err(err).Msg("bad input body model")
c.Data(400, "plain/text", []byte(err.Error()))
return
}
dto, err := userService.CreateUser(
c,
services.UserCreateParams{ services.UserCreateParams{
Email: params.Email, Email: input.Email,
Password: params.Password, Password: input.Password,
Name: params.Name, Name: input.Name,
}, },
) )
if err == services.ErrUserExists {
ctxLogger.Error().Err(err).Msg("user already exists") out := createUserOutput{}
c.Data(400, "plain/text", []byte(err.Error()))
return
}
if err == services.ErrUserBadPassword {
ctxLogger.Error().Err(err).Msg("password does not satisfy requirements")
c.Data(400, "plain/text", []byte(err.Error()))
return
}
if err != nil { if err != nil {
ctxLogger.Error().Err(err).Msg("unexpected create user error") return out, err
c.Data(500, "plain/text", []byte(err.Error()))
return
} }
resultBody, err := json.Marshal( return createUserOutput{
createUserOutput{ Id: user.Id,
Id: dto.Id, Email: user.Email,
Email: dto.Email, Name: user.Name,
Name: dto.Name, }, nil
}, },
) )
if err != nil {
ctxLogger.Error().Err(err).Msg("marshal user model error")
c.Data(500, "plain/text", []byte(err.Error()))
return
}
c.Data(200, "application/json", resultBody)
}
} }

View File

@ -2,8 +2,9 @@ package handlers
import ( import (
"backend/internal/core/services" "backend/internal/core/services"
httpserver "backend/internal/http_server"
"backend/pkg/logger" "backend/pkg/logger"
"encoding/json" "context"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@ -17,43 +18,17 @@ type loginUserOutput struct {
Token string `json:"token"` Token string `json:"token"`
} }
func NewUserLoginHandler(logger logger.Logger, userService services.UserService) gin.HandlerFunc { func NewUserLoginHandler(log logger.Logger, userService services.UserService) gin.HandlerFunc {
return func(c *gin.Context) { return httpserver.WrapGin(log,
ctxLogger := logger.WithContext(c).WithPrefix("NewUserLoginHandler") func(ctx context.Context, input loginUserInput) (loginUserOutput, error) {
token, err := userService.AuthenticateUser(ctx, input.Login, input.Password)
params := loginUserInput{}
if err := c.ShouldBindJSON(&params); err != nil {
ctxLogger.Error().Err(err).Msg("bad input body model")
c.AbortWithError(400, err)
return
}
token, err := userService.AuthenticateUser(c, params.Login, params.Password)
if err == services.ErrUserNotExists {
ctxLogger.Error().Err(err).Msg("user does not exist")
c.AbortWithError(400, err)
return
}
if err == services.ErrUserWrongPassword {
ctxLogger.Error().Err(err).Msg("wrong password")
c.AbortWithError(400, err)
return
}
if err != nil { if err != nil {
ctxLogger.Error().Err(err).Msg("AuthenticateUser internal error") return loginUserOutput{}, err
c.AbortWithError(500, err)
return
} }
resultBody, err := json.Marshal(loginUserOutput{ return loginUserOutput{
Token: token, Token: token,
}) }, nil
if err != nil { },
ctxLogger.Error().Err(err).Msg("marshal json internal error") )
c.AbortWithError(500, err)
return
}
c.Data(200, "application/json", resultBody)
}
} }

31
cmd/shortlinks/grpc.go Normal file
View File

@ -0,0 +1,31 @@
package main
import (
"backend/internal/core/services"
"backend/internal/grpc_server/shortlinks"
httpserver "backend/internal/http_server"
"backend/pkg/logger"
"context"
)
func NewShortlinksGrpc(log logger.Logger, shortlinkService services.ShortlinkService, host string) *ShortlinksGrpc {
return &ShortlinksGrpc{
handler: NewCreateHandler(log, shortlinkService, host),
}
}
type ShortlinksGrpc struct {
shortlinks.UnimplementedShortlinksServer
handler httpserver.Handler[shortlinkCreateInput, shortlinkCreateOutput]
}
func (s *ShortlinksGrpc) Create(ctx context.Context, req *shortlinks.CreateRequest) (*shortlinks.CreateResponse, error) {
output, err := s.handler(ctx, shortlinkCreateInput{req.Url})
if err != nil {
return nil, err
}
return &shortlinks.CreateResponse{
Link: output.Link,
}, nil
}

View File

@ -2,91 +2,50 @@ package main
import ( import (
"backend/internal/core/services" "backend/internal/core/services"
"backend/internal/grpc_server/shortlinks" httpserver "backend/internal/http_server"
"backend/pkg/logger" "backend/pkg/logger"
"context" "context"
"encoding/json"
"fmt" "fmt"
"net/url" "net/url"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
type shortlinkCreateInput struct {
Url string `json:"url"`
}
type shortlinkCreateOutput struct { type shortlinkCreateOutput struct {
Link string `json:"link"` Link string `json:"link"`
} }
type ShortlinksGrpc struct { func NewCreateHandler(
shortlinks.UnimplementedShortlinksServer log logger.Logger,
log logger.Logger shortlinkService services.ShortlinkService,
host string host string,
shortlinkService services.ShortlinkService ) httpserver.Handler[shortlinkCreateInput, shortlinkCreateOutput] {
} return func(ctx context.Context, input shortlinkCreateInput) (shortlinkCreateOutput, error) {
output := shortlinkCreateOutput{}
func (s *ShortlinksGrpc) Create(ctx context.Context, req *shortlinks.CreateRequest) (*shortlinks.CreateResponse, error) { u, err := url.Parse(input.Url)
ctxLogger := s.log.WithContext(ctx)
rawUrl := req.GetUrl()
if rawUrl == "" {
ctxLogger.Error().Msg("url query param missing")
return nil, fmt.Errorf("url query param missing")
}
u, err := url.Parse(rawUrl)
if err != nil { if err != nil {
ctxLogger.Error().Err(err).Msg("error parsing url param") return output, err
return nil, err
}
u.Scheme = "https"
linkId, err := s.shortlinkService.CreateShortlink(ctx, u.String())
if err != nil {
ctxLogger.Error().Err(err).Msg("err creating shortlink")
return nil, err
}
return &shortlinks.CreateResponse{
Link: fmt.Sprintf("%s/s/%s", s.host, linkId),
}, nil
}
func NewShortlinkCreateHandler(logger logger.Logger, shortlinkService services.ShortlinkService, host string) gin.HandlerFunc {
return func(ctx *gin.Context) {
ctxLogger := logger.WithContext(ctx)
rawUrl := ctx.Query("url")
if rawUrl == "" {
ctxLogger.Error().Msg("url query param missing")
ctx.AbortWithError(400, fmt.Errorf("url query param missing"))
return
}
u, err := url.Parse(rawUrl)
if err != nil {
ctxLogger.Error().Err(err).Msg("error parsing url param")
ctx.Data(400, "plain/text", []byte(err.Error()))
return
} }
u.Scheme = "https" u.Scheme = "https"
linkId, err := shortlinkService.CreateShortlink(ctx, u.String()) linkId, err := shortlinkService.CreateShortlink(ctx, u.String())
if err != nil { if err != nil {
ctxLogger.Error().Err(err).Msg("err creating shortlink") return output, err
ctx.Data(500, "plain/text", []byte(err.Error()))
return
} }
resultBody, err := json.Marshal(shortlinkCreateOutput{ return shortlinkCreateOutput{
Link: fmt.Sprintf("%s/s/%s", host, linkId), Link: fmt.Sprintf("%s/s/%s", host, linkId),
}) }, nil
if err != nil { }
ctxLogger.Error().Err(err).Msg("err marshalling shortlink")
ctx.AbortWithError(500, err)
return
} }
ctx.Data(200, "application/json", resultBody) func NewShortlinkCreateHandler(log logger.Logger, shortlinkService services.ShortlinkService, host string) gin.HandlerFunc {
} return httpserver.WrapGin(log, NewCreateHandler(log, shortlinkService, host))
} }
func NewShortlinkResolveHandler(logger logger.Logger, shortlinkService services.ShortlinkService) gin.HandlerFunc { func NewShortlinkResolveHandler(logger logger.Logger, shortlinkService services.ShortlinkService) gin.HandlerFunc {

View File

@ -102,14 +102,11 @@ func RunServer(ctx context.Context, log logger.Logger, tracer trace.Tracer, conf
linkGroup.POST("/new", NewShortlinkCreateHandler(log, shortlinkService, host)) linkGroup.POST("/new", NewShortlinkCreateHandler(log, shortlinkService, host))
linkGroup.GET("/:linkId", NewShortlinkResolveHandler(log, shortlinkService)) linkGroup.GET("/:linkId", NewShortlinkResolveHandler(log, shortlinkService))
grpcObj := &ShortlinksGrpc{
log: log,
host: host,
shortlinkService: shortlinkService,
}
grpcUnderlying := grpc.NewServer() grpcUnderlying := grpc.NewServer()
shortlinks.RegisterShortlinksServer(grpcUnderlying, grpcObj) shortlinks.RegisterShortlinksServer(
grpcUnderlying,
NewShortlinksGrpc(log, shortlinkService, host),
)
httpServer := httpserver.New( httpServer := httpserver.New(
httpserver.NewServerOpts{ httpserver.NewServerOpts{

View File

@ -0,0 +1,79 @@
package httpserver
import (
"backend/pkg/logger"
"context"
"encoding/json"
"github.com/gin-gonic/gin"
)
type Handler[Input, Output any] func(ctx context.Context, input Input) (Output, error)
type ResponseOk struct {
Status string `json:"status"`
Result interface{} `json:"result"`
}
type ResponseError struct {
Status string `json:"status"`
Error struct {
Id string `json:"id"`
Message string `json:"message"`
} `json:"error"`
}
func WrapGin[In, Out interface{}](log logger.Logger, handler Handler[In, Out]) gin.HandlerFunc {
return func(c *gin.Context) {
log := log.WithContext(c)
var input In
if err := c.ShouldBindJSON(&input); err != nil {
response := ResponseError{
Status: "error",
Error: struct {
Id string `json:"id"`
Message string `json:"message"`
}{
Id: "WrongBody",
Message: err.Error(),
},
}
body, _ := json.Marshal(response)
c.Data(400, "application/json", body)
return
}
var response interface{}
output, err := handler(c, input)
if err != nil {
log.Error().Err(err).Msg("error in request handler")
response = ResponseError{
Status: "error",
Error: struct {
Id string `json:"id"`
Message string `json:"message"`
}{
Id: "-",
Message: err.Error(),
},
}
} else {
response = ResponseOk{
Status: "success",
Result: output,
}
}
body, err := json.Marshal(response)
if err != nil {
log.Error().Err(err).Msg("marshal response error")
c.Data(500, "plain/text", []byte(err.Error()))
return
}
c.Data(200, "application/json", body)
}
}