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
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_topic: "backend_events"

View File

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

View File

@ -2,8 +2,9 @@ package handlers
import (
"backend/internal/core/services"
httpserver "backend/internal/http_server"
"backend/pkg/logger"
"encoding/json"
"context"
"github.com/gin-gonic/gin"
)
@ -17,43 +18,17 @@ type loginUserOutput struct {
Token string `json:"token"`
}
func NewUserLoginHandler(logger logger.Logger, userService services.UserService) gin.HandlerFunc {
return func(c *gin.Context) {
ctxLogger := logger.WithContext(c).WithPrefix("NewUserLoginHandler")
func NewUserLoginHandler(log logger.Logger, userService services.UserService) gin.HandlerFunc {
return httpserver.WrapGin(log,
func(ctx context.Context, input loginUserInput) (loginUserOutput, error) {
token, err := userService.AuthenticateUser(ctx, input.Login, input.Password)
if err != nil {
return loginUserOutput{}, err
}
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 {
ctxLogger.Error().Err(err).Msg("AuthenticateUser internal error")
c.AbortWithError(500, err)
return
}
resultBody, err := json.Marshal(loginUserOutput{
Token: token,
})
if err != nil {
ctxLogger.Error().Err(err).Msg("marshal json internal error")
c.AbortWithError(500, err)
return
}
c.Data(200, "application/json", resultBody)
}
return loginUserOutput{
Token: token,
}, nil
},
)
}

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,93 +2,52 @@ package main
import (
"backend/internal/core/services"
"backend/internal/grpc_server/shortlinks"
httpserver "backend/internal/http_server"
"backend/pkg/logger"
"context"
"encoding/json"
"fmt"
"net/url"
"github.com/gin-gonic/gin"
)
type shortlinkCreateInput struct {
Url string `json:"url"`
}
type shortlinkCreateOutput struct {
Link string `json:"link"`
}
type ShortlinksGrpc struct {
shortlinks.UnimplementedShortlinksServer
log logger.Logger
host string
shortlinkService services.ShortlinkService
}
func NewCreateHandler(
log logger.Logger,
shortlinkService services.ShortlinkService,
host string,
) 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) {
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 {
ctxLogger.Error().Err(err).Msg("error parsing url param")
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)
u, err := url.Parse(input.Url)
if err != nil {
ctxLogger.Error().Err(err).Msg("error parsing url param")
ctx.Data(400, "plain/text", []byte(err.Error()))
return
return output, err
}
u.Scheme = "https"
linkId, err := shortlinkService.CreateShortlink(ctx, u.String())
if err != nil {
ctxLogger.Error().Err(err).Msg("err creating shortlink")
ctx.Data(500, "plain/text", []byte(err.Error()))
return
return output, err
}
resultBody, err := json.Marshal(shortlinkCreateOutput{
return shortlinkCreateOutput{
Link: fmt.Sprintf("%s/s/%s", host, linkId),
})
if err != nil {
ctxLogger.Error().Err(err).Msg("err marshalling shortlink")
ctx.AbortWithError(500, err)
return
}
ctx.Data(200, "application/json", resultBody)
}, nil
}
}
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 {
return func(ctx *gin.Context) {
ctxLogger := logger.WithContext(ctx)

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.GET("/:linkId", NewShortlinkResolveHandler(log, shortlinkService))
grpcObj := &ShortlinksGrpc{
log: log,
host: host,
shortlinkService: shortlinkService,
}
grpcUnderlying := grpc.NewServer()
shortlinks.RegisterShortlinksServer(grpcUnderlying, grpcObj)
shortlinks.RegisterShortlinksServer(
grpcUnderlying,
NewShortlinksGrpc(log, shortlinkService, host),
)
httpServer := httpserver.New(
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)
}
}