add internal/chi/middleware, replace gin with go-chi

This commit is contained in:
Dawid Wysokiński 2021-07-17 09:56:12 +02:00
parent ed7ecd8758
commit aa1e6ad5b8
9 changed files with 181 additions and 54 deletions

View File

@ -20,7 +20,7 @@ DB_POOL_SIZE=40
LOG_DB_QUERIES=true
ACCESS_SECRET=access_token_secret
FILE_STORAGE_PATH=path_to_the_folder_where_uploaded_files_will_be_stored
DISABLE_ACCESS_LOG=false
ENABLE_ACCESS_LOG=false
```
1. Clone this repo.

4
go.mod
View File

@ -5,6 +5,7 @@ go 1.16
require (
github.com/99designs/gqlgen v0.13.0
github.com/Kichiyaki/appmode v0.0.0-20210502105643-0a26207c548d
github.com/Kichiyaki/chilogrus v0.0.0-20210717074801-6ecc28dbf6b9 // indirect
github.com/Kichiyaki/ginlogrus v0.0.0-20210502060406-bb0049cc40c4
github.com/Kichiyaki/go-pg-logrus-query-logger/v10 v10.0.0-20210502060056-ad595ba7b858
github.com/Kichiyaki/gopgutil/v10 v10.0.0-20210521204542-cc672e361b3d
@ -13,6 +14,8 @@ require (
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/gin-contrib/cors v1.3.1
github.com/gin-gonic/gin v1.6.3
github.com/go-chi/chi/v5 v5.0.3 // indirect
github.com/go-chi/cors v1.2.0 // indirect
github.com/go-pg/pg/v10 v10.10.2
github.com/google/uuid v1.3.0
github.com/gosimple/slug v1.9.0
@ -25,5 +28,6 @@ require (
github.com/sirupsen/logrus v1.8.1
github.com/vektah/gqlparser/v2 v2.1.0
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
)

11
go.sum
View File

@ -4,6 +4,10 @@ github.com/99designs/gqlgen v0.13.0/go.mod h1:NV130r6f4tpRWuAI+zsrSdooO/eWUv+Gyy
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Kichiyaki/appmode v0.0.0-20210502105643-0a26207c548d h1:ApX13STtfJc2YPH5D2JnBa6+4AM2vt7a81so/MPr/bA=
github.com/Kichiyaki/appmode v0.0.0-20210502105643-0a26207c548d/go.mod h1:41p1KTy/fiVocPnJR2h/iXh2NvWWVBdNoZrN8TWVXUI=
github.com/Kichiyaki/chilogrus v0.0.0-20210714125912-ed2a840463bf h1:IAr9vs4vth1eX6bbdiiCPO7LPvEiKjGuNmh5D+IGAio=
github.com/Kichiyaki/chilogrus v0.0.0-20210714125912-ed2a840463bf/go.mod h1:DkStLjMOKdbuZ/qFps1/fUW+17Glvefijm5NS1KCbIE=
github.com/Kichiyaki/chilogrus v0.0.0-20210717074801-6ecc28dbf6b9 h1:ze0F+qhjUaQrZ+3UdZ6Dz3L21T/mZwB6UzzZ8LdG44Y=
github.com/Kichiyaki/chilogrus v0.0.0-20210717074801-6ecc28dbf6b9/go.mod h1:DkStLjMOKdbuZ/qFps1/fUW+17Glvefijm5NS1KCbIE=
github.com/Kichiyaki/ginlogrus v0.0.0-20210502060406-bb0049cc40c4 h1:1fPLsfbM6DGZcpdV+IeD/52BP7tL33MoVh7FdUGj14g=
github.com/Kichiyaki/ginlogrus v0.0.0-20210502060406-bb0049cc40c4/go.mod h1:PTGPHApaVoNA6eEC72vqvzKRhhSUQnHfh9uSho3rsXk=
github.com/Kichiyaki/go-pg-logrus-query-logger/v10 v10.0.0-20210502060056-ad595ba7b858 h1:O7EPPY5lWaKbYB/5yJzE8WMesismUYuje7gOemo1UNo=
@ -45,7 +49,12 @@ github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm
github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do=
github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
github.com/go-chi/chi v3.3.2+incompatible h1:uQNcQN3NsV1j4ANsPh42P4ew4t6rnRbJb8frvpp31qQ=
github.com/go-chi/chi v3.3.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/go-chi/chi/v5 v5.0.3 h1:khYQBdPivkYG1s1TAzDQG1f6eX4kD2TItYVZexL5rS4=
github.com/go-chi/chi/v5 v5.0.3/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/cors v1.2.0 h1:tV1g1XENQ8ku4Bq3K9ub2AtgG+p16SmzeMSGTwrOKdE=
github.com/go-chi/cors v1.2.0/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
github.com/go-pg/pg/v10 v10.9.1/go.mod h1:rgmTPgHgl5EN2CNKKoMwC7QT62t8BqsdpEkUQuiZMQs=
github.com/go-pg/pg/v10 v10.10.2 h1:8G2DdKrB3/0nRIlpur0HySEWBJnHYUByiC0ko4XzE8w=
github.com/go-pg/pg/v10 v10.10.2/go.mod h1:EmoJGYErc+stNN/1Jf+o4csXuprjxcRztBnn6cHe38E=
@ -251,6 +260,8 @@ golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=

View File

@ -0,0 +1,50 @@
package middleware
import (
"context"
"github.com/pkg/errors"
"net/http"
"github.com/zdam-egzamin-zawodowy/backend/internal/auth"
"github.com/zdam-egzamin-zawodowy/backend/internal/model"
)
const (
authorizationHeader = "Authorization"
)
var (
authenticateKey contextKey = "current_user"
)
func Authenticate(ucase auth.Usecase) func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := extractToken(r.Header.Get("Authorization"))
if token != "" {
ctx := r.Context()
user, err := ucase.ExtractAccessTokenMetadata(ctx, token)
if err == nil && user != nil {
ctx = context.WithValue(ctx, authenticateKey, user)
r = r.WithContext(ctx)
}
}
next.ServeHTTP(w, r)
})
}
}
func UserFromContext(ctx context.Context) (*model.User, error) {
user := ctx.Value(authenticateKey)
if user == nil {
err := errors.New("couldn't retrieve *model.User")
return nil, err
}
u, ok := user.(*model.User)
if !ok {
err := errors.New("*model.User has wrong type")
return nil, err
}
return u, nil
}

View File

@ -0,0 +1,3 @@
package middleware
type contextKey string

View File

@ -0,0 +1,37 @@
package middleware
import (
"context"
"github.com/pkg/errors"
"net/http"
"github.com/zdam-egzamin-zawodowy/backend/internal/graphql/dataloader"
)
var (
dataLoaderToContext contextKey = "data_loader"
)
func DataLoaderToContext(cfg dataloader.Config) func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := context.WithValue(r.Context(), dataLoaderToContext, dataloader.New(cfg))
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}
func DataLoaderFromContext(ctx context.Context) (*dataloader.DataLoader, error) {
dataLoader := ctx.Value(dataLoaderToContext)
if dataLoader == nil {
err := errors.New("couldn't retrieve dataloader.DataLoader")
return nil, err
}
dl, ok := dataLoader.(*dataloader.DataLoader)
if !ok {
err := errors.New("dataloader.DataLoader has wrong type")
return nil, err
}
return dl, nil
}

View File

@ -0,0 +1,13 @@
package middleware
import (
"strings"
)
func extractToken(bearToken string) string {
strArr := strings.Split(bearToken, " ")
if len(strArr) == 2 {
return strArr[1]
}
return ""
}

View File

@ -3,16 +3,19 @@ package httpdelivery
import (
"fmt"
"github.com/Kichiyaki/appmode"
"github.com/go-chi/chi/v5"
"github.com/pkg/errors"
"github.com/zdam-egzamin-zawodowy/backend/internal/graphql/querycomplexity"
"net/http"
"time"
"github.com/zdam-egzamin-zawodowy/backend/internal/graphql/querycomplexity"
"github.com/99designs/gqlgen/graphql/handler"
"github.com/99designs/gqlgen/graphql/handler/extension"
"github.com/99designs/gqlgen/graphql/handler/lru"
"github.com/99designs/gqlgen/graphql/handler/transport"
"github.com/99designs/gqlgen/graphql/playground"
"github.com/gin-gonic/gin"
"github.com/zdam-egzamin-zawodowy/backend/internal/graphql/directive"
"github.com/zdam-egzamin-zawodowy/backend/internal/graphql/generated"
"github.com/zdam-egzamin-zawodowy/backend/internal/graphql/resolvers"
@ -29,21 +32,20 @@ type Config struct {
Directive *directive.Directive
}
func Attach(group *gin.RouterGroup, cfg Config) error {
func Attach(r chi.Router, cfg Config) error {
if cfg.Resolver == nil {
return errors.New("cfg.Resolver is required")
}
gqlHandler := graphqlHandler(prepareConfig(cfg.Resolver, cfg.Directive))
group.GET(graphqlEndpoint, gqlHandler)
group.POST(graphqlEndpoint, gqlHandler)
r.Get(graphqlEndpoint, gqlHandler)
r.Post(graphqlEndpoint, gqlHandler)
if appmode.Equals(appmode.DevelopmentMode) {
group.GET(playgroundEndpoint, playgroundHandler())
r.Get(playgroundEndpoint, playgroundHandler())
}
return nil
}
// Defining the GraphQL handler
func graphqlHandler(cfg generated.Config) gin.HandlerFunc {
func graphqlHandler(cfg generated.Config) http.HandlerFunc {
srv := handler.New(generated.NewExecutableSchema(cfg))
srv.AddTransport(transport.GET{})
@ -61,19 +63,18 @@ func graphqlHandler(cfg generated.Config) gin.HandlerFunc {
srv.Use(extension.Introspection{})
}
return func(c *gin.Context) {
c.Header("Cache-Control", "no-store, must-revalidate")
srv.ServeHTTP(c.Writer, c.Request)
return func(w http.ResponseWriter, r *http.Request) {
r.Header.Add("Cache-Control", "no-store, must-revalidate")
srv.ServeHTTP(w, r)
}
}
// Defining the Playground handler
func playgroundHandler() gin.HandlerFunc {
func playgroundHandler() http.HandlerFunc {
h := playground.Handler("Playground", graphqlEndpoint)
return func(c *gin.Context) {
c.Header("Cache-Control", fmt.Sprintf(`public, max-age=%d`, playgroundTTL))
h.ServeHTTP(c.Writer, c.Request)
return func(w http.ResponseWriter, r *http.Request) {
r.Header.Add("Cache-Control", fmt.Sprintf(`public, max-age=%d`, playgroundTTL))
h.ServeHTTP(w, r)
}
}

82
main.go
View File

@ -3,12 +3,16 @@ package main
import (
"context"
"github.com/Kichiyaki/appmode"
"github.com/Kichiyaki/chilogrus"
"github.com/Kichiyaki/goutil/envutil"
"github.com/go-chi/chi/v5"
"log"
"net/http"
"os"
"os/signal"
"time"
"github.com/zdam-egzamin-zawodowy/backend/internal/chi/middleware"
graphqlhttpdelivery "github.com/zdam-egzamin-zawodowy/backend/internal/graphql/delivery/httpdelivery"
"github.com/zdam-egzamin-zawodowy/backend/internal/graphql/directive"
"github.com/zdam-egzamin-zawodowy/backend/internal/graphql/resolvers"
@ -17,7 +21,6 @@ import (
"github.com/zdam-egzamin-zawodowy/backend/internal/auth/jwt"
authusecase "github.com/zdam-egzamin-zawodowy/backend/internal/auth/usecase"
"github.com/zdam-egzamin-zawodowy/backend/internal/gin/middleware"
"github.com/zdam-egzamin-zawodowy/backend/internal/graphql/dataloader"
"github.com/zdam-egzamin-zawodowy/backend/internal/postgres"
professionrepository "github.com/zdam-egzamin-zawodowy/backend/internal/profession/repository"
@ -29,13 +32,13 @@ import (
userrepository "github.com/zdam-egzamin-zawodowy/backend/internal/user/repository"
userusecase "github.com/zdam-egzamin-zawodowy/backend/internal/user/usecase"
"github.com/Kichiyaki/ginlogrus"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
"github.com/joho/godotenv"
"github.com/sirupsen/logrus"
"github.com/zdam-egzamin-zawodowy/backend/fstorage"
chimiddleware "github.com/go-chi/chi/v5/middleware"
"github.com/go-chi/cors"
)
func init() {
@ -45,7 +48,7 @@ func init() {
godotenv.Load(".env.local")
}
setupLogger()
prepareLogger()
}
func main() {
@ -120,25 +123,28 @@ func main() {
logrus.Fatal(errors.Wrap(err, "questionUsecase"))
}
router := setupRouter()
graphql := router.Group("")
graphql.Use(
middleware.GinContextToContext(),
middleware.DataLoaderToContext(dataloader.Config{
ProfessionRepo: professionRepository,
QualificationRepo: qualificationRepository,
}),
middleware.Authenticate(authUsecase),
)
graphqlhttpdelivery.Attach(graphql, graphqlhttpdelivery.Config{
Resolver: &resolvers.Resolver{
AuthUsecase: authUsecase,
UserUsecase: userUsecase,
ProfessionUsecase: professionUsecase,
QualificationUsecase: qualificationUsecase,
QuestionUsecase: questionUsecase,
},
Directive: &directive.Directive{},
router := prepareRouter()
router.Group(func(r chi.Router) {
r.Use(
middleware.DataLoaderToContext(dataloader.Config{
ProfessionRepo: professionRepository,
QualificationRepo: qualificationRepository,
}),
middleware.Authenticate(authUsecase),
)
err := graphqlhttpdelivery.Attach(r, graphqlhttpdelivery.Config{
Resolver: &resolvers.Resolver{
AuthUsecase: authUsecase,
UserUsecase: userUsecase,
ProfessionUsecase: professionUsecase,
QualificationUsecase: qualificationUsecase,
QuestionUsecase: questionUsecase,
},
Directive: &directive.Directive{},
})
if err != nil {
log.Fatalln(err)
}
})
srv := &http.Server{
Addr: ":8080",
@ -165,7 +171,7 @@ func main() {
logrus.Info("Server exiting")
}
func setupLogger() {
func prepareLogger() {
if appmode.Equals(appmode.DevelopmentMode) {
logrus.SetLevel(logrus.DebugLevel)
}
@ -183,25 +189,27 @@ func setupLogger() {
}
}
func setupRouter() *gin.Engine {
router := gin.New()
func prepareRouter() *chi.Mux {
r := chi.NewRouter()
router.Use(gin.Recovery())
if !envutil.GetenvBool("DISABLE_ACCESS_LOG") {
router.Use(ginlogrus.Logger(logrus.StandardLogger()))
r.Use(chimiddleware.RealIP)
if envutil.GetenvBool("ENABLE_ACCESS_LOG") {
r.Use(chilogrus.Logger(logrus.StandardLogger()))
}
r.Use(chimiddleware.Recoverer)
if appmode.Equals(appmode.DevelopmentMode) {
router.Use(cors.New(cors.Config{
AllowOriginFunc: func(string) bool {
r.Use(cors.Handler(cors.Options{
AllowOriginFunc: func(*http.Request, string) bool {
return true
},
AllowCredentials: true,
ExposeHeaders: []string{"Authorization"},
AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"},
AllowHeaders: []string{"Origin", "Content-Length", "Content-Type", "Authorization"},
AllowWebSockets: false,
ExposedHeaders: []string{"Authorization"},
AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"},
AllowedHeaders: []string{"Origin", "Content-Length", "Content-Type", "Authorization"},
MaxAge: 300,
}))
}
return router
return r
}