diff --git a/flake.nix b/flake.nix index 1ca34fc..3ad3ac2 100644 --- a/flake.nix +++ b/flake.nix @@ -27,6 +27,8 @@ watchman docker-language-server dockerfile-language-server-nodejs + gcc_multi + glibc_multi ]; # Define the shell that will be executed. @@ -41,6 +43,9 @@ export GOPATH="$HOME/.local/go" echo "Settings GOPATH to: $HOME/.local/go " + export GOOS=linux + export GOARCH=amd64 + # Exec zsh to replace the current shell process with zsh. # This ensures your prompt and zsh configurations load correctly. exec zsh diff --git a/go.mod b/go.mod index 31e6e1b..d938e8a 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/haydenhargreaves/Potion -go 1.24.3 +go 1.25.0 require ( github.com/a-h/templ v0.3.920 diff --git a/internal/app/handlers/auth_handler.go b/internal/app/handlers/auth_handler.go index 3c37be0..50d48df 100644 --- a/internal/app/handlers/auth_handler.go +++ b/internal/app/handlers/auth_handler.go @@ -1,50 +1,47 @@ package handlers -import ( - "net/http" - "time" - - "github.com/gin-gonic/gin" - domain "github.com/haydenhargreaves/Potion/internal/domain/server" - "github.com/haydenhargreaves/Potion/internal/templates/components" -) - // GoogleLogin directs the user to Googles select user login page. Once the user has selected an // account, they will be directed to the GoogleCallback handler where the main logic resides. -func GoogleLogin(ctx *gin.Context) { - deps := ctx.MustGet("deps").(*domain.InjectedDependencies) - url := deps.AuthService.GetGoogleAuthUrl() - - ctx.Redirect(http.StatusSeeOther, url) -} +// +// DEPRECATED: As of September 4th, 2025. +// func GoogleLogin(ctx *gin.Context) { +// deps := ctx.MustGet("deps").(*domain.InjectedDependencies) +// url := deps.AuthService.GetGoogleAuthUrl() +// +// ctx.Redirect(http.StatusSeeOther, url) +// } // GoogleCallback is the callback handler when the user successfully logs in with their Google // account. They will be directed here and a JWT is generated. This JWT is stored in the users // cookies and will be used by protected routes to validate their login status. // // We do not need to return all of this data, it is just for testing. -func GoogleCallback(ctx *gin.Context) { - deps := ctx.MustGet("deps").(*domain.InjectedDependencies) - - var ( - state string = ctx.Query("state") - code string = ctx.Query("code") - ) - - if jwt, err := deps.AuthService.GoogleAuthSuccess(state, code); err != nil { - components.RenderErrorBanner(ctx, err.Error()) - ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - } else { - domain.SetCookie(ctx, "jwt_token", jwt, time.Hour*24*7) - ctx.Redirect(http.StatusSeeOther, "/") - } -} +// +// DEPRECATED: As of September 4th, 2025. +// func GoogleCallback(ctx *gin.Context) { +// deps := ctx.MustGet("deps").(*domain.InjectedDependencies) +// +// var ( +// state string = ctx.Query("state") +// code string = ctx.Query("code") +// ) +// +// if jwt, err := deps.AuthService.GoogleAuthSuccess(state, code); err != nil { +// components.RenderErrorBanner(ctx, err.Error()) +// ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) +// } else { +// domain.SetCookie(ctx, "jwt_token", jwt, time.Hour*24*7) +// ctx.Redirect(http.StatusSeeOther, "/") +// } +// } // Logout removes the token from the user's browser. Effectively "logging them out." Routes that // require authentication will require the user to sign back in before accessing them again. // This route will direct the user back to the home page. -func Logout(ctx *gin.Context) { - domain.SetCookie(ctx, "jwt_token", "", -1) - domain.SetCookie(ctx, "search-filters", "", -1) - ctx.Redirect(http.StatusSeeOther, domain.WEB_HOME) -} +// +// DEPRECATED: As of September 4th, 2025. +// func Logout(ctx *gin.Context) { +// domain.SetCookie(ctx, "jwt_token", "", -1) +// domain.SetCookie(ctx, "search-filters", "", -1) +// ctx.Redirect(http.StatusSeeOther, domain.WEB_HOME) +// } diff --git a/internal/app/handlers/engagement_handler.go b/internal/app/handlers/engagement_handler.go index 571d6a3..3620d04 100644 --- a/internal/app/handlers/engagement_handler.go +++ b/internal/app/handlers/engagement_handler.go @@ -1,148 +1,142 @@ package handlers -import ( - "fmt" - "net/http" - "strconv" +// DEPRECATED: As of September 4th, 2025. +// func EngagementViewRecipe(ctx *gin.Context) { +// deps := ctx.MustGet("deps").(*domain.InjectedDependencies) +// recipeId, _ := strconv.Atoi(ctx.Param("id")) +// +// // Ensure user is logged in with a valid account +// user := deps.UserService.GetAuthenicatedUser(ctx) +// if user == nil { +// // Log (stale) user out +// domain.SetCookie(ctx, "jwt_token", "", -1) +// domain.SetCookie(ctx, "search-filters", "", -1) +// } +// +// if !domain.IsLoggedIn(ctx) || user == nil { +// if _, err := deps.EngagementService.ViewRecipe(recipeId); err != nil { +// components.RenderErrorBanner(ctx, err.Error()) +// ctx.JSON(http.StatusInternalServerError, gin.H{ +// "status": http.StatusInternalServerError, +// "message": err.Error(), +// }) +// } else { +// ctx.Header("HX-Redirect", fmt.Sprintf(domain.WEB_RECIPE, recipeId)) +// ctx.Status(http.StatusOK) +// } +// return +// } +// +// // We caught nil already, we can assume the user exists +// if _, err := deps.EngagementService.UserViewRecipe(user.Id, recipeId); err != nil { +// components.RenderErrorBanner(ctx, err.Error()) +// ctx.JSON(http.StatusInternalServerError, gin.H{ +// "status": http.StatusInternalServerError, +// "message": err.Error(), +// }) +// } else { +// ctx.Header("HX-Redirect", fmt.Sprintf(domain.WEB_RECIPE, recipeId)) +// ctx.Status(http.StatusOK) +// } +// } - "github.com/gin-gonic/gin" - domain "github.com/haydenhargreaves/Potion/internal/domain/server" - "github.com/haydenhargreaves/Potion/internal/templates/components" -) +// DEPRECATED: As of September 4th, 2025. +// func EngagementShareRecipe(ctx *gin.Context) { +// deps := ctx.MustGet("deps").(*domain.InjectedDependencies) +// recipeId, _ := strconv.Atoi(ctx.Param("id")) +// +// // Ensure user is logged in with a valid account +// user := deps.UserService.GetAuthenicatedUser(ctx) +// if user == nil { +// // Log (stale) user out +// domain.SetCookie(ctx, "jwt_token", "", -1) +// domain.SetCookie(ctx, "search-filters", "", -1) +// } +// +// if !domain.IsLoggedIn(ctx) || user == nil { +// if _, err := deps.EngagementService.ShareRecipe(recipeId); err != nil { +// components.RenderErrorBanner(ctx, err.Error()) +// ctx.JSON(http.StatusInternalServerError, gin.H{ +// "status": http.StatusInternalServerError, +// "message": err.Error(), +// }) +// } else { +// ctx.Status(http.StatusNoContent) +// } +// return +// } +// +// if _, err := deps.EngagementService.UserShareRecipe(user.Id, recipeId); err != nil { +// components.RenderErrorBanner(ctx, err.Error()) +// ctx.JSON(http.StatusInternalServerError, gin.H{ +// "status": http.StatusInternalServerError, +// "message": err.Error(), +// }) +// } else { +// ctx.Status(http.StatusNoContent) +// } +// } -func EngagementViewRecipe(ctx *gin.Context) { - deps := ctx.MustGet("deps").(*domain.InjectedDependencies) - recipeId, _ := strconv.Atoi(ctx.Param("id")) +// DEPRECATED: As of September 4th, 2025. +// func EngagementFavoriteRecipe(ctx *gin.Context) { +// deps := ctx.MustGet("deps").(*domain.InjectedDependencies) +// +// // Ensure user is logged in with a valid account +// user := deps.UserService.GetAuthenicatedUser(ctx) +// if user == nil { +// // Log (stale) user out +// domain.SetCookie(ctx, "jwt_token", "", -1) +// domain.SetCookie(ctx, "search-filters", "", -1) +// } +// +// if !domain.IsLoggedIn(ctx) || user == nil { +// ctx.Header("HX-Redirect", domain.WEB_LOGIN) +// ctx.Status(http.StatusOK) +// return +// } +// +// id := ctx.Param("id") +// recipeId, _ := strconv.Atoi(id) +// +// if _, err := deps.EngagementService.UserFavoriteRecipe(user.Id, recipeId); err != nil { +// components.RenderErrorBanner(ctx, fmt.Sprintf("Something went wrong. %s.", err.Error())) +// ctx.JSON(http.StatusInternalServerError, gin.H{ +// "status": http.StatusInternalServerError, +// "message": err.Error(), +// }) +// } else { +// ctx.Status(http.StatusNoContent) +// } +// } - // Ensure user is logged in with a valid account - user := deps.UserService.GetAuthenicatedUser(ctx) - if user == nil { - // Log (stale) user out - domain.SetCookie(ctx, "jwt_token", "", -1) - domain.SetCookie(ctx, "search-filters", "", -1) - } - - if !domain.IsLoggedIn(ctx) || user == nil { - if _, err := deps.EngagementService.ViewRecipe(recipeId); err != nil { - components.RenderErrorBanner(ctx, err.Error()) - ctx.JSON(http.StatusInternalServerError, gin.H{ - "status": http.StatusInternalServerError, - "message": err.Error(), - }) - } else { - ctx.Header("HX-Redirect", fmt.Sprintf(domain.WEB_RECIPE, recipeId)) - ctx.Status(http.StatusOK) - } - return - } - - // We caught nil already, we can assume the user exists - if _, err := deps.EngagementService.UserViewRecipe(user.Id, recipeId); err != nil { - components.RenderErrorBanner(ctx, err.Error()) - ctx.JSON(http.StatusInternalServerError, gin.H{ - "status": http.StatusInternalServerError, - "message": err.Error(), - }) - } else { - ctx.Header("HX-Redirect", fmt.Sprintf(domain.WEB_RECIPE, recipeId)) - ctx.Status(http.StatusOK) - } -} - -func EngagementShareRecipe(ctx *gin.Context) { - deps := ctx.MustGet("deps").(*domain.InjectedDependencies) - recipeId, _ := strconv.Atoi(ctx.Param("id")) - - // Ensure user is logged in with a valid account - user := deps.UserService.GetAuthenicatedUser(ctx) - if user == nil { - // Log (stale) user out - domain.SetCookie(ctx, "jwt_token", "", -1) - domain.SetCookie(ctx, "search-filters", "", -1) - } - - if !domain.IsLoggedIn(ctx) || user == nil { - if _, err := deps.EngagementService.ShareRecipe(recipeId); err != nil { - components.RenderErrorBanner(ctx, err.Error()) - ctx.JSON(http.StatusInternalServerError, gin.H{ - "status": http.StatusInternalServerError, - "message": err.Error(), - }) - } else { - ctx.Status(http.StatusNoContent) - } - return - } - - if _, err := deps.EngagementService.UserShareRecipe(user.Id, recipeId); err != nil { - components.RenderErrorBanner(ctx, err.Error()) - ctx.JSON(http.StatusInternalServerError, gin.H{ - "status": http.StatusInternalServerError, - "message": err.Error(), - }) - } else { - ctx.Status(http.StatusNoContent) - } -} - -func EngagementFavoriteRecipe(ctx *gin.Context) { - deps := ctx.MustGet("deps").(*domain.InjectedDependencies) - - // Ensure user is logged in with a valid account - user := deps.UserService.GetAuthenicatedUser(ctx) - if user == nil { - // Log (stale) user out - domain.SetCookie(ctx, "jwt_token", "", -1) - domain.SetCookie(ctx, "search-filters", "", -1) - } - - if !domain.IsLoggedIn(ctx) || user == nil { - ctx.Header("HX-Redirect", domain.WEB_LOGIN) - ctx.Status(http.StatusOK) - return - } - - id := ctx.Param("id") - recipeId, _ := strconv.Atoi(id) - - if _, err := deps.EngagementService.UserFavoriteRecipe(user.Id, recipeId); err != nil { - components.RenderErrorBanner(ctx, fmt.Sprintf("Something went wrong. %s.", err.Error())) - ctx.JSON(http.StatusInternalServerError, gin.H{ - "status": http.StatusInternalServerError, - "message": err.Error(), - }) - } else { - ctx.Status(http.StatusNoContent) - } -} - -func EngagementMakeRecipe(ctx *gin.Context) { - deps := ctx.MustGet("deps").(*domain.InjectedDependencies) - - // Ensure user is logged in with a valid account - user := deps.UserService.GetAuthenicatedUser(ctx) - if user == nil { - // Log (stale) user out - domain.SetCookie(ctx, "jwt_token", "", -1) - domain.SetCookie(ctx, "search-filters", "", -1) - } - - if !domain.IsLoggedIn(ctx) || user == nil { - ctx.Header("HX-Redirect", domain.WEB_LOGIN) - ctx.Status(http.StatusOK) - return - } - - id := ctx.Param("id") - recipeId, _ := strconv.Atoi(id) - - if _, err := deps.EngagementService.UserMakeRecipe(user.Id, recipeId); err != nil { - components.RenderErrorBanner(ctx, err.Error()) - ctx.JSON(http.StatusInternalServerError, gin.H{ - "status": http.StatusInternalServerError, - "message": err.Error(), - }) - } else { - ctx.Status(http.StatusNoContent) - } -} +// DEPRECATED: As of September 4th, 2025. +// func EngagementMakeRecipe(ctx *gin.Context) { +// deps := ctx.MustGet("deps").(*domain.InjectedDependencies) +// +// // Ensure user is logged in with a valid account +// user := deps.UserService.GetAuthenicatedUser(ctx) +// if user == nil { +// // Log (stale) user out +// domain.SetCookie(ctx, "jwt_token", "", -1) +// domain.SetCookie(ctx, "search-filters", "", -1) +// } +// +// if !domain.IsLoggedIn(ctx) || user == nil { +// ctx.Header("HX-Redirect", domain.WEB_LOGIN) +// ctx.Status(http.StatusOK) +// return +// } +// +// id := ctx.Param("id") +// recipeId, _ := strconv.Atoi(id) +// +// if _, err := deps.EngagementService.UserMakeRecipe(user.Id, recipeId); err != nil { +// components.RenderErrorBanner(ctx, err.Error()) +// ctx.JSON(http.StatusInternalServerError, gin.H{ +// "status": http.StatusInternalServerError, +// "message": err.Error(), +// }) +// } else { +// ctx.Status(http.StatusNoContent) +// } +// } diff --git a/internal/app/handlers/page_handler.go b/internal/app/handlers/page_handler.go index 8c84971..1165ecd 100755 --- a/internal/app/handlers/page_handler.go +++ b/internal/app/handlers/page_handler.go @@ -1,306 +1,296 @@ package handlers -import ( - "encoding/json" - "fmt" - "net/http" - "strconv" +// DEPRECATED: As of September 4th, 2025. +// func LoginPage(ctx *gin.Context) { +// title := "Potion - Login" +// page := pages.LoginPage() +// +// ctx.HTML(http.StatusOK, "", layouts.AppLayout(title, page)) +// } - "github.com/a-h/templ" - "github.com/gin-gonic/gin" - domainRecipe "github.com/haydenhargreaves/Potion/internal/domain/recipe" - domain "github.com/haydenhargreaves/Potion/internal/domain/server" - domainServer "github.com/haydenhargreaves/Potion/internal/domain/server" - "github.com/haydenhargreaves/Potion/internal/templates/components" - layouts "github.com/haydenhargreaves/Potion/internal/templates/layouts" - pages "github.com/haydenhargreaves/Potion/internal/templates/pages" - templates "github.com/haydenhargreaves/Potion/internal/templates/pages" -) +// DEPRECATED: As of September 4th, 2025. +// func HomePage(ctx *gin.Context) { +// deps := ctx.MustGet("deps").(*domainServer.InjectedDependencies) +// +// loggedIn := domain.IsLoggedIn(ctx) +// +// // Ensure user is logged in with a valid account +// if user := deps.UserService.GetAuthenicatedUser(ctx); user == nil { +// // Log (stale) user out +// domain.SetCookie(ctx, "jwt_token", "", -1) +// domain.SetCookie(ctx, "search-filters", "", -1) +// loggedIn = false +// } +// +// var page templ.Component +// if loggedIn { +// userId := ctx.MustGet("userId").(int) +// madeRecipes, err := deps.RecipeService.GetUserMadeRecipes(userId, 6) +// if err != nil { +// components.RenderErrorBanner(ctx, fmt.Sprintf("Error getting made recipes. %s\n", err.Error())) +// ctx.JSON(http.StatusInternalServerError, gin.H{ +// "status": http.StatusInternalServerError, +// "message": fmt.Sprintf("Error getting made recipes. %s\n", err.Error()), +// }) +// return +// } +// viewedRecipes, err := deps.RecipeService.GetUserViewedRecipes(userId, 6) +// if err != nil { +// components.RenderErrorBanner(ctx, fmt.Sprintf("Error getting viewed recipes. %s\n", err.Error())) +// ctx.JSON(http.StatusInternalServerError, gin.H{ +// "status": http.StatusInternalServerError, +// "message": fmt.Sprintf("Error getting viewed recipes. %s\n", err.Error()), +// }) +// return +// } +// +// // Get the recipe of the week +// recipeOfTheWeek, err := deps.RecipeService.GetRecipeOfTheWeek(&userId) +// if err != nil { +// components.RenderErrorBanner(ctx, fmt.Sprintf("Error getting recipe of the week. %s\n", err.Error())) +// ctx.JSON(http.StatusInternalServerError, gin.H{ +// "status": http.StatusInternalServerError, +// "message": fmt.Sprintf("Error getting recipe of the week. %s\n", err.Error()), +// }) +// return +// } +// +// if bytes, err := ctx.Cookie("search-filters"); err != nil { +// fmt.Printf("ERROR: Failed to get search-filter cookie. %s\n", err.Error()) +// page = templates.HomePage(true, viewedRecipes, madeRecipes, recipeOfTheWeek, nil) +// } else { +// var filters domainRecipe.SearchFilters +// if err := json.Unmarshal([]byte(bytes), &filters); err != nil { +// fmt.Printf("ERROR: Failed to unmarshal search-filter cookie. %s\n", err.Error()) +// page = templates.HomePage(true, viewedRecipes, madeRecipes, recipeOfTheWeek, nil) +// } else { +// page = templates.HomePage(true, viewedRecipes, madeRecipes, recipeOfTheWeek, &filters) +// } +// } +// } else { +// // Get the recipe of the week +// recipeOfTheWeek, err := deps.RecipeService.GetRecipeOfTheWeek(nil) +// if err != nil { +// components.RenderErrorBanner(ctx, fmt.Sprintf("Error getting recipe of the week. %s\n", err.Error())) +// ctx.JSON(http.StatusInternalServerError, gin.H{ +// "status": http.StatusInternalServerError, +// "message": fmt.Sprintf("Error getting recipe of the week. %s\n", err.Error()), +// }) +// return +// } +// +// if bytes, err := ctx.Cookie("search-filters"); err != nil { +// fmt.Printf("ERROR: Failed to get search-filter cookie. %s\n", err.Error()) +// page = templates.HomePage(false, nil, nil, recipeOfTheWeek, nil) +// } else { +// var filters domainRecipe.SearchFilters +// if err := json.Unmarshal([]byte(bytes), &filters); err != nil { +// fmt.Printf("ERROR: Failed to unmarshal search-filter cookie. %s\n", err.Error()) +// page = templates.HomePage(false, nil, nil, recipeOfTheWeek, nil) +// } else { +// page = templates.HomePage(false, nil, nil, recipeOfTheWeek, &filters) +// } +// } +// } +// +// title := "Potion - Home" +// +// ctx.HTML(http.StatusOK, "", layouts.AppLayout(title, page)) +// } -func LoginPage(ctx *gin.Context) { - title := "Potion - Login" - page := pages.LoginPage() +// DEPRECATED: As of September 4th, 2025. +// func FavoritesPage(ctx *gin.Context) { +// // If not logged in, direct to the login page +// if !domainServer.IsLoggedIn(ctx) { +// ctx.Redirect(http.StatusSeeOther, domainServer.WEB_LOGIN) +// return +// } +// +// title := "Potion - Favorites" +// var page templ.Component +// +// // Get filters from cookies +// if bytes, err := ctx.Cookie("search-filters"); err != nil { +// fmt.Printf("ERROR: Failed to get search-filter cookie. %s\n", err.Error()) +// page = pages.FavoritesPage(nil) +// } else { +// var filters domainRecipe.SearchFilters +// if err := json.Unmarshal([]byte(bytes), &filters); err != nil { +// fmt.Printf("ERROR: Failed to unmarshal search-filter cookie. %s\n", err.Error()) +// page = pages.FavoritesPage(nil) +// } else { +// page = pages.FavoritesPage(&filters) +// } +// } +// +// ctx.HTML(http.StatusOK, "", layouts.AppLayout(title, page)) +// } +// +// func CreatePage(ctx *gin.Context) { +// deps := ctx.MustGet("deps").(*domainServer.InjectedDependencies) +// +// // If not logged in, direct to the login page +// if !domainServer.IsLoggedIn(ctx) { +// ctx.Redirect(http.StatusSeeOther, domainServer.WEB_LOGIN) +// return +// } +// +// // Ensure user is logged in with a valid account +// if user := deps.UserService.GetAuthenicatedUser(ctx); user == nil { +// // Log (stale) user out +// domain.SetCookie(ctx, "jwt_token", "", -1) +// domain.SetCookie(ctx, "search-filters", "", -1) +// +// ctx.Redirect(http.StatusSeeOther, domainServer.WEB_LOGIN) +// return +// } +// +// title := "Potion - Create" +// page := pages.CreatePage() +// +// ctx.HTML(http.StatusOK, "", layouts.AppLayout(title, page)) +// } - ctx.HTML(http.StatusOK, "", layouts.AppLayout(title, page)) -} +// DEPRECATED: As of September 4th, 2025. +// func ProfilePage(ctx *gin.Context) { +// // If not logged in, direct to the login page +// if !domainServer.IsLoggedIn(ctx) { +// ctx.Redirect(http.StatusSeeOther, domainServer.WEB_LOGIN) +// return +// } +// +// // Else, get the user data +// deps := ctx.MustGet("deps").(*domainServer.InjectedDependencies) +// user := deps.UserService.GetAuthenicatedUser(ctx) +// if user == nil { +// // User is failing to be found, direct to the login page +// ctx.Redirect(http.StatusSeeOther, domainServer.WEB_LOGIN) +// return +// } +// +// recipes, err := deps.RecipeService.GetUserRecipes(user.Id) +// if err != nil { +// components.RenderErrorBanner(ctx, fmt.Sprintf("Error getting recipes. %s\n", err.Error())) +// ctx.JSON(http.StatusInternalServerError, gin.H{ +// "status": http.StatusInternalServerError, +// "message": fmt.Sprintf("Error getting recipes. %s\n", err.Error()), +// }) +// return +// } +// +// favorites, err := deps.RecipeService.GetUserFavoriteRecipes(user.Id) +// if err != nil { +// components.RenderErrorBanner(ctx, fmt.Sprintf("Error getting favorite recipes. %s\n", err.Error())) +// ctx.JSON(http.StatusInternalServerError, gin.H{ +// "status": http.StatusInternalServerError, +// "message": fmt.Sprintf("Error getting favorite recipes. %s\n", err.Error()), +// }) +// return +// } +// +// // Get the engagement data, not sure what will happen when errors occur +// engagements, err := deps.EngagementService.GetUserEngagement(user.Id, 6) +// if err != nil { +// components.RenderErrorBanner(ctx, fmt.Sprintf("Error getting user engagements. %s\n", err.Error())) +// ctx.JSON(http.StatusInternalServerError, gin.H{ +// "status": http.StatusInternalServerError, +// "message": fmt.Sprintf("Error getting user engagements. %s\n", err.Error()), +// }) +// return +// } +// +// title := "Potion - Profile" +// page := pages.ProfilePage(*user, recipes, favorites, engagements) +// +// ctx.HTML(http.StatusOK, "", layouts.AppLayout(title, page)) +// } -func HomePage(ctx *gin.Context) { - deps := ctx.MustGet("deps").(*domainServer.InjectedDependencies) +// DEPRECATED: As of September 4th, 2025. +// func ListPage(ctx *gin.Context) { +// title := "Potion - Shopping List" +// page := pages.ListPage() +// +// ctx.HTML(http.StatusOK, "", layouts.AppLayout(title, page)) +// } - loggedIn := domain.IsLoggedIn(ctx) +// DEPRECATED: As of September 4th, 2025. +// func RecipePage(ctx *gin.Context) { +// // Call recipe service to get via ID +// deps := ctx.MustGet("deps").(*domainServer.InjectedDependencies) +// id := ctx.Param("id") +// +// // Parse ID +// parsed, err := strconv.Atoi(id) +// if err != nil { +// components.RenderErrorBanner(ctx, fmt.Sprintf("ERROR: %s", err.Error())) +// ctx.JSON(400, err.Error()) +// return +// } +// +// // Get signed in user, if they exist +// var userId *int = nil +// var loggedIn = domainServer.IsLoggedIn(ctx) +// +// // Ensure user is logged in with a valid account +// if user := deps.UserService.GetAuthenicatedUser(ctx); user == nil { +// // Log (stale) user out +// domain.SetCookie(ctx, "jwt_token", "", -1) +// domain.SetCookie(ctx, "search-filters", "", -1) +// loggedIn = false +// } +// +// if loggedIn { +// storeId := ctx.MustGet("userId").(int) +// userId = &storeId +// } +// +// // Get recipe +// recipe, err := deps.RecipeService.GetRecipe(parsed, userId) +// if err != nil { +// components.RenderErrorBanner(ctx, fmt.Sprintf("ERROR: %s", err.Error())) +// ctx.JSON(400, err.Error()) +// return +// } +// +// // Get user (owner) +// user, err := deps.UserService.GetUser(recipe.UserId) +// if err != nil { +// components.RenderErrorBanner(ctx, fmt.Sprintf("ERROR: %s", err.Error())) +// ctx.JSON(400, err.Error()) +// return +// } +// +// title := "Potion - View Recipe" +// page := pages.RecipePage(*recipe, *user, loggedIn, deps.EnvironmentConfig.Domain) +// +// ctx.HTML(http.StatusOK, "", layouts.AppLayout(title, page)) +// } +// +// func SearchPage(ctx *gin.Context) { +// var page templ.Component +// // Get filters from cookies +// if bytes, err := ctx.Cookie("search-filters"); err != nil { +// fmt.Printf("ERROR: Failed to get search-filter cookie. %s\n", err.Error()) +// page = pages.SearchPage(nil, false) +// } else { +// var filters domainRecipe.SearchFilters +// if err := json.Unmarshal([]byte(bytes), &filters); err != nil { +// fmt.Printf("ERROR: Failed to unmarshal search-filter cookie. %s\n", err.Error()) +// page = pages.SearchPage(nil, false) +// } else { +// page = pages.SearchPage(&filters, true) +// } +// } +// +// title := "Potion - Recipe Search" +// +// ctx.HTML(http.StatusOK, "", layouts.AppLayout(title, page)) +// } - // Ensure user is logged in with a valid account - if user := deps.UserService.GetAuthenicatedUser(ctx); user == nil { - // Log (stale) user out - domain.SetCookie(ctx, "jwt_token", "", -1) - domain.SetCookie(ctx, "search-filters", "", -1) - loggedIn = false - } - - var page templ.Component - if loggedIn { - userId := ctx.MustGet("userId").(int) - madeRecipes, err := deps.RecipeService.GetUserMadeRecipes(userId, 6) - if err != nil { - components.RenderErrorBanner(ctx, fmt.Sprintf("Error getting made recipes. %s\n", err.Error())) - ctx.JSON(http.StatusInternalServerError, gin.H{ - "status": http.StatusInternalServerError, - "message": fmt.Sprintf("Error getting made recipes. %s\n", err.Error()), - }) - return - } - viewedRecipes, err := deps.RecipeService.GetUserViewedRecipes(userId, 6) - if err != nil { - components.RenderErrorBanner(ctx, fmt.Sprintf("Error getting viewed recipes. %s\n", err.Error())) - ctx.JSON(http.StatusInternalServerError, gin.H{ - "status": http.StatusInternalServerError, - "message": fmt.Sprintf("Error getting viewed recipes. %s\n", err.Error()), - }) - return - } - - // Get the recipe of the week - recipeOfTheWeek, err := deps.RecipeService.GetRecipeOfTheWeek(&userId) - if err != nil { - components.RenderErrorBanner(ctx, fmt.Sprintf("Error getting recipe of the week. %s\n", err.Error())) - ctx.JSON(http.StatusInternalServerError, gin.H{ - "status": http.StatusInternalServerError, - "message": fmt.Sprintf("Error getting recipe of the week. %s\n", err.Error()), - }) - return - } - - if bytes, err := ctx.Cookie("search-filters"); err != nil { - fmt.Printf("ERROR: Failed to get search-filter cookie. %s\n", err.Error()) - page = templates.HomePage(true, viewedRecipes, madeRecipes, recipeOfTheWeek, nil) - } else { - var filters domainRecipe.SearchFilters - if err := json.Unmarshal([]byte(bytes), &filters); err != nil { - fmt.Printf("ERROR: Failed to unmarshal search-filter cookie. %s\n", err.Error()) - page = templates.HomePage(true, viewedRecipes, madeRecipes, recipeOfTheWeek, nil) - } else { - page = templates.HomePage(true, viewedRecipes, madeRecipes, recipeOfTheWeek, &filters) - } - } - } else { - // Get the recipe of the week - recipeOfTheWeek, err := deps.RecipeService.GetRecipeOfTheWeek(nil) - if err != nil { - components.RenderErrorBanner(ctx, fmt.Sprintf("Error getting recipe of the week. %s\n", err.Error())) - ctx.JSON(http.StatusInternalServerError, gin.H{ - "status": http.StatusInternalServerError, - "message": fmt.Sprintf("Error getting recipe of the week. %s\n", err.Error()), - }) - return - } - - if bytes, err := ctx.Cookie("search-filters"); err != nil { - fmt.Printf("ERROR: Failed to get search-filter cookie. %s\n", err.Error()) - page = templates.HomePage(false, nil, nil, recipeOfTheWeek, nil) - } else { - var filters domainRecipe.SearchFilters - if err := json.Unmarshal([]byte(bytes), &filters); err != nil { - fmt.Printf("ERROR: Failed to unmarshal search-filter cookie. %s\n", err.Error()) - page = templates.HomePage(false, nil, nil, recipeOfTheWeek, nil) - } else { - page = templates.HomePage(false, nil, nil, recipeOfTheWeek, &filters) - } - } - } - - title := "Potion - Home" - - ctx.HTML(http.StatusOK, "", layouts.AppLayout(title, page)) -} - -func FavoritesPage(ctx *gin.Context) { - // If not logged in, direct to the login page - if !domainServer.IsLoggedIn(ctx) { - ctx.Redirect(http.StatusSeeOther, domainServer.WEB_LOGIN) - return - } - - title := "Potion - Favorites" - var page templ.Component - - // Get filters from cookies - if bytes, err := ctx.Cookie("search-filters"); err != nil { - fmt.Printf("ERROR: Failed to get search-filter cookie. %s\n", err.Error()) - page = pages.FavoritesPage(nil) - } else { - var filters domainRecipe.SearchFilters - if err := json.Unmarshal([]byte(bytes), &filters); err != nil { - fmt.Printf("ERROR: Failed to unmarshal search-filter cookie. %s\n", err.Error()) - page = pages.FavoritesPage(nil) - } else { - page = pages.FavoritesPage(&filters) - } - } - - ctx.HTML(http.StatusOK, "", layouts.AppLayout(title, page)) -} - -func CreatePage(ctx *gin.Context) { - deps := ctx.MustGet("deps").(*domainServer.InjectedDependencies) - - // If not logged in, direct to the login page - if !domainServer.IsLoggedIn(ctx) { - ctx.Redirect(http.StatusSeeOther, domainServer.WEB_LOGIN) - return - } - - // Ensure user is logged in with a valid account - if user := deps.UserService.GetAuthenicatedUser(ctx); user == nil { - // Log (stale) user out - domain.SetCookie(ctx, "jwt_token", "", -1) - domain.SetCookie(ctx, "search-filters", "", -1) - - ctx.Redirect(http.StatusSeeOther, domainServer.WEB_LOGIN) - return - } - - title := "Potion - Create" - page := pages.CreatePage() - - ctx.HTML(http.StatusOK, "", layouts.AppLayout(title, page)) -} - -func ProfilePage(ctx *gin.Context) { - // If not logged in, direct to the login page - if !domainServer.IsLoggedIn(ctx) { - ctx.Redirect(http.StatusSeeOther, domainServer.WEB_LOGIN) - return - } - - // Else, get the user data - deps := ctx.MustGet("deps").(*domainServer.InjectedDependencies) - user := deps.UserService.GetAuthenicatedUser(ctx) - if user == nil { - // User is failing to be found, direct to the login page - ctx.Redirect(http.StatusSeeOther, domainServer.WEB_LOGIN) - return - } - - recipes, err := deps.RecipeService.GetUserRecipes(user.Id) - if err != nil { - components.RenderErrorBanner(ctx, fmt.Sprintf("Error getting recipes. %s\n", err.Error())) - ctx.JSON(http.StatusInternalServerError, gin.H{ - "status": http.StatusInternalServerError, - "message": fmt.Sprintf("Error getting recipes. %s\n", err.Error()), - }) - return - } - - favorites, err := deps.RecipeService.GetUserFavoriteRecipes(user.Id) - if err != nil { - components.RenderErrorBanner(ctx, fmt.Sprintf("Error getting favorite recipes. %s\n", err.Error())) - ctx.JSON(http.StatusInternalServerError, gin.H{ - "status": http.StatusInternalServerError, - "message": fmt.Sprintf("Error getting favorite recipes. %s\n", err.Error()), - }) - return - } - - // Get the engagement data, not sure what will happen when errors occur - engagements, err := deps.EngagementService.GetUserEngagement(user.Id, 6) - if err != nil { - components.RenderErrorBanner(ctx, fmt.Sprintf("Error getting user engagements. %s\n", err.Error())) - ctx.JSON(http.StatusInternalServerError, gin.H{ - "status": http.StatusInternalServerError, - "message": fmt.Sprintf("Error getting user engagements. %s\n", err.Error()), - }) - return - } - - title := "Potion - Profile" - page := pages.ProfilePage(*user, recipes, favorites, engagements) - - ctx.HTML(http.StatusOK, "", layouts.AppLayout(title, page)) -} - -func ListPage(ctx *gin.Context) { - title := "Potion - Shopping List" - page := pages.ListPage() - - ctx.HTML(http.StatusOK, "", layouts.AppLayout(title, page)) -} - -func RecipePage(ctx *gin.Context) { - // Call recipe service to get via ID - deps := ctx.MustGet("deps").(*domainServer.InjectedDependencies) - id := ctx.Param("id") - - // Parse ID - parsed, err := strconv.Atoi(id) - if err != nil { - components.RenderErrorBanner(ctx, fmt.Sprintf("ERROR: %s", err.Error())) - ctx.JSON(400, err.Error()) - return - } - - // Get signed in user, if they exist - var userId *int = nil - var loggedIn = domainServer.IsLoggedIn(ctx) - - // Ensure user is logged in with a valid account - if user := deps.UserService.GetAuthenicatedUser(ctx); user == nil { - // Log (stale) user out - domain.SetCookie(ctx, "jwt_token", "", -1) - domain.SetCookie(ctx, "search-filters", "", -1) - loggedIn = false - } - - if loggedIn { - storeId := ctx.MustGet("userId").(int) - userId = &storeId - } - - // Get recipe - recipe, err := deps.RecipeService.GetRecipe(parsed, userId) - if err != nil { - components.RenderErrorBanner(ctx, fmt.Sprintf("ERROR: %s", err.Error())) - ctx.JSON(400, err.Error()) - return - } - - // Get user (owner) - user, err := deps.UserService.GetUser(recipe.UserId) - if err != nil { - components.RenderErrorBanner(ctx, fmt.Sprintf("ERROR: %s", err.Error())) - ctx.JSON(400, err.Error()) - return - } - - title := "Potion - View Recipe" - page := pages.RecipePage(*recipe, *user, loggedIn, deps.EnvironmentConfig.Domain) - - ctx.HTML(http.StatusOK, "", layouts.AppLayout(title, page)) -} - -func SearchPage(ctx *gin.Context) { - var page templ.Component - // Get filters from cookies - if bytes, err := ctx.Cookie("search-filters"); err != nil { - fmt.Printf("ERROR: Failed to get search-filter cookie. %s\n", err.Error()) - page = pages.SearchPage(nil, false) - } else { - var filters domainRecipe.SearchFilters - if err := json.Unmarshal([]byte(bytes), &filters); err != nil { - fmt.Printf("ERROR: Failed to unmarshal search-filter cookie. %s\n", err.Error()) - page = pages.SearchPage(nil, false) - } else { - page = pages.SearchPage(&filters, true) - } - } - - title := "Potion - Recipe Search" - - ctx.HTML(http.StatusOK, "", layouts.AppLayout(title, page)) -} - -func NotFoundPage(ctx *gin.Context) { - title := "Potion - Not Found" - page := pages.NotFoundPage() - - ctx.HTML(http.StatusOK, "", layouts.AppLayout(title, page)) -} +// DEPRECATED: As of September 4th, 2025. +// func NotFoundPage(ctx *gin.Context) { +// title := "Potion - Not Found" +// page := pages.NotFoundPage() +// +// 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 770c25c..868de1b 100644 --- a/internal/app/handlers/recipe_handler.go +++ b/internal/app/handlers/recipe_handler.go @@ -1,147 +1,136 @@ package handlers -import ( - "encoding/json" - "fmt" - "net/http" - "strconv" - "time" +// DEPRECATED: As of September 4th, 2025. +// const CREATE_ERROR_HTML = ` +//
+// Uh oh! Something went wrong when creating your recipe. Please try again. %s +//
+// ` - "github.com/gin-gonic/gin" - domainRecipe "github.com/haydenhargreaves/Potion/internal/domain/recipe" - domain "github.com/haydenhargreaves/Potion/internal/domain/server" - "github.com/haydenhargreaves/Potion/internal/templates/components" - templates "github.com/haydenhargreaves/Potion/internal/templates/pages" -) - -const CREATE_ERROR_HTML = ` -- Uh oh! Something went wrong when creating your recipe. Please try again. %s -
-` - -func CreateRecipe(ctx *gin.Context) { - deps := ctx.MustGet("deps").(*domain.InjectedDependencies) - - recipe, err := deps.RecipeService.CreateRecipe(ctx) - if err != nil { - components.RenderErrorBanner(ctx, err.Error()) - ctx.String(http.StatusOK, CREATE_ERROR_HTML, err.Error()) - return - } - - // Send HTMX redirection - url := fmt.Sprintf(domain.WEB_RECIPE, recipe.Id) - ctx.Header("HX-Redirect", url) - ctx.Status(http.StatusCreated) -} +// DEPRECATED: As of September 4th, 2025. +// func CreateRecipe(ctx *gin.Context) { +// deps := ctx.MustGet("deps").(*domain.InjectedDependencies) +// +// recipe, err := deps.RecipeService.CreateRecipe(ctx) +// if err != nil { +// components.RenderErrorBanner(ctx, err.Error()) +// ctx.String(http.StatusOK, CREATE_ERROR_HTML, err.Error()) +// return +// } +// +// // Send HTMX redirection +// url := fmt.Sprintf(domain.WEB_RECIPE, recipe.Id) +// ctx.Header("HX-Redirect", url) +// ctx.Status(http.StatusCreated) +// } // toBits converts an array of stringified numbers into a single summed value -func toBits(arr []string) (bits int) { - for _, x := range arr { - num, _ := strconv.Atoi(x) - bits += num - } - return -} +// +// DEPRECATED: As of September 4th, 2025. +// func toBits(arr []string) (bits int) { +// for _, x := range arr { +// num, _ := strconv.Atoi(x) +// bits += num +// } +// return +// } -// TODO: I don't love doing all of this here, but it seems to be the only way to get it to work... -func SearchRecipes(ctx *gin.Context) { - deps := ctx.MustGet("deps").(*domain.InjectedDependencies) +// DEPRECATED: As of September 4th, 2025. +// func SearchRecipes(ctx *gin.Context) { +// deps := ctx.MustGet("deps").(*domain.InjectedDependencies) +// +// // create filters +// filters := domainRecipe.SearchFilters{ +// Search: ctx.PostForm("search"), // string, search query for titles +// MealType: toBits(ctx.PostFormArray("meal")), +// Time: toBits(ctx.PostFormArray("time")), +// Difficulty: toBits(ctx.PostFormArray("difficulty")), +// ServingSize: toBits(ctx.PostFormArray("serving")), +// } +// +// // Set the filters into the cookies, so they can be reloaded +// if bytes, err := json.Marshal(filters); err == nil { +// domain.SetCookie(ctx, "search-filters", string(bytes), time.Hour*24) +// // ctx.SetCookie( +// // "search-filters", +// // string(bytes), +// // int(time.Now().Add(24*time.Hour).Sub(time.Now()).Seconds()), +// // "/", +// // true, +// // ) +// } +// +// redirect := ctx.PostForm("redirect") +// if redirect == "true" { +// ctx.Header("HX-Redirect", domain.WEB_SEARCH) +// ctx.Status(http.StatusOK) +// return +// } +// +// // Get user if logged in, so we can get favorite status +// var userId *int = nil +// if domain.IsLoggedIn(ctx) { +// id := ctx.MustGet("userId").(int) +// userId = &id +// } +// +// // TODO: Not sure if we need to ensure the user is valid here +// +// // We don't care about favorite status, so use false +// recipes, err := deps.RecipeService.SearchRecipes(filters, userId, false) +// if err != nil { +// components.RenderErrorBanner(ctx, err.Error()) +// ctx.JSON(http.StatusOK, gin.H{"error": err.Error()}) +// } +// +// // Render content as the response +// ctx.Status(200) +// templates.ResultList(recipes).Render(ctx.Request.Context(), ctx.Writer) +// } - // create filters - filters := domainRecipe.SearchFilters{ - Search: ctx.PostForm("search"), // string, search query for titles - MealType: toBits(ctx.PostFormArray("meal")), - Time: toBits(ctx.PostFormArray("time")), - Difficulty: toBits(ctx.PostFormArray("difficulty")), - ServingSize: toBits(ctx.PostFormArray("serving")), - } - - // Set the filters into the cookies, so they can be reloaded - if bytes, err := json.Marshal(filters); err == nil { - domain.SetCookie(ctx, "search-filters", string(bytes), time.Hour*24) - // ctx.SetCookie( - // "search-filters", - // string(bytes), - // int(time.Now().Add(24*time.Hour).Sub(time.Now()).Seconds()), - // "/", - // "", // TODO: Need an actual domain - // false, // TODO: True in prod - // true, - // ) - } - - redirect := ctx.PostForm("redirect") - if redirect == "true" { - ctx.Header("HX-Redirect", domain.WEB_SEARCH) - ctx.Status(http.StatusOK) - return - } - - // Get user if logged in, so we can get favorite status - var userId *int = nil - if domain.IsLoggedIn(ctx) { - id := ctx.MustGet("userId").(int) - userId = &id - } - - // TODO: Not sure if we need to ensure the user is valid here - - // We don't care about favorite status, so use false - recipes, err := deps.RecipeService.SearchRecipes(filters, userId, false) - if err != nil { - components.RenderErrorBanner(ctx, err.Error()) - ctx.JSON(http.StatusOK, gin.H{"error": err.Error()}) - } - - // Render content as the response - ctx.Status(200) - templates.ResultList(recipes).Render(ctx.Request.Context(), ctx.Writer) -} - -func SearchRecipesFavorites(ctx *gin.Context) { - deps := ctx.MustGet("deps").(*domain.InjectedDependencies) - - // create filters - filters := domainRecipe.SearchFilters{ - Search: ctx.PostForm("search"), // string, search query for titles - MealType: toBits(ctx.PostFormArray("meal")), - Time: toBits(ctx.PostFormArray("time")), - Difficulty: toBits(ctx.PostFormArray("difficulty")), - ServingSize: toBits(ctx.PostFormArray("serving")), - } - - // Set the filters into the cookies, so they can be reloaded - if bytes, err := json.Marshal(filters); err == nil { - domain.SetCookie(ctx, "search-filters", string(bytes), time.Hour*24) - // ctx.SetCookie( - // "search-filters", - // string(bytes), - // int(time.Now().Add(24*time.Hour).Sub(time.Now()).Seconds()), - // "/", - // "", // TODO: Need an actual domain - // false, // TODO: True in prod - // true, - // ) - } - - // TODO: Error here if they're not logged in? - // Get user data (they should be logged in) - if !domain.IsLoggedIn(ctx) { - components.RenderErrorBanner(ctx, "User is not logged in. User will be nil.") - 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) - if err != nil { - components.RenderErrorBanner(ctx, err.Error()) - ctx.JSON(http.StatusOK, gin.H{"error": err.Error()}) - } - - // Render content as the response - ctx.Status(200) - templates.FavoriteList(recipes).Render(ctx.Request.Context(), ctx.Writer) -} +// DEPRECATED: As of September 4th, 2025. +// func SearchRecipesFavorites(ctx *gin.Context) { +// deps := ctx.MustGet("deps").(*domain.InjectedDependencies) +// +// // create filters +// filters := domainRecipe.SearchFilters{ +// Search: ctx.PostForm("search"), // string, search query for titles +// MealType: toBits(ctx.PostFormArray("meal")), +// Time: toBits(ctx.PostFormArray("time")), +// Difficulty: toBits(ctx.PostFormArray("difficulty")), +// ServingSize: toBits(ctx.PostFormArray("serving")), +// } +// +// // Set the filters into the cookies, so they can be reloaded +// if bytes, err := json.Marshal(filters); err == nil { +// domain.SetCookie(ctx, "search-filters", string(bytes), time.Hour*24) +// // ctx.SetCookie( +// // "search-filters", +// // string(bytes), +// // int(time.Now().Add(24*time.Hour).Sub(time.Now()).Seconds()), +// // "/", +// // "", // TODO: Need an actual domain +// // false, // TODO: True in prod +// // true, +// // ) +// } +// +// // TODO: Error here if they're not logged in? +// // Get user data (they should be logged in) +// if !domain.IsLoggedIn(ctx) { +// components.RenderErrorBanner(ctx, "User is not logged in. User will be nil.") +// 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) +// if err != nil { +// components.RenderErrorBanner(ctx, err.Error()) +// ctx.JSON(http.StatusOK, gin.H{"error": err.Error()}) +// } +// +// // Render content as the response +// ctx.Status(200) +// templates.FavoriteList(recipes).Render(ctx.Request.Context(), ctx.Writer) +// } diff --git a/internal/app/handlers/state_handler.go b/internal/app/handlers/state_handler.go index 91c2de3..d2c55b5 100644 --- a/internal/app/handlers/state_handler.go +++ b/internal/app/handlers/state_handler.go @@ -1,76 +1,71 @@ package handlers -import ( - "fmt" - "net/http" - "strings" +// DEPRECATED: As of September 4th, 2025. +// const TAG_HTML = ` +//+ Uh oh! Something went wrong when creating your recipe. Please try again. %s +
+` + +func (s *Server) CreateRecipeHandler(ctx *gin.Context) { + recipe, err := s.deps.RecipeService.CreateRecipe(ctx) + if err != nil { + components.RenderErrorBanner(ctx, err.Error()) + ctx.String(http.StatusOK, CREATE_ERROR_HTML, err.Error()) + return + } + + // Send HTMX redirection + url := fmt.Sprintf(domain.WEB_RECIPE, recipe.Id) + ctx.Header("HX-Redirect", url) + ctx.Status(http.StatusCreated) +} + +// toBits converts an array of stringified numbers into a single summed value +func toBits(arr []string) (bits int) { + for _, x := range arr { + num, _ := strconv.Atoi(x) + bits += num + } + return +} + +// TODO: (7/06/2025) I don't love doing all of this here, but it seems to be the only way to get it to work... +func (s *Server) SearchRecipesHandler(ctx *gin.Context) { + // create filters + filters := domainRecipe.SearchFilters{ + Search: ctx.PostForm("search"), // string, search query for titles + MealType: toBits(ctx.PostFormArray("meal")), + Time: toBits(ctx.PostFormArray("time")), + Difficulty: toBits(ctx.PostFormArray("difficulty")), + ServingSize: toBits(ctx.PostFormArray("serving")), + } + + // Set the filters into the cookies, so they can be reloaded + if bytes, err := json.Marshal(filters); err == nil { + s.SetCookie(ctx, "search-filters", string(bytes), time.Hour*24) + } + + redirect := ctx.PostForm("redirect") + if redirect == "true" { + ctx.Header("HX-Redirect", domain.WEB_SEARCH) + ctx.Status(http.StatusOK) + return + } + + // Get user if logged in, so we can get favorite status + var userId *int = nil + if domain.IsLoggedIn(ctx) { + id := ctx.MustGet("userId").(int) + userId = &id + } + + // TODO: Not sure if we need to ensure the user is valid here + + // We don't care about favorite status, so use false + recipes, err := s.deps.RecipeService.SearchRecipes(filters, userId, false) + if err != nil { + components.RenderErrorBanner(ctx, err.Error()) + ctx.JSON(http.StatusOK, gin.H{"error": err.Error()}) + } + + // Render content as the response + ctx.Status(200) + templates.ResultList(recipes).Render(ctx.Request.Context(), ctx.Writer) +} + +func (s *Server) SearchRecipesFavoritesHandler(ctx *gin.Context) { + // create filters + filters := domainRecipe.SearchFilters{ + Search: ctx.PostForm("search"), // string, search query for titles + MealType: toBits(ctx.PostFormArray("meal")), + Time: toBits(ctx.PostFormArray("time")), + Difficulty: toBits(ctx.PostFormArray("difficulty")), + ServingSize: toBits(ctx.PostFormArray("serving")), + } + + // Set the filters into the cookies, so they can be reloaded + if bytes, err := json.Marshal(filters); err == nil { + s.SetCookie(ctx, "search-filters", string(bytes), time.Hour*24) + } + + if !domain.IsLoggedIn(ctx) { + components.RenderErrorBanner(ctx, "User is not logged in. User will be nil.") + ctx.JSON(http.StatusOK, gin.H{"error": "User is not logged in. User will be nil."}) + } + + userId := ctx.MustGet("userId").(int) + + recipes, err := s.deps.RecipeService.SearchRecipes(filters, &userId, true) + if err != nil { + components.RenderErrorBanner(ctx, err.Error()) + ctx.JSON(http.StatusOK, gin.H{"error": err.Error()}) + } + + // Render content as the response + ctx.Status(200) + templates.FavoriteList(recipes).Render(ctx.Request.Context(), ctx.Writer) +} diff --git a/internal/app/server/server.go b/internal/app/server/server.go index 07b9af6..3e8f85f 100644 --- a/internal/app/server/server.go +++ b/internal/app/server/server.go @@ -9,7 +9,6 @@ import ( "github.com/a-h/templ/examples/integration-gin/gintemplrenderer" "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" - "github.com/haydenhargreaves/Potion/internal/app/handlers" "github.com/haydenhargreaves/Potion/internal/app/service" domain "github.com/haydenhargreaves/Potion/internal/domain/server" "github.com/haydenhargreaves/Potion/internal/infrastructure/auth" @@ -23,6 +22,7 @@ type Server struct { Router *gin.Engine config cors.Config DB *sql.DB + deps domain.InjectedDependencies } // Init initializes the server with the provided port. CORS settings are defined here. @@ -53,6 +53,7 @@ func (s *Server) Start() { s.Router.Run(fmt.Sprintf(":%d", s.port)) } +// TODO: (9/4/2025) Abstract these functions and cleanup. This is fucking messy... func (s *Server) Setup() *Server { // SETUP THE ENVIRONMENT CONFIGURATION cfg, err := domain.LoadEnvironment() @@ -109,7 +110,7 @@ func (s *Server) Setup() *Server { recipeService := service.NewRecipeService(recipeRepo, engagementRepo) engagementService := service.NewEngagementService(engagementRepo, recipeRepo) - deps := &domain.InjectedDependencies{ + s.deps = domain.InjectedDependencies{ UserService: userService, AuthService: authService, RecipeService: recipeService, @@ -119,7 +120,6 @@ func (s *Server) Setup() *Server { // Apply middleware s.Router.Use(RecoveryMiddleware()) - s.Router.Use(DepedencyInjectionMiddleware(deps)) s.Router.Use(JwtAuthMiddleWare(jwtSecret)) // Redirect index to home page: Update this as needed @@ -138,44 +138,41 @@ 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) { - deps := ctx.MustGet("deps").(*domain.InjectedDependencies) - ctx.JSON(200, gin.H{"config": deps.EnvironmentConfig}) - }) // WEB router endpoints router_web.GET("/", func(ctx *gin.Context) { ctx.Redirect(http.StatusSeeOther, domain.WEB_HOME) }) - router_web.GET("/login", handlers.LoginPage) - router_web.GET("/home", handlers.HomePage) - router_web.GET("/favorites", handlers.FavoritesPage) - router_web.GET("/create", handlers.CreatePage) - router_web.GET("/profile", handlers.ProfilePage) - router_web.GET("/list", handlers.ListPage) - router_web.GET("/recipe/:id", handlers.RecipePage) - router_web.GET("/search", handlers.SearchPage) - router_web.GET("/404", handlers.NotFoundPage) + router_web.GET("/login", s.LoginPageHandler) + router_web.GET("/home", s.HomePageHandler) + router_web.GET("/favorites", s.FavoritesPageHandler) + router_web.GET("/create", s.CreatePageHandler) + router_web.GET("/profile", s.ProfilePageHandler) + router_web.GET("/list", s.ListPageHandler) + router_web.GET("/recipe/:id", s.RecipePageHandler) + router_web.GET("/search", s.SearchPageHandler) + router_web.GET("/404", s.NotFoundPageHandler) // WEB state endpoints - router_state.POST("/tags", handlers.NewTag) - router_state.POST("/tags/delete", handlers.DeleteTag) + router_state.POST("/tags", s.NewTagHandler) + router_state.POST("/tags/delete", s.DeleteTagHandler) // Authentication - router_api.GET("/auth/login", handlers.GoogleLogin) - router_api.GET("/auth/callback", handlers.GoogleCallback) - router_api.GET("/auth/logout", handlers.Logout) + router_api.GET("/auth/login", s.GoogleLoginHandler) + router_api.GET("/auth/callback", s.GoogleCallbackHandler) + router_api.GET("/auth/logout", s.LogoutHandler) // Recipe endpoints - router_api.POST("/recipe", handlers.CreateRecipe) - router_api.POST("/recipe/search", handlers.SearchRecipes) - router_api.POST("/recipe/search/favorites", handlers.SearchRecipesFavorites) - router_api.GET("/user/recipes", handlers.GetUserRecipes) - router_api.GET("/user/favorites", handlers.GetUserFavoriteRecipes) + router_api.POST("/recipe", s.CreateRecipeHandler) + router_api.POST("/recipe/search", s.SearchRecipesHandler) + router_api.POST("/recipe/search/favorites", s.SearchRecipesFavoritesHandler) + + router_api.GET("/user/recipes", s.GetUserFavoriteRecipesHandler) + router_api.GET("/user/favorites", s.GetUserFavoriteRecipesHandler) // Engagement endpoints - router_api.POST("/engagement/view/:id", handlers.EngagementViewRecipe) - router_api.POST("/engagement/share/:id", handlers.EngagementShareRecipe) - router_api.POST("/engagement/favorite/:id", handlers.EngagementFavoriteRecipe) - router_api.POST("/engagement/make/:id", handlers.EngagementMakeRecipe) + router_api.POST("/engagement/view/:id", s.EngagementViewRecipeHandler) + router_api.POST("/engagement/share/:id", s.EngagementShareRecipeHandler) + router_api.POST("/engagement/favorite/:id", s.EngagementFavoriteRecipeHandler) + router_api.POST("/engagement/make/:id", s.EngagementMakeRecipeHandler) // Catch un-routed URLS s.Router.NoRoute(func(ctx *gin.Context) { diff --git a/internal/app/server/state_handler.go b/internal/app/server/state_handler.go new file mode 100644 index 0000000..139985f --- /dev/null +++ b/internal/app/server/state_handler.go @@ -0,0 +1,76 @@ +package server + +import ( + "fmt" + "net/http" + "strings" + + "github.com/gin-gonic/gin" + domain "github.com/haydenhargreaves/Potion/internal/domain/server" +) + +const TAG_HTML = ` +