This commit is contained in:
PB
2023-04-16 17:17:05 +02:00
parent cd9bbdfd75
commit 7adf3b9512
24 changed files with 509 additions and 325 deletions

View File

@@ -0,0 +1,31 @@
package server
import "fmt"
type Config struct {
AppID string
AppName string
AppDomain string
PathPrefix string
NetAddr string
Port int
RegistryAddr string
KVNamespace string
LoggerAddr string `json:"logger_addr"`
DbURL string `json:"db_url"`
CacheAddr string `json:"cache_addr"`
CachePassword string `json:"cache_password"`
MongoDbUrl string `json:"mongodb_url"`
EventBusURL string `json:"eventbus_url"`
EventBusExchange string `json:"eventbus_exchange"`
EventBusQueue string `json:"eventbus_queue"`
HttpReadTimeout int `json:"http_read_timeout"`
HttpWriteTimeout int `json:"http_write_timeout"`
HttpIdleTimeout int `json:"http_idle_timeout"`
// Fields with json mapping are available trough ConsulKV
}
func (c *Config) GetAppFullName() string {
return fmt.Sprintf("%s_%s", c.AppName, c.AppID)
}

View File

@@ -0,0 +1,9 @@
package server
import (
"github.com/gofiber/fiber/v2"
)
func (s *Server) ConfigHandler(c *fiber.Ctx) error {
return c.JSON(s.Config)
}

View File

@@ -0,0 +1,15 @@
package server
import (
"github.com/gofiber/fiber/v2"
)
type HealthResponse struct {
Status string `json:"status,omitempty"`
}
func (s *Server) HealthHandler(c *fiber.Ctx) error {
return c.JSON(&HealthResponse{
Status: "OK",
})
}

View File

@@ -0,0 +1,32 @@
package server
import (
"git.pbiernat.dev/egommerce/identity-service/internal/service"
"github.com/gofiber/fiber/v2"
)
type AuthLoginRequest struct {
Username string `json:"username"`
Password string `json:"password"`
}
type AuthLoginResponse struct {
JWTToken string `json:"jwt_token"`
}
func (s *Server) LoginHandler(c *fiber.Ctx) error {
data := new(AuthLoginRequest)
if err := c.BodyParser(data); err != nil {
return err
}
token, err := service.AuthService.Login(data.Username, data.Password)
if err != nil {
return fiber.NewError(fiber.StatusUnauthorized)
}
cookie := service.AuthService.Cookie("auth_token", token)
c.Cookie(cookie)
return c.JSON(&AuthLoginResponse{JWTToken: token})
}

View File

@@ -0,0 +1,33 @@
package server
import (
"strings"
"github.com/gofiber/fiber/v2"
"git.pbiernat.dev/egommerce/go-api-pkg/fluentd"
)
// "github.com/gofiber/fiber/v2"
// "github.com/gofiber/fiber/v2/middleware/cors"
func SetupMiddleware(s *Server) {
s.Base.Use(defaultCORS)
s.Base.Use(LoggingMiddleware(s.Logger))
}
func LoggingMiddleware(log *fluentd.Logger) func(c *fiber.Ctx) error {
return func(c *fiber.Ctx) error {
path := string(c.Request().URI().Path())
if strings.Contains(path, "/health") {
return c.Next()
}
log.Log("Request: %s, remote: %s, via: %s",
c.Request().URI().String(),
c.Context().RemoteIP().String(),
string(c.Context().UserAgent()))
return c.Next()
}
}

View File

@@ -0,0 +1,26 @@
package server
import (
"github.com/gofiber/fiber/v2/middleware/cors"
)
var (
defaultCORS = cors.New(cors.Config{
AllowOrigins: "*",
AllowCredentials: true,
AllowMethods: "GET, POST, PATCH, PUT, DELETE, OPTIONS",
AllowHeaders: "Accept, Authorization, Content-Type, Vary, X-Request-Id",
})
)
func SetupRouter(s *Server) {
s.Base.Options("*", defaultCORS)
s.Base.Get("/health", s.HealthHandler)
s.Base.Get("/config", s.ConfigHandler)
api := s.Base.Group("/api")
v1 := api.Group("/v1")
v1.Post("/login", s.LoginHandler)
v1.All("/traefik", s.TraefikHandler)
}

View File

