Compare commits
No commits in common. "dd43845138c0b13da87b784aba2bc5084bce4675" and "745a59ecaaef7730191cf6b7973bd2f41b65d5c8" have entirely different histories.
dd43845138
...
745a59ecaa
@ -1,6 +1,10 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import "github.com/haydenhargreaves/Potion/internal/app/server"
|
import (
|
||||||
|
"github.com/haydenhargreaves/Potion/internal/app/server"
|
||||||
|
"github.com/haydenhargreaves/Potion/internal/infrastructure/logging"
|
||||||
|
"github.com/haydenhargreaves/Potion/internal/infrastructure/logging/loggers"
|
||||||
|
)
|
||||||
|
|
||||||
const PORT = 3000
|
const PORT = 3000
|
||||||
|
|
||||||
@ -8,5 +12,8 @@ func main() {
|
|||||||
s := server.Init(PORT).Setup()
|
s := server.Init(PORT).Setup()
|
||||||
defer s.DB.Close()
|
defer s.DB.Close()
|
||||||
|
|
||||||
|
logger := loggers.NewConsoleLogger()
|
||||||
|
logger.Log(logging.LogLevelDebug, "%s", "Hello world")
|
||||||
|
|
||||||
s.Start()
|
s.Start()
|
||||||
}
|
}
|
||||||
|
|||||||
1
go.mod
1
go.mod
@ -24,7 +24,6 @@ require (
|
|||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/go-playground/validator/v10 v10.26.0 // indirect
|
github.com/go-playground/validator/v10 v10.26.0 // indirect
|
||||||
github.com/goccy/go-json v0.10.5 // indirect
|
github.com/goccy/go-json v0.10.5 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
||||||
github.com/kr/text v0.2.0 // indirect
|
github.com/kr/text v0.2.0 // indirect
|
||||||
|
|||||||
2
go.sum
2
go.sum
@ -57,8 +57,6 @@ github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVI
|
|||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
|
||||||
github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o=
|
github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o=
|
||||||
github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM=
|
github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM=
|
||||||
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
|
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
|
||||||
|
|||||||
@ -2,13 +2,13 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/golang-jwt/jwt/v5"
|
"github.com/golang-jwt/jwt/v5"
|
||||||
domain "github.com/haydenhargreaves/Potion/internal/domain/server"
|
domain "github.com/haydenhargreaves/Potion/internal/domain/server"
|
||||||
"github.com/haydenhargreaves/Potion/internal/infrastructure/logging"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// DepedencyInjectionMiddleware injects the dependencies into the context set. This is a middleware
|
// DepedencyInjectionMiddleware injects the dependencies into the context set. This is a middleware
|
||||||
@ -77,13 +77,14 @@ func JwtAuthMiddleWare(jwtSecretKey []byte) gin.HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func RecoveryMiddleware(logs []logging.Logger) gin.HandlerFunc {
|
func RecoveryMiddleware() gin.HandlerFunc {
|
||||||
|
|
||||||
return func(ctx *gin.Context) {
|
return func(ctx *gin.Context) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
// Log the panic with stack trace
|
// Log the panic with stack trace
|
||||||
err := fmt.Errorf("panic recovered: %v\n%s", r, debug.Stack())
|
err := fmt.Errorf("panic recovered: %v\n%s", r, debug.Stack())
|
||||||
logging.LogAll(logs, logging.LogLevelFatal, "[PANIC RECOVERY] %s\n", err)
|
log.Printf("[PANIC RECOVERY] %s", err)
|
||||||
|
|
||||||
ctx.JSON(http.StatusOK, gin.H{
|
ctx.JSON(http.StatusOK, gin.H{
|
||||||
"status": http.StatusOK,
|
"status": http.StatusOK,
|
||||||
|
|||||||
@ -3,12 +3,10 @@ package server
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/golang-jwt/jwt/v5"
|
"github.com/golang-jwt/jwt/v5"
|
||||||
domain "github.com/haydenhargreaves/Potion/internal/domain/server"
|
domain "github.com/haydenhargreaves/Potion/internal/domain/server"
|
||||||
"github.com/haydenhargreaves/Potion/internal/infrastructure/logging"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// JwtAuthMiddlewareV2 is responsible for protecting routes. Anything that may go wrong
|
// JwtAuthMiddlewareV2 is responsible for protecting routes. Anything that may go wrong
|
||||||
@ -66,9 +64,9 @@ func JwtAuthMiddlewareV2(jwtSecretKey []byte) gin.HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// JwtOptionalAuthMiddlewareV2 is responsible for collecting user data for routes where
|
// JwtOptionalAuthMiddlewareV2 is responsible for collecting user data for routes where
|
||||||
// authentication is optional. Meaning: if the use is not logged in, this function does
|
// authentication is optional. Meaning: if the use is not logged in, this function does
|
||||||
// not fail or return, it simply does nothing. But if the user is logged in, then the
|
// not fail or return, it simply does nothing. But if the user is logged in, then the
|
||||||
// 'userId' and 'userEmail' context values are set.
|
// 'userId' and 'userEmail' context values are set.
|
||||||
//
|
//
|
||||||
// e.g., `userIdAny, exists := ctx.Get("userId")`
|
// e.g., `userIdAny, exists := ctx.Get("userId")`
|
||||||
@ -98,34 +96,3 @@ func JwtOptionalAuthMiddlewareV2(jwtSecretKey []byte) gin.HandlerFunc {
|
|||||||
ctx.Next()
|
ctx.Next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoggingMiddleware(logs []logging.Logger) gin.HandlerFunc {
|
|
||||||
// TODO: Need traces using IDs?
|
|
||||||
return func(ctx *gin.Context) {
|
|
||||||
start := time.Now()
|
|
||||||
|
|
||||||
ctx.Next()
|
|
||||||
|
|
||||||
var (
|
|
||||||
status int = ctx.Writer.Status()
|
|
||||||
latency string = time.Since(start).String()
|
|
||||||
client string = ctx.ClientIP()
|
|
||||||
method string = ctx.Request.Method
|
|
||||||
path string = ctx.Request.URL.Path
|
|
||||||
)
|
|
||||||
|
|
||||||
// TODO: Add color to status
|
|
||||||
|
|
||||||
format := "%d | %-14s | %15s | %-9s \"%s\""
|
|
||||||
logging.LogAll(
|
|
||||||
logs,
|
|
||||||
logging.LogLevelInfo,
|
|
||||||
format,
|
|
||||||
status,
|
|
||||||
latency,
|
|
||||||
client,
|
|
||||||
method,
|
|
||||||
path,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -13,8 +13,6 @@ import (
|
|||||||
domain "github.com/haydenhargreaves/Potion/internal/domain/server"
|
domain "github.com/haydenhargreaves/Potion/internal/domain/server"
|
||||||
"github.com/haydenhargreaves/Potion/internal/infrastructure/auth"
|
"github.com/haydenhargreaves/Potion/internal/infrastructure/auth"
|
||||||
"github.com/haydenhargreaves/Potion/internal/infrastructure/database/repository"
|
"github.com/haydenhargreaves/Potion/internal/infrastructure/database/repository"
|
||||||
"github.com/haydenhargreaves/Potion/internal/infrastructure/logging"
|
|
||||||
"github.com/haydenhargreaves/Potion/internal/infrastructure/logging/loggers"
|
|
||||||
|
|
||||||
_ "github.com/lib/pq"
|
_ "github.com/lib/pq"
|
||||||
)
|
)
|
||||||
@ -25,24 +23,18 @@ type Server struct {
|
|||||||
config cors.Config
|
config cors.Config
|
||||||
DB *sql.DB
|
DB *sql.DB
|
||||||
deps domain.InjectedDependencies
|
deps domain.InjectedDependencies
|
||||||
logs []logging.Logger
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init initializes the server with the provided port. CORS settings are defined here.
|
// Init initializes the server with the provided port. CORS settings are defined here.
|
||||||
// A pointer to a server object is returned which allows for method chaining.
|
// A pointer to a server object is returned which allows for method chaining.
|
||||||
func Init(port int) *Server {
|
func Init(port int) *Server {
|
||||||
server := &Server{
|
server := &Server{
|
||||||
Router: gin.New(), // Not default anymore, to allow for custom logger
|
Router: gin.Default(),
|
||||||
port: port,
|
port: port,
|
||||||
config: cors.DefaultConfig(),
|
config: cors.DefaultConfig(),
|
||||||
logs: []logging.Logger{},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default logger which logs everything
|
|
||||||
server.logs = append(server.logs, loggers.NewConsoleLogger(logging.LogLevelTrace))
|
|
||||||
|
|
||||||
// Some stuff for templ rendering
|
// Some stuff for templ rendering
|
||||||
// TODO: Remove this
|
|
||||||
htmlRenderer := server.Router.HTMLRender
|
htmlRenderer := server.Router.HTMLRender
|
||||||
server.Router.HTMLRender = &gintemplrenderer.HTMLTemplRenderer{FallbackHtmlRenderer: htmlRenderer}
|
server.Router.HTMLRender = &gintemplrenderer.HTMLTemplRenderer{FallbackHtmlRenderer: htmlRenderer}
|
||||||
|
|
||||||
@ -62,27 +54,22 @@ func Init(port int) *Server {
|
|||||||
|
|
||||||
// Start starts the server on the port provided when the server was initialized
|
// Start starts the server on the port provided when the server was initialized
|
||||||
func (s *Server) Start() {
|
func (s *Server) Start() {
|
||||||
logging.LogAll(s.logs, logging.LogLevelDebug, "Server started on :%d\n", s.port)
|
|
||||||
s.Router.Run(fmt.Sprintf(":%d", s.port))
|
s.Router.Run(fmt.Sprintf(":%d", s.port))
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: (9/4/2025) Abstract these functions and cleanup. This is fucking messy...
|
// TODO: (9/4/2025) Abstract these functions and cleanup. This is fucking messy...
|
||||||
// TODO: (1/26/2026) Abstract these functions and cleanup. This is fucking messy... still
|
|
||||||
func (s *Server) Setup() *Server {
|
func (s *Server) Setup() *Server {
|
||||||
// SETUP THE ENVIRONMENT CONFIGURATION
|
// SETUP THE ENVIRONMENT CONFIGURATION
|
||||||
cfg, err := domain.LoadEnvironment(s.logs)
|
cfg, err := domain.LoadEnvironment()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logging.LogAll(s.logs, logging.LogLevelFatal, err.Error())
|
|
||||||
panic(err.Error())
|
panic(err.Error())
|
||||||
}
|
}
|
||||||
if cfg == nil {
|
if cfg == nil {
|
||||||
logging.LogAll(s.logs, logging.LogLevelFatal, "Environment configuration is nil, crashing.")
|
|
||||||
panic("Environment configuration is nil, crashing.")
|
panic("Environment configuration is nil, crashing.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Using release on them all? Def need to clean this shitty environment up
|
|
||||||
if cfg.Environment == "dev" {
|
if cfg.Environment == "dev" {
|
||||||
gin.SetMode(gin.ReleaseMode)
|
gin.SetMode(gin.DebugMode)
|
||||||
} else if cfg.Environment == "prod" {
|
} else if cfg.Environment == "prod" {
|
||||||
gin.SetMode(gin.ReleaseMode)
|
gin.SetMode(gin.ReleaseMode)
|
||||||
} else {
|
} else {
|
||||||
@ -116,24 +103,6 @@ func (s *Server) Setup() *Server {
|
|||||||
|
|
||||||
s.DB = db
|
s.DB = db
|
||||||
|
|
||||||
// TODO: Implement environment here for logging file
|
|
||||||
path := "./logs.log"
|
|
||||||
|
|
||||||
fileLogger, cleanup, err := loggers.NewFileLogger(path, logging.LogLevelDebug)
|
|
||||||
if err != nil {
|
|
||||||
logging.LogAll(s.logs, logging.LogLevelWarning, "Failed to create file logger. %s\n", err.Error())
|
|
||||||
} else {
|
|
||||||
s.logs = append(s.logs, fileLogger)
|
|
||||||
defer cleanup()
|
|
||||||
}
|
|
||||||
|
|
||||||
databaseLogger, err := loggers.NewDatabaseLogger(s.DB, "logs", logging.LogLevelInfo)
|
|
||||||
if err != nil {
|
|
||||||
logging.LogAll(s.logs, logging.LogLevelWarning, "Failed to create database logger. %s\n", err.Error())
|
|
||||||
} else {
|
|
||||||
s.logs = append(s.logs, databaseLogger)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SETUP JWT
|
// SETUP JWT
|
||||||
jwtSecret := []byte(cfg.JwtSecret)
|
jwtSecret := []byte(cfg.JwtSecret)
|
||||||
|
|
||||||
@ -142,7 +111,7 @@ func (s *Server) Setup() *Server {
|
|||||||
recipeRepo := repository.NewRecipeRepository(s.DB)
|
recipeRepo := repository.NewRecipeRepository(s.DB)
|
||||||
engagementRepo := repository.NewEngagementRepository(s.DB)
|
engagementRepo := repository.NewEngagementRepository(s.DB)
|
||||||
userService := service.NewUserService(userRepo)
|
userService := service.NewUserService(userRepo)
|
||||||
authService := service.NewAuthService(userRepo, jwtSecret, s.logs)
|
authService := service.NewAuthService(userRepo, jwtSecret)
|
||||||
recipeService := service.NewRecipeService(recipeRepo, engagementRepo)
|
recipeService := service.NewRecipeService(recipeRepo, engagementRepo)
|
||||||
engagementService := service.NewEngagementService(engagementRepo, recipeRepo)
|
engagementService := service.NewEngagementService(engagementRepo, recipeRepo)
|
||||||
|
|
||||||
@ -155,8 +124,9 @@ func (s *Server) Setup() *Server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Apply middleware
|
// Apply middleware
|
||||||
// TODO: Review the recovery middleware
|
s.Router.Use(RecoveryMiddleware())
|
||||||
s.Router.Use(gin.Recovery(), RecoveryMiddleware(s.logs), LoggingMiddleware(s.logs))
|
// NOTE: No longer running on every connection!
|
||||||
|
// s.Router.Use(JwtAuthMiddleWare(jwtSecret))
|
||||||
|
|
||||||
// Redirect index to home page: Update this as needed
|
// Redirect index to home page: Update this as needed
|
||||||
s.Router.GET("/", func(ctx *gin.Context) { ctx.Redirect(http.StatusSeeOther, domain.WEB_HOME) })
|
s.Router.GET("/", func(ctx *gin.Context) { ctx.Redirect(http.StatusSeeOther, domain.WEB_HOME) })
|
||||||
@ -249,21 +219,14 @@ func (s *Server) Setup() *Server {
|
|||||||
router_api_v2.GET("/user/recipes/made", JwtAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.GetAuthenicatedUserMadeRecipesV2)
|
router_api_v2.GET("/user/recipes/made", JwtAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.GetAuthenicatedUserMadeRecipesV2)
|
||||||
router_api_v2.GET("/user/recipes/viewed", JwtAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.GetAuthenicatedUserViewedRecipesV2)
|
router_api_v2.GET("/user/recipes/viewed", JwtAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.GetAuthenicatedUserViewedRecipesV2)
|
||||||
|
|
||||||
|
router_api_v2.GET("/protected", JwtAuthMiddlewareV2([]byte(cfg.JwtSecret)), func(ctx *gin.Context) {
|
||||||
|
ctx.JSON(http.StatusOK, gin.H{"msg": "YAY"})
|
||||||
|
})
|
||||||
|
|
||||||
router_api_v2.POST("/engagement/view/:id", JwtOptionalAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.EngagementViewRecipeHandlerV2)
|
router_api_v2.POST("/engagement/view/:id", JwtOptionalAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.EngagementViewRecipeHandlerV2)
|
||||||
router_api_v2.POST("/engagement/share/:id", JwtOptionalAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.EngagementShareRecipeHandlerV2)
|
router_api_v2.POST("/engagement/share/:id", JwtOptionalAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.EngagementShareRecipeHandlerV2)
|
||||||
router_api_v2.POST("/engagement/favorite/:id", JwtAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.EngagementFavoriteRecipeHandlerV2)
|
router_api_v2.POST("/engagement/favorite/:id", JwtAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.EngagementFavoriteRecipeHandlerV2)
|
||||||
router_api_v2.POST("/engagement/make/:id", JwtAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.EngagementMakeRecipeHandlerV2)
|
router_api_v2.POST("/engagement/make/:id", JwtAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.EngagementMakeRecipeHandlerV2)
|
||||||
|
|
||||||
if cfg.Environment == "dev" {
|
|
||||||
s.debugDisplayRoutes()
|
|
||||||
}
|
|
||||||
|
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) debugDisplayRoutes() {
|
|
||||||
for _, route := range s.Router.Routes() {
|
|
||||||
format := "%-8s %s"
|
|
||||||
logging.LogAll(s.logs, logging.LogLevelDebug, format, route.Method, route.Path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -10,7 +10,6 @@ import (
|
|||||||
domainServer "github.com/haydenhargreaves/Potion/internal/domain/server"
|
domainServer "github.com/haydenhargreaves/Potion/internal/domain/server"
|
||||||
domain "github.com/haydenhargreaves/Potion/internal/domain/user"
|
domain "github.com/haydenhargreaves/Potion/internal/domain/user"
|
||||||
"github.com/haydenhargreaves/Potion/internal/infrastructure/auth"
|
"github.com/haydenhargreaves/Potion/internal/infrastructure/auth"
|
||||||
"github.com/haydenhargreaves/Potion/internal/infrastructure/logging"
|
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -32,7 +31,6 @@ import (
|
|||||||
type AuthService struct {
|
type AuthService struct {
|
||||||
userRepository domain.UserRepository
|
userRepository domain.UserRepository
|
||||||
jwtSecret []byte
|
jwtSecret []byte
|
||||||
logs []logging.Logger
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compile-time check to ensure the AuthService implements domain.AuthService
|
// Compile-time check to ensure the AuthService implements domain.AuthService
|
||||||
@ -40,11 +38,10 @@ var _ domainAuth.AuthService = (*AuthService)(nil)
|
|||||||
|
|
||||||
// NewAuthService creates a user service object which can be passed into the context. The service
|
// NewAuthService creates a user service object which can be passed into the context. The service
|
||||||
// requires a user repository which it will use to hit the database when needed.
|
// requires a user repository which it will use to hit the database when needed.
|
||||||
func NewAuthService(userRepository domain.UserRepository, jwtSecret []byte, logs []logging.Logger) domainAuth.AuthService {
|
func NewAuthService(userRepository domain.UserRepository, jwtSecret []byte) domainAuth.AuthService {
|
||||||
return &AuthService{
|
return &AuthService{
|
||||||
userRepository: userRepository,
|
userRepository: userRepository,
|
||||||
jwtSecret: jwtSecret,
|
jwtSecret: jwtSecret,
|
||||||
logs: logs,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,53 +124,3 @@ func generateJwt(userId int, email string, jwtSecret []byte) (string, error) {
|
|||||||
|
|
||||||
return tokenString, nil
|
return tokenString, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// import (
|
|
||||||
// "bytes"
|
|
||||||
// "time"
|
|
||||||
//
|
|
||||||
// "github.com/gin-gonic/gin"
|
|
||||||
// "github.com/google/uuid"
|
|
||||||
// "golang.org/x/exp/slog" // or your logging.Logger
|
|
||||||
// )
|
|
||||||
//
|
|
||||||
// // LoggerMiddleware logs HTTP requests with structured fields
|
|
||||||
// func LoggerMiddleware(logger *slog.Logger) gin.HandlerFunc {
|
|
||||||
// return func(c *gin.Context) {
|
|
||||||
// // Generate request ID for tracing
|
|
||||||
// reqID := uuid.New().String()
|
|
||||||
// start := time.Now()
|
|
||||||
//
|
|
||||||
// // Capture request body (if enabled)
|
|
||||||
// var reqBody []byte
|
|
||||||
// if c.Request.ContentLength > 0 && c.Request.Body != nil {
|
|
||||||
// reqBody, _ = io.ReadAll(c.Request.Body)
|
|
||||||
// c.Request.Body.Close()
|
|
||||||
// c.Request.Body = io.NopCloser(bytes.NewBuffer(reqBody))
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // Log request start
|
|
||||||
// logger.Info("request started",
|
|
||||||
// slog.String("req_id", reqID),
|
|
||||||
// slog.String("method", c.Request.Method),
|
|
||||||
// slog.String("path", c.Request.URL.Path),
|
|
||||||
// slog.Int("content_length", int(c.Request.ContentLength)),
|
|
||||||
// slog.String("user_agent", c.Request.UserAgent()),
|
|
||||||
// )
|
|
||||||
//
|
|
||||||
// // Process request
|
|
||||||
// c.Next()
|
|
||||||
//
|
|
||||||
// // Log request completion
|
|
||||||
// duration := time.Since(start)
|
|
||||||
// logger.Info("request completed",
|
|
||||||
// slog.String("req_id", reqID),
|
|
||||||
// slog.Int("status", c.Writer.Status()),
|
|
||||||
// slog.String("method", c.Request.Method),
|
|
||||||
// slog.String("path", c.Request.URL.Path),
|
|
||||||
// slog.Duration("duration", duration),
|
|
||||||
// slog.Int("size", c.Writer.Size()),
|
|
||||||
// slog.Any("req_body", string(reqBody)), // truncate if too big
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|||||||
@ -10,7 +10,6 @@ import (
|
|||||||
domainEngagement "github.com/haydenhargreaves/Potion/internal/domain/engagement"
|
domainEngagement "github.com/haydenhargreaves/Potion/internal/domain/engagement"
|
||||||
domainRecipe "github.com/haydenhargreaves/Potion/internal/domain/recipe"
|
domainRecipe "github.com/haydenhargreaves/Potion/internal/domain/recipe"
|
||||||
domainUser "github.com/haydenhargreaves/Potion/internal/domain/user"
|
domainUser "github.com/haydenhargreaves/Potion/internal/domain/user"
|
||||||
"github.com/haydenhargreaves/Potion/internal/infrastructure/logging"
|
|
||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -56,10 +55,10 @@ func IsLoggedIn(ctx *gin.Context) bool {
|
|||||||
// the event that required fields are not provided, an error will return and the caller should handle
|
// the event that required fields are not provided, an error will return and the caller should handle
|
||||||
// the missing value or panic. Toggles between 'dev', 'prod', etc are also handled by this method,
|
// the missing value or panic. Toggles between 'dev', 'prod', etc are also handled by this method,
|
||||||
// the values can be access assuming they are the proper values based on the provided environment.
|
// the values can be access assuming they are the proper values based on the provided environment.
|
||||||
func LoadEnvironment(logs []logging.Logger) (*EnvironmentConfig, error) {
|
func LoadEnvironment() (*EnvironmentConfig, error) {
|
||||||
err := godotenv.Load(".env")
|
err := godotenv.Load(".env")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logging.LogAll(logs, logging.LogLevelWarning, "No .env file found or error loading .env: %v. Relying on system environment variables.", err)
|
fmt.Printf("No .env file found or error loading .env: %v. Relying on system environment variables.", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
env := os.Getenv("ENVIRONMENT")
|
env := os.Getenv("ENVIRONMENT")
|
||||||
@ -131,7 +130,7 @@ func LoadEnvironment(logs []logging.Logger) (*EnvironmentConfig, error) {
|
|||||||
FrontendDomain: frontendDomain,
|
FrontendDomain: frontendDomain,
|
||||||
}
|
}
|
||||||
|
|
||||||
logging.LogAll(logs, logging.LogLevelDebug, "Environment Config: %+v\n", cfg)
|
fmt.Printf("Environment Config: %+v\n", cfg)
|
||||||
|
|
||||||
return cfg, nil
|
return cfg, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +0,0 @@
|
|||||||
-- Author: Hayden Hargreaves (hhargreaves2006@gmail.com)
|
|
||||||
-- Desc: Create the logs table.
|
|
||||||
-- Date: 01/23/2026
|
|
||||||
|
|
||||||
BEGIN;
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS Logs (
|
|
||||||
Id SERIAL PRIMARY KEY NOT NULL,
|
|
||||||
Level TEXT NOT NULL CHECK (level IN ('TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL')),
|
|
||||||
Message TEXT NOT NULL,
|
|
||||||
Created TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
||||||
);
|
|
||||||
|
|
||||||
COMMIT;
|
|
||||||
@ -13,5 +13,4 @@ psql -h "$PSQL_HOST" -U "$PSQL_USERNAME" -d "$PSQL_DATABASE" -f ./internal/infra
|
|||||||
psql -h "$PSQL_HOST" -U "$PSQL_USERNAME" -d "$PSQL_DATABASE" -f ./internal/infrastructure/database/migrations/011_update_engagement_enum.sql
|
psql -h "$PSQL_HOST" -U "$PSQL_USERNAME" -d "$PSQL_DATABASE" -f ./internal/infrastructure/database/migrations/011_update_engagement_enum.sql
|
||||||
|
|
||||||
psql -h "$PSQL_HOST" -U "$PSQL_USERNAME" -d "$PSQL_DATABASE" -f ./internal/infrastructure/database/migrations/013_update_recipes_allow_large_servings.sql
|
psql -h "$PSQL_HOST" -U "$PSQL_USERNAME" -d "$PSQL_DATABASE" -f ./internal/infrastructure/database/migrations/013_update_recipes_allow_large_servings.sql
|
||||||
psql -h "$PSQL_HOST" -U "$PSQL_USERNAME" -d "$PSQL_DATABASE" -f ./internal/infrastructure/database/migrations/014_create_logs_table.sql
|
|
||||||
|
|
||||||
|
|||||||
@ -3,61 +3,14 @@ package logging
|
|||||||
type LogLevel string
|
type LogLevel string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
LogLevelTrace LogLevel = "TRACE"
|
LogLevelTrace LogLevel = "TRACE"
|
||||||
LogLevelDebug LogLevel = "DEBUG"
|
LogLevelDebug LogLevel = "DEBUG"
|
||||||
LogLevelInfo LogLevel = "INFO"
|
LogLevelInformation LogLevel = "INFORMATION"
|
||||||
LogLevelWarning LogLevel = "WARN"
|
LogLevelWarning LogLevel = "WARNING"
|
||||||
LogLevelError LogLevel = "ERROR"
|
LogLevelError LogLevel = "ERROR"
|
||||||
LogLevelFatal LogLevel = "FATAL"
|
LogLevelFatal LogLevel = "FATAL"
|
||||||
)
|
|
||||||
|
|
||||||
// MatchFilter is called on a filter (l) with a target level to match on the filter. Match
|
|
||||||
// means returning true of the target is greater than OR EQUAL TO the filter level. They order
|
|
||||||
// by scale of magnitude.
|
|
||||||
func (filter LogLevel) MatchFilter(target LogLevel) bool {
|
|
||||||
// Define severity levels (higher number = more severe)
|
|
||||||
severity := map[LogLevel]int{
|
|
||||||
LogLevelTrace: 0,
|
|
||||||
LogLevelDebug: 1,
|
|
||||||
LogLevelInfo: 2,
|
|
||||||
LogLevelWarning: 3,
|
|
||||||
LogLevelError: 4,
|
|
||||||
LogLevelFatal: 5,
|
|
||||||
}
|
|
||||||
|
|
||||||
filterSeverity, filterOk := severity[filter]
|
|
||||||
targetSeverity, targetOk := severity[target]
|
|
||||||
|
|
||||||
if !filterOk || !targetOk {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return targetSeverity >= filterSeverity
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Background colors
|
|
||||||
BgBlack = "\033[40m"
|
|
||||||
BgRed = "\033[41m"
|
|
||||||
BgGreen = "\033[42m"
|
|
||||||
BgYellow = "\033[43m"
|
|
||||||
BgBlue = "\033[44m"
|
|
||||||
BgMagenta = "\033[45m"
|
|
||||||
BgCyan = "\033[46m"
|
|
||||||
BgWhite = "\033[47m"
|
|
||||||
|
|
||||||
// Reset
|
|
||||||
Reset = "\033[0m"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Logger interface {
|
type Logger interface {
|
||||||
Log(level LogLevel, format string, v ...any)
|
Log(level LogLevel, format string, v ...any)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LogAll takes all of the inputs for a single logger and executes the logging operation
|
|
||||||
// on each of the loggers (logs) provided. This is just a convince function.
|
|
||||||
func LogAll(logs []Logger, level LogLevel, format string, v ...any) {
|
|
||||||
for _, log := range logs {
|
|
||||||
log.Log(level, format, v...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -4,56 +4,29 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/haydenhargreaves/Potion/internal/infrastructure/logging"
|
"github.com/haydenhargreaves/Potion/internal/infrastructure/logging"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TODO: Implement the Logger interface
|
||||||
|
|
||||||
type ConsoleLogger struct {
|
type ConsoleLogger struct {
|
||||||
writer io.Writer
|
writer io.Writer
|
||||||
filter logging.LogLevel
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ logging.Logger = (*ConsoleLogger)(nil)
|
var _ logging.Logger = (*ConsoleLogger)(nil)
|
||||||
|
|
||||||
// NewConsoleLogger creates a new logger which writes directly to standard out (stdout).
|
func NewConsoleLogger() ConsoleLogger {
|
||||||
func NewConsoleLogger(filter logging.LogLevel) logging.Logger {
|
|
||||||
return &ConsoleLogger{
|
return ConsoleLogger{
|
||||||
writer: os.Stdout,
|
writer: os.Stdout,
|
||||||
filter: filter,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// formatLevelString converts a log level string (level) into a new, formatted output string.
|
|
||||||
// This also includes color, if the shell supports it. Otherwise, the rendering may appear odd.
|
|
||||||
func formatLevelString(level logging.LogLevel) string {
|
|
||||||
switch level {
|
|
||||||
case logging.LogLevelTrace:
|
|
||||||
return fmt.Sprintf("%s[%s]%s", logging.BgMagenta, level, logging.Reset)
|
|
||||||
case logging.LogLevelDebug:
|
|
||||||
return fmt.Sprintf("%s[%s]%s", logging.BgBlue, level, logging.Reset)
|
|
||||||
case logging.LogLevelInfo:
|
|
||||||
return fmt.Sprintf("%s[%s]%s", logging.BgGreen, level, logging.Reset)
|
|
||||||
case logging.LogLevelWarning:
|
|
||||||
return fmt.Sprintf("%s[%s]%s", logging.BgYellow, level, logging.Reset)
|
|
||||||
case logging.LogLevelError:
|
|
||||||
return fmt.Sprintf("%s[%s]%s", logging.BgRed, level, logging.Reset)
|
|
||||||
case logging.LogLevelFatal:
|
|
||||||
return fmt.Sprintf("%s[%s]%s", logging.BgRed, level, logging.Reset)
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("[%s]", level)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log implements the interface.
|
|
||||||
func (l *ConsoleLogger) Log(level logging.LogLevel, format string, v ...any) {
|
func (l *ConsoleLogger) Log(level logging.LogLevel, format string, v ...any) {
|
||||||
// level is too low, do not log
|
prefix := fmt.Appendf(nil, "[%s] ", level)
|
||||||
if !l.filter.MatchFilter(level) {
|
bytes := fmt.Appendf(prefix, format, v...)
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
timestamp := time.Now().UTC().Format("01/02/2006 - 15:04:05")
|
// WARN: Do we need to worry about errors?
|
||||||
levelStr := formatLevelString(level)
|
_, _ = l.writer.Write(bytes)
|
||||||
fullFormat := fmt.Sprintf("%-18s %s | %s\n", levelStr, timestamp, format)
|
|
||||||
bytes := fmt.Appendf(nil, fullFormat, v...)
|
|
||||||
l.writer.Write(bytes)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,77 +0,0 @@
|
|||||||
package loggers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/haydenhargreaves/Potion/internal/infrastructure/logging"
|
|
||||||
)
|
|
||||||
|
|
||||||
type DatabaseLogger struct {
|
|
||||||
db *sql.DB
|
|
||||||
table string
|
|
||||||
filter logging.LogLevel
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ logging.Logger = (*DatabaseLogger)(nil)
|
|
||||||
|
|
||||||
func NewDatabaseLogger(conn *sql.DB, table string, filter logging.LogLevel) (logging.Logger, error) {
|
|
||||||
if conn == nil {
|
|
||||||
return &DatabaseLogger{}, fmt.Errorf("Connection is nil, something is very wrong.")
|
|
||||||
}
|
|
||||||
// Ensure the DB is open
|
|
||||||
if err := conn.Ping(); err != nil {
|
|
||||||
return &DatabaseLogger{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure the table exists
|
|
||||||
exists, err := tableExists(conn, table)
|
|
||||||
if err != nil {
|
|
||||||
return &DatabaseLogger{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !exists {
|
|
||||||
return &DatabaseLogger{}, fmt.Errorf("Database table '%s' does not exist on provided connection.", table)
|
|
||||||
}
|
|
||||||
|
|
||||||
logger := &DatabaseLogger{
|
|
||||||
db: conn,
|
|
||||||
table: table,
|
|
||||||
filter: filter,
|
|
||||||
}
|
|
||||||
|
|
||||||
return logger, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// tableExists queries a database connection and returns whether the table name provided
|
|
||||||
// exists on the table.
|
|
||||||
func tableExists(conn *sql.DB, tableName string) (bool, error) {
|
|
||||||
var exists bool
|
|
||||||
err := conn.QueryRow(`
|
|
||||||
SELECT EXISTS (
|
|
||||||
SELECT FROM information_schema.tables
|
|
||||||
WHERE table_schema = 'public'
|
|
||||||
AND table_name = $1
|
|
||||||
)`,
|
|
||||||
tableName).Scan(&exists)
|
|
||||||
return exists, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log implements the interface.
|
|
||||||
func (l *DatabaseLogger) Log(level logging.LogLevel, format string, v ...any) {
|
|
||||||
// level is too low, do not log
|
|
||||||
if !l.filter.MatchFilter(level) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
message := fmt.Sprintf(format, v...)
|
|
||||||
query := "INSERT INTO logs (level, message) VALUES ($1, $2);"
|
|
||||||
|
|
||||||
// Ignoring result and error, cuz what the hell would we do with them lol
|
|
||||||
_, err := l.db.Exec(query, level, message)
|
|
||||||
|
|
||||||
// TODO: Remove
|
|
||||||
if err != nil {
|
|
||||||
println(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,58 +1,3 @@
|
|||||||
package loggers
|
package loggers
|
||||||
|
|
||||||
import (
|
// TODO: Implement the Logger interface
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/haydenhargreaves/Potion/internal/infrastructure/logging"
|
|
||||||
)
|
|
||||||
|
|
||||||
type FileLogger struct {
|
|
||||||
writer io.Writer
|
|
||||||
filter logging.LogLevel
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ logging.Logger = (*FileLogger)(nil)
|
|
||||||
|
|
||||||
// NewFileLogger creates a new file logger, opened on the filepath provided. If any errors
|
|
||||||
// occur, an error will be returned, along with an EMPTY logger. This is not a pointer return
|
|
||||||
// so it will never be nil, just empty.
|
|
||||||
//
|
|
||||||
// This function does not close the file, cleanup function that is returned should be called
|
|
||||||
// to close the file opened in this function.
|
|
||||||
func NewFileLogger(filepath string, filter logging.LogLevel) (logging.Logger, func() error, error) {
|
|
||||||
f, err := os.OpenFile(filepath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644)
|
|
||||||
if err != nil {
|
|
||||||
return &FileLogger{}, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if f == nil {
|
|
||||||
return &FileLogger{}, nil, fmt.Errorf("File could not be opened. File is nil.")
|
|
||||||
}
|
|
||||||
|
|
||||||
logger := &FileLogger{
|
|
||||||
writer: f,
|
|
||||||
filter: filter,
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup := func() error {
|
|
||||||
return f.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
return logger, cleanup, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log implements the interface.
|
|
||||||
func (l *FileLogger) Log(level logging.LogLevel, format string, v ...any) {
|
|
||||||
// level is too low, do not log
|
|
||||||
if !l.filter.MatchFilter(level) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
timestamp := time.Now().UTC().Format("01/02/2006 - 15:04:05")
|
|
||||||
fullFormat := fmt.Sprintf("%-13s %s | %s\n", "["+level+"]", timestamp, format)
|
|
||||||
bytes := fmt.Appendf(nil, fullFormat, v...)
|
|
||||||
l.writer.Write(bytes)
|
|
||||||
}
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user