add wrapper for server handlers
This commit is contained in:
parent
6a4607364c
commit
cf01b1e36f
@ -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"
|
||||||
@ -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(¶ms); 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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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(¶ms); 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
31
cmd/shortlinks/grpc.go
Normal 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
|
||||||
|
}
|
||||||
@ -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 {
|
||||||
|
|||||||
@ -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{
|
||||||
|
|||||||
79
internal/http_server/wrapper.go
Normal file
79
internal/http_server/wrapper.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user