@@ -0,0 +1,184 @@
package server
import (
"bytes"
"context"
"encoding/json"
"log"
"os"
"strconv"
"time"
"github.com/go-redis/redis/v8"
"github.com/jackc/pgx/v5/pgxpool"
"git.pbiernat.dev/egommerce/go-api-pkg/consul"
"git.pbiernat.dev/egommerce/go-api-pkg/fluentd"
db "git.pbiernat.dev/egommerce/identity-service/pkg/database"
srv "git.pbiernat.dev/egommerce/identity-service/pkg/server"
cnf "git.pbiernat.dev/egommerce/identity-service/internal/config"
)
type (
Server struct {
Base *srv.Server
Config *cnf.Config
Cache *redis.Client
Database *pgxpool.Pool
Logger *fluentd.Logger
Registry *consul.Service
}
OptionFn func(*Server) error // FIXME: similar in worker
)
func New(c *cnf.Config, opts ...OptionFn) *Server {
svr := &Server{
Base: srv.New(c.Base),
Config: c,
}
for _, opt := range opts {
if err := opt(svr); err != nil {
log.Fatalf("Failed to attach extension to the server. Err: %v\n", err)
}
}
SetupMiddleware(svr)
SetupRouter(svr)
return svr
}
func WithCache(c *cnf.Config) OptionFn {
redis := redis.NewClient(&redis.Options{
Addr: c.CacheAddr,
Password: c.CachePassword,
DB: 0,
})
return func(s *Server) error {
s.Cache = redis
return nil
}
}
func WithDatabase(c *cnf.Config) OptionFn {
dbConn, err := db.Connect(c.DbURL)
if err != nil {
log.Fatalf("Failed to connect to the Database: %s. Err: %v\n", c.DbURL, err)
os.Exit(1)
}
return func(s *Server) error {
s.Database = dbConn
return nil
}
}
func WithLogger(c *cnf.Config) OptionFn {
return func(s *Server) error {
logHost, logPort, err := fluentd.ParseAddr(c.LoggerAddr)
if err != nil {
log.Fatalf("Failed to parse Fluentd address: %s. Err: %v", c.LoggerAddr, err)
}
logger, err := fluentd.NewLogger(c.Base.GetAppFullName(), logHost, logPort)
if err != nil {
log.Fatalf("Failed to connect to the Fluentd on %s:%d. Err: %v", logHost, logPort, err)
}
s.Logger = logger
return nil
}
}
func WithRegistry(c *cnf.Config) OptionFn {
return func(s *Server) error {
port, _ := strconv.Atoi(c.Base.NetAddr[1:]) // FIXME: can be IP:PORT which will cause error
registry, err := consul.NewService(c.RegistryAddr, c.Base.AppID, c.Base.AppName, c.Base.AppID, c.Base.AppName, c.Base.PathPrefix, port)
if err != nil {
log.Fatalf("Failed to connect to the Consul on: %s. Err: %v", c.RegistryAddr, err)
}
err = registry.Register()
if err != nil {
log.Fatalf("Failed to register in the Consul service. Err: %v", err)
}
s.Registry = registry
go func() { // Consul KV updater
ticker := time.NewTicker(time.Second * 15)
for range ticker.C {
fetchKVConfig(s) // FIXME: duplicated in worker
}
}()
go func() { // Server metadata cache updater
ticker := time.NewTicker(time.Second * 5)
for range ticker.C {
s.cacheMetadata()
}
}()
return nil
}
}
func (s *Server) Shutdown() srv.PurgeFn {
return func(srv *srv.Server) error {
s.Logger.Log("Server %s is going down...", s.Base.AppID)
s.Registry.Unregister()
s.clearMetadataCache()
s.Database.Close()
s.Logger.Log("Gone.")
s.Logger.Close()
return s.Base.Shutdown()
}
}
// @CHECK: merge s.Config and s.Base.Config to display all config as one array/map
func fetchKVConfig(s *Server) { // @FIXME: merge duplication in server.go and worker.go
config, _, err := s.Registry.KV().Get(s.Config.KVNamespace, nil)
if err != nil || config == nil {
return
}
kvCnf := bytes.NewBuffer(config.Value)
decoder := json.NewDecoder(kvCnf)
if err := decoder.Decode(&s.Config); err != nil {
return
}
}
func (s *Server) cacheMetadata() {
ctx := context.Background()
key, address := s.getMetadataIPsKey(), s.Base.Config.AppID
pos := s.Cache.LPos(ctx, key, address, redis.LPosArgs{}).Val()
if pos >= 0 {
s.Cache.LRem(ctx, key, 0, address)
}
s.Cache.LPush(ctx, key, address).Err()
}
func (s *Server) clearMetadataCache() {
ctx := context.Background()
key, address := s.getMetadataIPsKey(), s.Config.Base.AppID
s.Cache.LRem(ctx, key, 0, address)
}
func (s *Server) getMetadataIPsKey() string {
return "internal__" + s.Base.Config.AppName + "__ips"
}

View File

@@ -0,0 +1,31 @@
package server
import (
"net/http"
"git.pbiernat.dev/egommerce/identity-service/internal/service"
"github.com/gofiber/fiber/v2"
)
type TraefikAuthResponse struct {
Status string `json:"status,omitempty"`
Message string `json:"msg,omitempty"`
}
func (s *Server) TraefikHandler(c *fiber.Ctx) error {
cookie := service.AuthService.Cookie("traefik", "tmp-dummy-traefik-token")
c.Cookie(cookie)
s.Logger.Log("Traefik action set cookie. done.")
c.Response().Header.Add("Server", "identity-svc/traefik")
reqCookie := c.Request().Header.Cookie("basket_id")
s.Logger.Log("Request cookie: %v", reqCookie)
return c.
Status(http.StatusOK).
JSON(&TraefikAuthResponse{Status: "OK"})
// return c.
// Status(http.StatusUnauthorized).
// JSON(&TraefikAuthResponse{Message: "Access denied mf..."})
}