From 65c73bddfa3ae7837221fe27dbe467cd1cf921d6 Mon Sep 17 00:00:00 2001 From: Hayden Hargreaves Date: Tue, 22 Jul 2025 21:37:47 -0700 Subject: [PATCH 1/3] (FIX): Working on finding all of the nil-deref errors. This may have caught a few. Next I need to actually work on the development/testing/production environment setup. --- internal/app/handlers/auth_handler.go | 2 + internal/app/handlers/page_handler.go | 32 +---- internal/app/handlers/recipe_handler.go | 4 + internal/app/server/server.go | 16 --- .../database/repository/user_repository.go | 5 + internal/templates/components/navbar_templ.go | 2 +- internal/templates/pages/favorites.templ | 121 +++++++++--------- internal/templates/pages/favorites_templ.go | 12 +- internal/templates/pages/recipe.templ | 8 +- internal/templates/pages/recipe_templ.go | 26 +++- 10 files changed, 106 insertions(+), 122 deletions(-) diff --git a/internal/app/handlers/auth_handler.go b/internal/app/handlers/auth_handler.go index 2da9eff..ebdf7bc 100644 --- a/internal/app/handlers/auth_handler.go +++ b/internal/app/handlers/auth_handler.go @@ -61,5 +61,7 @@ func GoogleCallback(ctx *gin.Context) { func Logout(ctx *gin.Context) { // TODO: Use same values as the GoogleCallback function ctx.SetCookie("jwt_token", "", -1, "/", "", false, true) // TODO: Update settings + ctx.SetCookie("search-filters", "", -1, "/", "", false, true) + ctx.Redirect(http.StatusSeeOther, domain.WEB_HOME) } diff --git a/internal/app/handlers/page_handler.go b/internal/app/handlers/page_handler.go index e9e0f7a..cf90ba2 100644 --- a/internal/app/handlers/page_handler.go +++ b/internal/app/handlers/page_handler.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "net/http" + "os" "strconv" "github.com/a-h/templ" @@ -82,19 +83,6 @@ func FavoritesPage(ctx *gin.Context) { } } - // Else, get the user's favorites - // BUG: Depreciated, not displaying a list, using search to drive this page as well - // deps := ctx.MustGet("deps").(*domainServer.InjectedDependencies) - // userId := ctx.MustGet("userId").(int) - // recipes, err := deps.RecipeService.GetUserFavoriteRecipes(userId) - // if err != nil { - // ctx.JSON(http.StatusInternalServerError, gin.H{ - // "status": http.StatusInternalServerError, - // "message": fmt.Sprintf("Error getting favorites. %s\n", err.Error()), - // }) - // return - // } - ctx.HTML(http.StatusOK, "", layouts.AppLayout(title, page)) } @@ -200,24 +188,8 @@ func RecipePage(ctx *gin.Context) { return } - // Add engagement - // BUG: Don't want to do this here - // if loggedIn { - // if _, err = deps.EngagementService.UserViewRecipe(*userId, recipe.Id); err != nil { - // fmt.Printf("ERROR: %s\n", err.Error()) - // ctx.JSON(400, err.Error()) - // return - // } - // } else { - // if _, err = deps.EngagementService.ViewRecipe(recipe.Id); err != nil { - // fmt.Printf("ERROR: %s\n", err.Error()) - // ctx.JSON(400, err.Error()) - // return - // } - // } - title := "Potion - View Recipe" - page := pages.RecipePage(*recipe, *user, loggedIn) + page := pages.RecipePage(*recipe, *user, loggedIn, os.Getenv("DOMAIN")) ctx.HTML(http.StatusOK, "", layouts.AppLayout(title, page)) } diff --git a/internal/app/handlers/recipe_handler.go b/internal/app/handlers/recipe_handler.go index a37ffc2..681e36f 100644 --- a/internal/app/handlers/recipe_handler.go +++ b/internal/app/handlers/recipe_handler.go @@ -121,6 +121,10 @@ func SearchRecipesFavorites(ctx *gin.Context) { // TODO: Error here if they're not logged in? // Get user data (they should be logged in) + if !domain.IsLoggedIn(ctx) { + ctx.JSON(http.StatusOK, gin.H{"error": "User is not logged in. User will be nil."}) + } + userId := ctx.MustGet("userId").(int) recipes, err := deps.RecipeService.SearchRecipes(filters, &userId, true) diff --git a/internal/app/server/server.go b/internal/app/server/server.go index 9863e17..a921e6d 100644 --- a/internal/app/server/server.go +++ b/internal/app/server/server.go @@ -188,22 +188,6 @@ func (s *Server) Setup() *Server { router_api.GET("/user/recipes", handlers.GetUserRecipes) router_api.GET("/user/favorites", handlers.GetUserFavoriteRecipes) - router_api.GET("/user/temp", func(ctx *gin.Context) { - recipes, err := recipeService.GetUserMadeRecipes(3, 6) - - if err != nil { - ctx.JSON(http.StatusBadRequest, gin.H{ - "recipes": recipes, - "error": err.Error(), - }) - } else { - ctx.JSON(http.StatusBadRequest, gin.H{ - "recipes": recipes, - "error": "", - }) - } - }) - // Engagement endpoints router_api.POST("/engagement/view/:id", handlers.EngagementViewRecipe) router_api.POST("/engagement/share/:id", handlers.EngagementShareRecipe) diff --git a/internal/infrastructure/database/repository/user_repository.go b/internal/infrastructure/database/repository/user_repository.go index 1bf0b97..b593d3d 100644 --- a/internal/infrastructure/database/repository/user_repository.go +++ b/internal/infrastructure/database/repository/user_repository.go @@ -2,6 +2,7 @@ package repository import ( "database/sql" + "fmt" domain "github.com/haydenhargreaves/Potion/internal/domain/user" _ "github.com/lib/pq" @@ -35,6 +36,10 @@ func (r *UserRepository) CreateGoogleUser(googleUserInfo *domain.GoogleUserInfo, return domain.User{}, err } + if googleUserInfo == nil { + return domain.User{}, fmt.Errorf("Google user info provided was nil") + } + var user domain.User query := `INSERT INTO users (GoogleId, Name, Email, ImageUrl, GoogleRefreshToken) diff --git a/internal/templates/components/navbar_templ.go b/internal/templates/components/navbar_templ.go index 0ae98db..9a3c7c2 100644 --- a/internal/templates/components/navbar_templ.go +++ b/internal/templates/components/navbar_templ.go @@ -211,7 +211,7 @@ func Navbar(current string) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "\">

Potion

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "\">

Potion

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/internal/templates/pages/favorites.templ b/internal/templates/pages/favorites.templ index 230931c..7ddc99e 100644 --- a/internal/templates/pages/favorites.templ +++ b/internal/templates/pages/favorites.templ @@ -6,68 +6,73 @@ import "github.com/haydenhargreaves/Potion/internal/domain/recipe" import domainServer "github.com/haydenhargreaves/Potion/internal/domain/server" templ FavoriteList(recipes []domain.Recipe) { -
- for _, recipe := range recipes { - @favoriteResult(recipe) - } - if len(recipes) == 0 || recipes == nil { -

No results

- } else { -

End of results

- } -
+
+ for _, recipe := range recipes { + @favoriteResult(recipe) + } + if len(recipes) == 0 || recipes == nil { +

No results

+ } else { +

End of results

+ } +
} templ favoriteResult(recipe domain.Recipe) { -
- -
-
-
-

- { recipe.Title } -

-
- - @timeIconSm() - { recipe.Duration.Total } min - - - for _ = range(recipe.Difficulty) { - @starIconSm(true) - } - for _ = range(5 - recipe.Difficulty) { - @starIconSm(false) - } - - - @servingIconSm() - Serves { recipe.Serves } - -
-
- -
-

{ recipe.Description }

-
-
+
+ +
+
+
+

+ { recipe.Title } +

+
+ + @timeIconSm() + { recipe.Duration.Total } min + + + for _ = range(recipe.Difficulty) { + @starIconSm(true) + } + for _ = range(5 - recipe.Difficulty) { + @starIconSm(false) + } + + + @servingIconSm() + Serves { recipe.Serves } + +
+
+ +
+

{ recipe.Description }

+
+
} templ FavoritesPage(filters domain.SearchFilters) { -@components.Navbar("favorites") -
-
- @components.BannerText("Favorites") - @components.SearchBar(filters, false, true, true) -
- @FavoriteList(nil) -
-
+ @components.Navbar("favorites") +
+
+ @components.BannerText("Favorites") + @components.SearchBar(filters, false, true, true) +
+ @FavoriteList(nil) +
+
} diff --git a/internal/templates/pages/favorites_templ.go b/internal/templates/pages/favorites_templ.go index 7f56398..790cfd8 100644 --- a/internal/templates/pages/favorites_templ.go +++ b/internal/templates/pages/favorites_templ.go @@ -91,7 +91,7 @@ func favoriteResult(recipe domain.Recipe) templ.Component { var templ_7745c5c3_Var3 string templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf(domainServer.API_ENGAGEMENT_VIEW, recipe.Id)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/favorites.templ`, Line: 22, Col: 71} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/favorites.templ`, Line: 23, Col: 68} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { @@ -104,7 +104,7 @@ func favoriteResult(recipe domain.Recipe) templ.Component { var templ_7745c5c3_Var4 string templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(recipe.Title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/favorites.templ`, Line: 29, Col: 24} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/favorites.templ`, Line: 33, Col: 20} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { @@ -117,7 +117,7 @@ func favoriteResult(recipe domain.Recipe) templ.Component { var templ_7745c5c3_Var5 string templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(recipe.Category) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/favorites.templ`, Line: 29, Col: 95} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/favorites.templ`, Line: 33, Col: 91} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) if templ_7745c5c3_Err != nil { @@ -134,7 +134,7 @@ func favoriteResult(recipe domain.Recipe) templ.Component { var templ_7745c5c3_Var6 string templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(recipe.Duration.Total) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/favorites.templ`, Line: 34, Col: 35} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/favorites.templ`, Line: 38, Col: 30} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) if templ_7745c5c3_Err != nil { @@ -171,7 +171,7 @@ func favoriteResult(recipe domain.Recipe) templ.Component { var templ_7745c5c3_Var7 string templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(recipe.Serves) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/favorites.templ`, Line: 46, Col: 34} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/favorites.templ`, Line: 50, Col: 29} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) if templ_7745c5c3_Err != nil { @@ -184,7 +184,7 @@ func favoriteResult(recipe domain.Recipe) templ.Component { var templ_7745c5c3_Var8 string templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(recipe.Description) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/favorites.templ`, Line: 58, Col: 73} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/favorites.templ`, Line: 63, Col: 72} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) if templ_7745c5c3_Err != nil { diff --git a/internal/templates/pages/recipe.templ b/internal/templates/pages/recipe.templ index 3f4a99f..44b6b8c 100644 --- a/internal/templates/pages/recipe.templ +++ b/internal/templates/pages/recipe.templ @@ -291,7 +291,7 @@ templ buttonSection(favorited bool, id int, loggedIn bool) { } -templ RecipePage(recipe domain.Recipe, user domainUser.User, loggedIn bool) { +templ RecipePage(recipe domain.Recipe, user domainUser.User, loggedIn bool, domain string) { @components.Navbar("")
@@ -312,10 +312,10 @@ templ RecipePage(recipe domain.Recipe, user domainUser.User, loggedIn bool) { @tagList(recipe.Tags, recipe.Created, recipe.Modified)
- @scripts(recipe.Id) + @scripts(recipe.Id, domain) } -templ scripts(id int) { +templ scripts(id int, domain string) { ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "/v1/web/recipe/") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Var36, templ_7745c5c3_Err := templruntime.ScriptContentInsideStringLiteral(id) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 326, Col: 51} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var36) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "\"\n navigator.clipboard.writeText(url).then(() => {\n button.outerHTML = `\n \n `;\n\n setTimeout(() => {\n const newButton = document.getElementById(\"share-button\");\n newButton.outerHTML = before;\n }, 2000);\n });\n } else {\n console.warn(\"Clipboard API not available.\");\n\n button.outerHTML = `\n \n `;\n\n setTimeout(() => {\n const newButton = document.getElementById(\"share-button\");\n newButton.outerHTML = before;\n }, 2000);\n }\n }\n\n function makeButtonHandler() {\n const button = document.getElementById(\"make-button\");\n\n button.outerHTML = `\n \n \n \n \n \n \n Made This!\n \n `;\n }\n\n function favoriteButtonHandler() {\n const button = document.getElementById(\"favorite-button\");\n\n console.log(button.classList);\n console.log(button.classList.contains(\"border-blue-300\"));\n\n const toggleClasses = [\n \"border-gray-300\", \"hover:bg-gray-50\", \"hover:border-blue-300\",\n \"border-blue-300\", \"bg-blue-50\", \"hover:bg-blue-100\", \"hover:border-blue-500\"\n ];\n\n for (const cls of toggleClasses) {\n console.log(\"toggling class \" + cls);\n button.classList.toggle(cls);\n }\n\n if (!button.classList.contains(\"border-blue-300\")) {\n button.innerHTML = `\n \n \n \n Favorite\n `;\n\n } else {\n button.innerHTML = `\n \n \n \n Unfavorite\n `;\n }\n\n }\n\n") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } From 94740b4b54200a49e02d5f40b4c86ca4a8d2ff83 Mon Sep 17 00:00:00 2001 From: Hayden Hargreaves Date: Tue, 22 Jul 2025 22:11:11 -0700 Subject: [PATCH 2/3] (FEAT): Added a handler to recover from panics. This is not perfect, and def not set in stone. But for now, it will return a JSON page, and not crash. I want this to log the user out, clear the cookies, and then direct home with a message saying something went wrong. I also want it to send me an email with the details, but one step at a time for now, that is not 100% necessary, I just need to fix the bugs. I also need to fix the environments. --- internal/app/server/middleware.go | 45 +++++++++++++++++++++++++++++++ internal/app/server/server.go | 3 ++- 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/internal/app/server/middleware.go b/internal/app/server/middleware.go index 4eb7d6e..a22acdd 100644 --- a/internal/app/server/middleware.go +++ b/internal/app/server/middleware.go @@ -2,6 +2,9 @@ package server import ( "fmt" + "log" + "net/http" + "runtime/debug" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v5" @@ -71,3 +74,45 @@ func JwtAuthMiddleWare(jwtSecretKey []byte) gin.HandlerFunc { ctx.Next() } } + +func RecoveryMiddleware() gin.HandlerFunc { + + return func(ctx *gin.Context) { + defer func() { + if r := recover(); r != nil { + // Log the panic with stack trace + err := fmt.Errorf("panic recovered: %v\n%s", r, debug.Stack()) + log.Printf("[PANIC RECOVERY] %s", err) + + ctx.JSON(http.StatusOK, gin.H{ + "status": http.StatusOK, + "error": "API_PANIC_RECOVERED", + "message": err.Error(), + }) + + // Determine the content type of the request for appropriate response + // acceptHeader := ctx.Request.Header.Get("Accept") + // + // // Customize the response based on the request type (e.g., HTML vs. JSON) + // if strings.Contains(acceptHeader, "text/html") { + // // For browser requests (HTML), redirect to an error page or render a template + // ctx.HTML(http.StatusInternalServerError, "error.html", gin.H{ + // "title": "Something went wrong", + // "message": "An unexpected error occurred. Please try again later.", + // }) + // } else if strings.Contains(acceptHeader, "application/json") || ctx.Request.Method == http.MethodPost { + // // For API requests (JSON), return a JSON error response + // ctx.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{ + // "error": "An internal server error occurred.", + // "message": "We're working to fix the problem. Please try again later.", + // // You might include a unique error ID here for tracking + // }) + // } else { + // // Fallback for other content types + // ctx.AbortWithStatus(http.StatusInternalServerError) + // } + } + }() + ctx.Next() + } +} diff --git a/internal/app/server/server.go b/internal/app/server/server.go index a921e6d..e543222 100644 --- a/internal/app/server/server.go +++ b/internal/app/server/server.go @@ -129,6 +129,7 @@ func (s *Server) Setup() *Server { } // Apply middleware + s.Router.Use(RecoveryMiddleware()) s.Router.Use(DepedencyInjectionMiddleware(deps)) s.Router.Use(JwtAuthMiddleWare(jwtSecret)) @@ -201,7 +202,7 @@ func (s *Server) Setup() *Server { // TODO: Use constants for errors? if strings.HasPrefix(path, domain.VERSION+domain.API) { ctx.JSON(http.StatusNotFound, gin.H{ - "status": 404, + "status": http.StatusNotFound, "error": "API_NOT_FOUND", "message": "The request endpoint does not exist.", "path": path, From e0e1230660049e6bc9204bc40c240a5e6e5dd226 Mon Sep 17 00:00:00 2001 From: Hayden Hargreaves Date: Tue, 22 Jul 2025 22:43:40 -0700 Subject: [PATCH 3/3] (FEAT): Dev/prod environment toggle is complete! We can now use environment values to dictate which values are used for the DB and the domain. This is a simple solution, but for now, it works! This will merge into master and we can then see it live in action! --- cmd/web/main.go | 2 +- internal/app/handlers/page_handler.go | 3 +- internal/app/server/server.go | 69 ++++++++-------------- internal/domain/server/server.go | 85 +++++++++++++++++++++++++++ 4 files changed, 110 insertions(+), 49 deletions(-) diff --git a/cmd/web/main.go b/cmd/web/main.go index 8f3951d..56c437e 100644 --- a/cmd/web/main.go +++ b/cmd/web/main.go @@ -5,7 +5,7 @@ import "github.com/haydenhargreaves/Potion/internal/app/server" const PORT = 3000 func main() { - s := server.Init(PORT).ConfigureAuth().ConnectDatabase().Setup() + s := server.Init(PORT).Setup() defer s.DB.Close() s.Start() diff --git a/internal/app/handlers/page_handler.go b/internal/app/handlers/page_handler.go index cf90ba2..61dfe22 100644 --- a/internal/app/handlers/page_handler.go +++ b/internal/app/handlers/page_handler.go @@ -4,7 +4,6 @@ import ( "encoding/json" "fmt" "net/http" - "os" "strconv" "github.com/a-h/templ" @@ -189,7 +188,7 @@ func RecipePage(ctx *gin.Context) { } title := "Potion - View Recipe" - page := pages.RecipePage(*recipe, *user, loggedIn, os.Getenv("DOMAIN")) + page := pages.RecipePage(*recipe, *user, loggedIn, deps.EnvironmentConfig.Domain) ctx.HTML(http.StatusOK, "", layouts.AppLayout(title, page)) } diff --git a/internal/app/server/server.go b/internal/app/server/server.go index e543222..233c031 100644 --- a/internal/app/server/server.go +++ b/internal/app/server/server.go @@ -4,7 +4,6 @@ import ( "database/sql" "fmt" "net/http" - "os" "strings" "github.com/a-h/templ/examples/integration-gin/gintemplrenderer" @@ -15,7 +14,6 @@ import ( domain "github.com/haydenhargreaves/Potion/internal/domain/server" "github.com/haydenhargreaves/Potion/internal/infrastructure/auth" "github.com/haydenhargreaves/Potion/internal/infrastructure/database/repository" - "github.com/joho/godotenv" _ "github.com/lib/pq" ) @@ -53,18 +51,26 @@ func Init(port int) *Server { return server } -func (s *Server) ConfigureAuth() *Server { - err := godotenv.Load(".env") +// Start starts the server on the port provided when the server was initialized +func (s *Server) Start() { + s.Router.Run(fmt.Sprintf(":%d", s.port)) +} + +func (s *Server) Setup() *Server { + // SETUP THE ENVIRONMENT CONFIGURATION + cfg, err := domain.LoadEnvironment() if err != nil { - fmt.Printf("No .env file found or error loading .env: %v. Relying on system environment variables.", err) + panic(err.Error()) + } + if cfg == nil { + panic("Environment configuration is nil, crashing.") } - redirect_domain := os.Getenv("DOMAIN") - + // SETUP GOOGLE AUTH var ( - redirectUrl string = fmt.Sprintf("%s%s", redirect_domain, domain.API_AUTH_CALLBACK) - clientId string = os.Getenv("GOOGLE_CLIENT_ID") - clientSecret string = os.Getenv("GOOGLE_CLIENT_SECRET") + redirectUrl string = fmt.Sprintf("%s%s", cfg.Domain, domain.API_AUTH_CALLBACK) + clientId string = cfg.GoogleClientId + clientSecret string = cfg.GoogleClientSecret scope []string = []string{ "https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/userinfo.profile", @@ -74,18 +80,8 @@ func (s *Server) ConfigureAuth() *Server { // Setup Google OAuth auth.NewGoogleConfig(redirectUrl, clientId, clientSecret, scope) - return s -} - -func (s *Server) ConnectDatabase() *Server { - err := godotenv.Load(".env") - if err != nil { - fmt.Printf("No .env file found or error loading .env: %v. Relying on system environment variables.", err) - } - - var connUrl string = os.Getenv("DATABASE_URL") - - db, err := sql.Open("postgres", connUrl) + // SETUP DATABASE + db, err := sql.Open("postgres", cfg.DatabaseUrl) if err != nil { panic("Could not connect to database: " + err.Error()) } @@ -96,21 +92,8 @@ func (s *Server) ConnectDatabase() *Server { s.DB = db - return s -} - -// Start starts the server on the port provided when the server was initialized -func (s *Server) Start() { - s.Router.Run(fmt.Sprintf(":%d", s.port)) -} - -func (s *Server) Setup() *Server { - err := godotenv.Load(".env") - if err != nil { - fmt.Printf("No .env file found or error loading .env: %v. Relying on system environment variables.", err) - } - - jwtSecret := []byte(os.Getenv("JWT_SECRET")) + // SETUP JWT + jwtSecret := []byte(cfg.JwtSecret) // Initialize and inject dependencies userRepo := repository.NewUserRepository(s.DB) @@ -126,6 +109,7 @@ func (s *Server) Setup() *Server { AuthService: authService, RecipeService: recipeService, EngagementService: engagementService, + EnvironmentConfig: *cfg, } // Apply middleware @@ -150,15 +134,8 @@ func (s *Server) Setup() *Server { // API router endpoints router_api.GET("/", func(ctx *gin.Context) { ctx.JSON(200, gin.H{"message": "Server is active."}) }) router_api.GET("/tmp", func(ctx *gin.Context) { - if !domain.IsLoggedIn(ctx) { - ctx.JSON(200, gin.H{"error": "User is not logged in. Please login to continue"}) - return - } - - userId := ctx.MustGet("userId").(int) - userEmail := ctx.MustGet("userEmail").(string) - - ctx.JSON(200, gin.H{"id": userId, "email": userEmail}) + deps := ctx.MustGet("deps").(*domain.InjectedDependencies) + ctx.JSON(200, gin.H{"config": deps.EnvironmentConfig}) }) // WEB router endpoints diff --git a/internal/domain/server/server.go b/internal/domain/server/server.go index a8861aa..e2d2712 100644 --- a/internal/domain/server/server.go +++ b/internal/domain/server/server.go @@ -1,14 +1,30 @@ package domain import ( + "fmt" + "os" + "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v5" domainAuth "github.com/haydenhargreaves/Potion/internal/domain/auth" domainEngagement "github.com/haydenhargreaves/Potion/internal/domain/engagement" domainRecipe "github.com/haydenhargreaves/Potion/internal/domain/recipe" domainUser "github.com/haydenhargreaves/Potion/internal/domain/user" + "github.com/joho/godotenv" ) +// EnvironmentConfig stores the configuration of the environment. Anything loaded from the .env +// or docker environment will be stored here and can be accessed from the InjectedDependencies +// struct, which this is attached to. +type EnvironmentConfig struct { + GoogleClientId string + GoogleClientSecret string + JwtSecret string + DatabaseUrl string + Environment string + Domain string +} + // InjectedDependencies is a collection of dependencies that are injected into the application. They // are stored in the context and can be accessed by handlers via the context. type InjectedDependencies struct { @@ -16,6 +32,7 @@ type InjectedDependencies struct { AuthService domainAuth.AuthService RecipeService domainRecipe.RecipeService EngagementService domainEngagement.EngagementService + EnvironmentConfig EnvironmentConfig } // JwtClaims is the data stored in the JSON web token. All that is needed is the users ID and their @@ -32,3 +49,71 @@ func IsLoggedIn(ctx *gin.Context) bool { _, email := ctx.Get("userEmail") return id && email } + +func LoadEnvironment() (*EnvironmentConfig, error) { + err := godotenv.Load(".env") + if err != nil { + fmt.Printf("No .env file found or error loading .env: %v. Relying on system environment variables.", err) + } + + env := os.Getenv("ENVIRONMENT") + if env == "" { + return nil, fmt.Errorf("ENVIRONMENT environment variable is required.") + } + + googleClientId := os.Getenv("GOOGLE_CLIENT_ID") + if googleClientId == "" { + return nil, fmt.Errorf("GOOGLE_CLIENT_ID environment variable is required.") + } + + googleClientSecret := os.Getenv("GOOGLE_CLIENT_SECRET") + if googleClientSecret == "" { + return nil, fmt.Errorf("GOOGLE_CLIENT_SECRET environment variable is required.") + } + + jwtSecret := os.Getenv("JWT_SECRET") + if jwtSecret == "" { + return nil, fmt.Errorf("JWT_SECRET environment variable is required.") + } + + var domain string + if env == "dev" { + domain = os.Getenv("DOMAIN_DEV") + if domain == "" { + return nil, fmt.Errorf("DOMAIN_DEV environment variable is required when ENVIRONMENT is 'dev'.") + } + } else if env == "prod" { + domain = os.Getenv("DOMAIN_PROD") + if domain == "" { + return nil, fmt.Errorf("DOMAIN_PROD environment variable is required when ENVIRONMENT is 'prod'.") + } + } else { + return nil, fmt.Errorf("ENVIRONMENT environment variable is required and must be 'dev' or 'prod'.") + } + + var dbUrl string + if env == "dev" { + dbUrl = os.Getenv("DATABASE_URL_DEV") + if dbUrl == "" { + return nil, fmt.Errorf("DATABASE_URL_DEV environment variable is required when ENVIRONMENT is 'dev'.") + } + } else if env == "prod" { + dbUrl = os.Getenv("DATABASE_URL_PROD") + if dbUrl == "" { + return nil, fmt.Errorf("DATABASE_URL_PROD environment variable is required when ENVIRONMENT is 'prod'.") + } + } else { + return nil, fmt.Errorf("ENVIRONMENT environment variable is required and must be 'dev' or 'prod'.") + } + + cfg := &EnvironmentConfig{ + GoogleClientId: googleClientId, + GoogleClientSecret: googleClientSecret, + JwtSecret: jwtSecret, + DatabaseUrl: dbUrl, + Environment: env, + Domain: domain, + } + + return cfg, nil +}