diff --git a/internal/app/handlers/engagement_handler.go b/internal/app/handlers/engagement_handler.go new file mode 100644 index 0000000..36bcddc --- /dev/null +++ b/internal/app/handlers/engagement_handler.go @@ -0,0 +1,80 @@ +package handlers + +import ( + "net/http" + "strconv" + + "github.com/gin-gonic/gin" + domain "github.com/haydenhargreaves/Potion/internal/domain/server" +) + + + +func EngagementViewRecipe(ctx *gin.Context) { + deps := ctx.MustGet("deps").(*domain.InjectedDependencies) + id := ctx.Param("id") + + if !domain.IsLoggedIn(ctx) { + // TODO: Anon view + ctx.Status(http.StatusNoContent) + return + } + + recipeId, _ := strconv.Atoi(id) + userId := ctx.MustGet("userId").(int) + + if _, err := deps.EngagementService.UserViewRecipe(userId, recipeId); err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{ + "status": http.StatusInternalServerError, + "message": err.Error(), + }) + } else { + ctx.Status(http.StatusNoContent) + } +} + +func EngagementLikeRecipe(ctx *gin.Context) { + deps := ctx.MustGet("deps").(*domain.InjectedDependencies) + + if !domain.IsLoggedIn(ctx) { + ctx.Header("HX-Redirect", domain.WEB_LOGIN) + ctx.Status(http.StatusOK) + return + } + + id := ctx.Param("id") + recipeId, _ := strconv.Atoi(id) + userId := ctx.MustGet("userId").(int) + + if _, err := deps.EngagementService.UserLikeRecipe(userId, recipeId); err != nil { + 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) + + if !domain.IsLoggedIn(ctx) { + ctx.Header("HX-Redirect", domain.WEB_LOGIN) + ctx.Status(http.StatusOK) + return + } + + id := ctx.Param("id") + recipeId, _ := strconv.Atoi(id) + userId := ctx.MustGet("userId").(int) + + if _, err := deps.EngagementService.UserMakeRecipe(userId, recipeId); err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{ + "status": http.StatusInternalServerError, + "message": err.Error(), + }) + } else { + ctx.Status(http.StatusNoContent) + } +} diff --git a/internal/app/server/server.go b/internal/app/server/server.go index 33865f9..fee14fd 100644 --- a/internal/app/server/server.go +++ b/internal/app/server/server.go @@ -186,6 +186,11 @@ func (s *Server) Setup() *Server { router_api.POST("/recipe/search", handlers.SearchRecipes) router_api.GET("/user/recipes", handlers.GetUserRecipes) + // Engagement endpoints + router_api.POST("/engagement/view/:id", handlers.EngagementViewRecipe) + router_api.POST("/engagement/like/:id", handlers.EngagementLikeRecipe) + router_api.POST("/engagement/make/:id", handlers.EngagementMakeRecipe) + // Catch un-routed URLS s.Router.NoRoute(func(ctx *gin.Context) { path := ctx.Request.URL.Path diff --git a/internal/app/service/engagement_service.go b/internal/app/service/engagement_service.go index 1ff43fb..27f0971 100644 --- a/internal/app/service/engagement_service.go +++ b/internal/app/service/engagement_service.go @@ -53,6 +53,20 @@ func (s *EngagementService) UserLikeRecipe(userId, recipeId int) (domain.Engagem return s.engagementRepository.AddUserEntityEngagement(userId, recipeId, message, domain.EngagementLiked) } +// UserLikeRecipe requires a user ID and a recipe ID to create an engagement record in the database. +// A message will be generated using the recipe data and then used to add a like engagement to the +// database. +func (s *EngagementService) UserMakeRecipe(userId, recipeId int) (domain.Engagement, error) { + recipe, err := s.recipeRepository.GetRecipe(recipeId) + if err != nil { + return domain.Engagement{}, err + } + + message := fmt.Sprintf("Made \"%s\"", recipe.Title) + + return s.engagementRepository.AddUserEntityEngagement(userId, recipeId, message, domain.EngagementMade) +} + // GetUserEngagement returns a list of the users most recent engagement entries. The number of records // is determined by the limit passed into this function. The results are sorted, newest-to-oldest. func (s *EngagementService) GetUserEngagement(userId, limit int) ([]domain.Engagement, error) { diff --git a/internal/domain/engagement/service.go b/internal/domain/engagement/service.go index a4884b4..2643e91 100644 --- a/internal/domain/engagement/service.go +++ b/internal/domain/engagement/service.go @@ -3,5 +3,6 @@ package domain type EngagementService interface { UserViewRecipe(userId, recipeId int) (Engagement, error) UserLikeRecipe(userId, recipeId int) (Engagement, error) + UserMakeRecipe(userId, recipeId int) (Engagement, error) GetUserEngagement(userId, limit int) ([]Engagement, error) } diff --git a/internal/domain/server/routes.go b/internal/domain/server/routes.go index 194940e..221e1fa 100644 --- a/internal/domain/server/routes.go +++ b/internal/domain/server/routes.go @@ -25,6 +25,10 @@ const API_AUTH_LOGOUT = VERSION + API + "/auth/logout" const API_CREATE_RECIPE = VERSION + API + "/recipe" const API_SEARCH_RECIPES = VERSION + API + "/recipe/search" +const API_ENGAGEMENT_VIEW = VERSION + API + "/engagement/view/%d" +const API_ENGAGEMENT_LIKE = VERSION + API + "/engagement/like/%d" +const API_ENGAGEMENT_MAKE = VERSION + API + "/engagement/make/%d" + // State prefixed routes const STATE_TAGS_CREATE = VERSION + WEB + STATE + "/tags" const STATE_TAGS_DELETE = VERSION + WEB + STATE + "/tags/delete" diff --git a/internal/infrastructure/database/repository/engagement_repository.go b/internal/infrastructure/database/repository/engagement_repository.go index 4d9faf8..50ab3a4 100644 --- a/internal/infrastructure/database/repository/engagement_repository.go +++ b/internal/infrastructure/database/repository/engagement_repository.go @@ -68,6 +68,8 @@ func (r *EngagementRepository) AddUserEngagement(userId int, message string, eng // entity. The message should be provided, but a blank string ("") is acceptable. The engagement // type parameter determines the labeling of the engagement in the database. Any errors will be // bubbled to the caller. +// +// TODO: Disallow users to "make" the same recipe more than once a day func (r *EngagementRepository) AddUserEntityEngagement(userId, entityId int, message string, engagementType domain.EngagementType) (domain.Engagement, error) { tx, err := r.db.Begin() if err != nil { diff --git a/internal/templates/pages/favorites.templ b/internal/templates/pages/favorites.templ index 8d0e238..853262f 100644 --- a/internal/templates/pages/favorites.templ +++ b/internal/templates/pages/favorites.templ @@ -3,13 +3,13 @@ package templates import "github.com/haydenhargreaves/Potion/internal/templates/components" templ FavoritesPage() { - @components.Navbar("favorites") -
Sit tight, this page is coming soon!
-Sit tight, this page is coming soon!
+- Welcome to your ultimate recipe hub! Whether you're a seasoned chef or just starting your culinary adventure, - we're here to inspire. Explore thousands of delicious recipes, from quick weeknight dinners to gourmet delights, - all at your fingertips. Find exactly what you're craving with our powerful search and intuitive filters, or - browse our trending dishes for fresh ideas. -
-+ Welcome to your ultimate recipe hub! Whether you're a seasoned chef or just starting your culinary adventure, + we're here to inspire. Explore thousands of delicious recipes, from quick weeknight dinners to gourmet delights, + all at your fingertips. Find exactly what you're craving with our powerful search and intuitive filters, or + browse our trending dishes for fresh ideas. +
+- Our 'Recipe of the Week' is the cream of the crop! We handpick it by looking at what recipes - our community loves most. This isn't just about how many people view a recipe; it's also about - how many times it's been made, liked, reviewed, and its average rating, all combined to find - the true fan favorite of the week. It's our way of highlighting the best recipes that truly - resonate with our users! -
-+ Our 'Recipe of the Week' is the cream of the crop! We handpick it by looking at what recipes + our community loves most. This isn't just about how many people view a recipe; it's also about + how many times it's been made, liked, reviewed, and its average rating, all combined to find + the true fan favorite of the week. It's our way of highlighting the best recipes that truly + resonate with our users! +
+- Have a unique recipe idea? Want to share your culinary masterpiece with the world? - It's time to bring your creations to life! -
- ++ Have a unique recipe idea? Want to share your culinary masterpiece with the world? + It's time to bring your creations to life! +
+ - Create Your Recipe! - -Sit tight, this page is coming soon!
-Sit tight, this page is coming soon!
+Author: { user.Name }
-Category: { recipe.Category }
-{ recipe.Description }
-Created: { created.Format("January 2, 2006") }
if modified != nil { @@ -169,13 +149,9 @@ templ tagList(tags []domain.Tag, created time.Time, modified *time.Time) {