Skip to content

Commit

Permalink
refact: config management (#65)
Browse files Browse the repository at this point in the history
  • Loading branch information
crlssn authored Nov 22, 2024
1 parent 9d57995 commit ab1cc34
Show file tree
Hide file tree
Showing 11 changed files with 175 additions and 91 deletions.
14 changes: 3 additions & 11 deletions apps/backend/cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ package main

import (
"fmt"
"os"

"github.com/bufbuild/protovalidate-go"
"github.com/joho/godotenv"
"go.uber.org/fx"
"go.uber.org/zap"
"google.golang.org/grpc"

"github.com/crlssn/getstronger/apps/backend/pkg/config"
"github.com/crlssn/getstronger/apps/backend/pkg/db"
"github.com/crlssn/getstronger/apps/backend/pkg/jwt"
"github.com/crlssn/getstronger/apps/backend/pkg/repo"
Expand All @@ -27,21 +27,13 @@ func main() {
func options() []fx.Option {
return []fx.Option{
jwt.Module(),
rpc.NewModule(),
rpc.Module(),
fx.Provide(
func() db.Options {
return db.Options{
Host: os.Getenv("DB_HOST"),
Port: os.Getenv("DB_PORT"),
User: os.Getenv("DB_USER"),
Password: os.Getenv("DB_PASSWORD"),
Database: os.Getenv("DB_NAME"),
}
},
db.New,
zap.NewDevelopment,
repo.New,
grpc.NewServer,
config.New,
protovalidate.New,
),
}
Expand Down
56 changes: 56 additions & 0 deletions apps/backend/pkg/config/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package config

import (
"os"
"strings"
)

func New() *Config {
return &Config{
DB: DB{
Host: os.Getenv("DB_HOST"),
Port: os.Getenv("DB_PORT"),
Name: os.Getenv("DB_NAME"),
User: os.Getenv("DB_USER"),
Password: os.Getenv("DB_PASSWORD"),
},
JWT: JWT{
AccessTokenKey: os.Getenv("JWT_ACCESS_TOKEN_KEY"),
RefreshTokenKey: os.Getenv("JWT_REFRESH_TOKEN_KEY"),
},
Server: Server{
Port: os.Getenv("SERVER_PORT"),
KeyPath: os.Getenv("SERVER_KEY_PATH"),
CertPath: os.Getenv("SERVER_CERT_PATH"),
CookieDomain: os.Getenv("COOKIE_DOMAIN"),
AllowedOrigins: strings.Split(os.Getenv("CORS_ALLOWED_ORIGIN"), ","),
},
}
}

type Config struct {
DB DB
JWT JWT
Server Server
}

type DB struct {
Host string
Port string
Name string
User string
Password string
}

type JWT struct {
AccessTokenKey string
RefreshTokenKey string
}

type Server struct {
Port string
KeyPath string
CertPath string
CookieDomain string
AllowedOrigins []string
}
14 changes: 4 additions & 10 deletions apps/backend/pkg/db/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,12 @@ import (
"net"

_ "github.com/jackc/pgx/v5/stdlib" // Register pgx driver
)

type Options struct {
Host string
Port string
User string
Password string
Database string
}
"github.com/crlssn/getstronger/apps/backend/pkg/config"
)

func New(opts Options) (*sql.DB, error) {
db, err := sql.Open("pgx", fmt.Sprintf("postgresql://%s:%s@%s/%s", opts.User, opts.Password, net.JoinHostPort(opts.Host, opts.Port), opts.Database))
func New(c *config.Config) (*sql.DB, error) {
db, err := sql.Open("pgx", fmt.Sprintf("postgresql://%s:%s@%s/%s", c.DB.User, c.DB.Password, net.JoinHostPort(c.DB.Host, c.DB.Port), c.DB.Name))
if err != nil {
return nil, fmt.Errorf("open db: %w", err)
}
Expand Down
8 changes: 4 additions & 4 deletions apps/backend/pkg/jwt/module.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
package jwt

import (
"os"

"go.uber.org/fx"

"github.com/crlssn/getstronger/apps/backend/pkg/config"
)

func Module() fx.Option {
return fx.Provide(
func() *Manager {
return NewManager([]byte(os.Getenv("JWT_ACCESS_TOKEN_KEY")), []byte(os.Getenv("JWT_REFRESH_TOKEN_KEY")))
func(c *config.Config) *Manager {
return NewManager([]byte(c.JWT.AccessTokenKey), []byte(c.JWT.RefreshTokenKey))
},
)
}
6 changes: 3 additions & 3 deletions apps/backend/rpc/interceptors/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ type auth struct {
methods map[string]bool
}

func NewAuth(log *zap.Logger, m *jwt.Manager) Interceptor {
var _ Interceptor = (*auth)(nil)

func newAuth(log *zap.Logger, m *jwt.Manager) Interceptor {
a := &auth{
log: log,
jwt: m,
Expand Down Expand Up @@ -129,5 +131,3 @@ func (a *auth) claimsFromHeader(header http.Header) (*jwt.Claims, error) {

return claims, nil
}

var _ Interceptor = (*auth)(nil)
2 changes: 1 addition & 1 deletion apps/backend/rpc/interceptors/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func TestAuthSuite(t *testing.T) {
func (s *authSuite) SetupSuite() {
s.jwt = jwt.NewManager([]byte("access-token"), []byte("refresh-token"))

interceptor, ok := NewAuth(zap.NewExample(), s.jwt).(*auth)
interceptor, ok := newAuth(zap.NewExample(), s.jwt).(*auth)
s.Require().True(ok)

s.interceptor = interceptor
Expand Down
36 changes: 36 additions & 0 deletions apps/backend/rpc/interceptors/module.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package interceptors

import (
"connectrpc.com/connect"
"go.uber.org/fx"
)

const fxGroupInterceptors = `group:"interceptors"`

func Module() fx.Option {
return fx.Options(
fx.Provide(
// Annotate the interceptors to provide a slice of their interface.
fx.Annotate(
newAuth,
fx.ResultTags(fxGroupInterceptors),
),
fx.Annotate(
newValidator,
fx.ResultTags(fxGroupInterceptors),
),
fx.Annotate(
provideHandlerOptions,
fx.ParamTags(fxGroupInterceptors),
),
),
)
}

func provideHandlerOptions(i []Interceptor) []connect.HandlerOption {
opts := make([]connect.HandlerOption, 0, len(i))
for _, j := range i {
opts = append(opts, connect.WithInterceptors(j.Unary()))
}
return opts
}
6 changes: 3 additions & 3 deletions apps/backend/rpc/interceptors/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ type validator struct {
validator *protovalidate.Validator
}

func NewValidator(log *zap.Logger, v *protovalidate.Validator) Interceptor {
var _ Interceptor = (*validator)(nil)

func newValidator(log *zap.Logger, v *protovalidate.Validator) Interceptor {
return &validator{
log: log,
validator: v,
Expand Down Expand Up @@ -45,5 +47,3 @@ func (v *validator) Unary() connect.UnaryInterceptorFunc {
}
}
}

var _ Interceptor = (*validator)(nil)
22 changes: 15 additions & 7 deletions apps/backend/rpc/middlewares/middlewares.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,26 @@ package middlewares
import (
"context"
"net/http"
"os"

connectcors "connectrpc.com/cors"
"github.com/rs/cors"

"github.com/crlssn/getstronger/apps/backend/pkg/config"
"github.com/crlssn/getstronger/apps/backend/pkg/jwt"
)

func Register(h http.Handler) http.Handler {
type Middleware struct {
config *config.Config
}

func New(c *config.Config) *Middleware {
return &Middleware{c}
}

func (m *Middleware) Register(h http.Handler) http.Handler {
middlewares := []func(http.Handler) http.Handler{
coors,
cookies,
m.coors,
m.cookies,
}

for _, middleware := range middlewares {
Expand All @@ -24,10 +32,10 @@ func Register(h http.Handler) http.Handler {
return h
}

func coors(h http.Handler) http.Handler {
func (m *Middleware) coors(h http.Handler) http.Handler {
middleware := cors.New(cors.Options{
AllowCredentials: true,
AllowedOrigins: []string{os.Getenv("CORS_ALLOWED_ORIGIN")},
AllowedOrigins: m.config.Server.AllowedOrigins,
AllowedMethods: []string{
http.MethodGet,
http.MethodPost,
Expand All @@ -48,7 +56,7 @@ func coors(h http.Handler) http.Handler {
return middleware.Handler(h)
}

func cookies(h http.Handler) http.Handler {
func (m *Middleware) cookies(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
cookie, err := r.Cookie("refreshToken")
if err == nil {
Expand Down
70 changes: 26 additions & 44 deletions apps/backend/rpc/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,58 +4,36 @@ import (
"context"
"fmt"
"net/http"
"os"

"connectrpc.com/connect"
"go.uber.org/fx"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"

"github.com/crlssn/getstronger/apps/backend/pkg/config"
"github.com/crlssn/getstronger/apps/backend/pkg/pb/api/v1/apiv1connect"
"github.com/crlssn/getstronger/apps/backend/rpc/interceptors"
"github.com/crlssn/getstronger/apps/backend/rpc/middlewares"
v1 "github.com/crlssn/getstronger/apps/backend/rpc/v1"
)

type Handler func(opts ...connect.HandlerOption) (string, http.Handler)

const fxGroupInterceptors = `group:"interceptors"`

func NewModule() fx.Option {
func Module() fx.Option {
return fx.Options(
interceptors.Module(),
fx.Provide(
fx.Annotate(
interceptors.NewAuth,
fx.ResultTags(fxGroupInterceptors),
),
fx.Annotate(
interceptors.NewValidator,
fx.ResultTags(fxGroupInterceptors),
),
fx.Annotate(
newInterceptors,
fx.ParamTags(fxGroupInterceptors),
),
newHandlers,
registerHandlers,
v1.NewAuthHandler,
v1.NewRoutineHandler,
v1.NewWorkoutHandler,
v1.NewExerciseHandler,
middlewares.New,
),
fx.Invoke(
registerHandlers,
startServer,
),
)
}

func newInterceptors(i []interceptors.Interceptor) []connect.HandlerOption {
opts := make([]connect.HandlerOption, 0, len(i))
for _, i := range i {
opts = append(opts, connect.WithInterceptors(i.Unary()))
}
return opts
}

type Handlers struct {
fx.In

Expand All @@ -65,35 +43,39 @@ type Handlers struct {
Exercise apiv1connect.ExerciseServiceHandler
}

func newHandlers(p Handlers) []Handler {
return []Handler{
func(options ...connect.HandlerOption) (string, http.Handler) {
return apiv1connect.NewAuthServiceHandler(p.Auth, options...)
func registerHandlers(p Handlers, o []connect.HandlerOption, m *middlewares.Middleware) *http.ServeMux {
handlers := []func(opts ...connect.HandlerOption) (string, http.Handler){
func(opts ...connect.HandlerOption) (string, http.Handler) {
return apiv1connect.NewAuthServiceHandler(p.Auth, opts...)
},
func(options ...connect.HandlerOption) (string, http.Handler) {
return apiv1connect.NewRoutineServiceHandler(p.Routine, options...)
func(opts ...connect.HandlerOption) (string, http.Handler) {
return apiv1connect.NewRoutineServiceHandler(p.Routine, opts...)
},
func(options ...connect.HandlerOption) (string, http.Handler) {
return apiv1connect.NewWorkoutServiceHandler(p.Workout, options...)
func(opts ...connect.HandlerOption) (string, http.Handler) {
return apiv1connect.NewWorkoutServiceHandler(p.Workout, opts...)
},
func(options ...connect.HandlerOption) (string, http.Handler) {
return apiv1connect.NewExerciseServiceHandler(p.Exercise, options...)
func(opts ...connect.HandlerOption) (string, http.Handler) {
return apiv1connect.NewExerciseServiceHandler(p.Exercise, opts...)
},
}
}

func registerHandlers(lc fx.Lifecycle, handlers []Handler, options []connect.HandlerOption) {
mux := http.NewServeMux()
for _, h := range handlers {
path, handler := h(options...)
mux.Handle(path, middlewares.Register(handler))
path, handler := h(o...)
mux.Handle(path, m.Register(handler))
}

return mux
}

func startServer(lc fx.Lifecycle, c *config.Config, mux *http.ServeMux) {
lc.Append(fx.Hook{
OnStart: func(_ context.Context) error {
go func() {
if err := http.ListenAndServeTLS(fmt.Sprintf(":%s", os.Getenv("SERVER_PORT")), os.Getenv("SERVER_CERT_PATH"), os.Getenv("SERVER_KEY_PATH"), h2c.NewHandler(mux, &http2.Server{})); err != nil {
panic(err)
address := fmt.Sprintf(":%s", c.Server.Port)
err := http.ListenAndServeTLS(address, c.Server.CertPath, c.Server.KeyPath, h2c.NewHandler(mux, &http2.Server{}))
if err != nil {
panic(fmt.Errorf("listen and serve: %w", err))
}
}()
return nil
Expand Down
Loading

0 comments on commit ab1cc34

Please sign in to comment.