(FEAT): Implemented API for the share engagement.

This includes user and no user routes! Now wired to the frontend,
however, it will still create an engagement even if it fails...
This commit is contained in:
Hayden Hargreaves 2025-07-15 21:19:47 -07:00
parent e4c1a575be
commit 2a33edc8f6
8 changed files with 148 additions and 71 deletions

View File

@ -8,24 +8,55 @@ import (
domain "github.com/haydenhargreaves/Potion/internal/domain/server"
)
func EngagementViewRecipe(ctx *gin.Context) {
deps := ctx.MustGet("deps").(*domain.InjectedDependencies)
id := ctx.Param("id")
recipeId, _ := strconv.Atoi(ctx.Param("id"))
if !domain.IsLoggedIn(ctx) {
// TODO: Anon view
ctx.Status(http.StatusNoContent)
if _, err := deps.EngagementService.ViewRecipe(recipeId); err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{
"status": http.StatusInternalServerError,
"message": err.Error(),
})
} else {
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,
"status": http.StatusInternalServerError,
"message": err.Error(),
})
} else {
ctx.Status(http.StatusNoContent)
}
}
func EngagementShareRecipe(ctx *gin.Context) {
deps := ctx.MustGet("deps").(*domain.InjectedDependencies)
recipeId, _ := strconv.Atoi(ctx.Param("id"))
if !domain.IsLoggedIn(ctx) {
if _, err := deps.EngagementService.ShareRecipe(recipeId); err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{
"status": http.StatusInternalServerError,
"message": err.Error(),
})
} else {
ctx.Status(http.StatusNoContent)
}
return
}
userId := ctx.MustGet("userId").(int)
if _, err := deps.EngagementService.UserShareRecipe(userId, recipeId); err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{
"status": http.StatusInternalServerError,
"message": err.Error(),
})
} else {
@ -48,7 +79,7 @@ func EngagementFavoriteRecipe(ctx *gin.Context) {
if _, err := deps.EngagementService.UserFavoriteRecipe(userId, recipeId); err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{
"status": http.StatusInternalServerError,
"status": http.StatusInternalServerError,
"message": err.Error(),
})
} else {
@ -71,7 +102,7 @@ func EngagementMakeRecipe(ctx *gin.Context) {
if _, err := deps.EngagementService.UserMakeRecipe(userId, recipeId); err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{
"status": http.StatusInternalServerError,
"status": http.StatusInternalServerError,
"message": err.Error(),
})
} else {

View File

@ -130,14 +130,12 @@ func RecipePage(ctx *gin.Context) {
// Add engagement
if loggedIn {
fmt.Println("CALLING USER VIEW")
if _, err = deps.EngagementService.UserViewRecipe(*userId, recipe.Id); err != nil {
fmt.Printf("ERROR: %s\n", err.Error())
ctx.JSON(400, err.Error())
return
}
} else {
fmt.Println("CALLING VIEW")
if _, err = deps.EngagementService.ViewRecipe(recipe.Id); err != nil {
fmt.Printf("ERROR: %s\n", err.Error())
ctx.JSON(400, err.Error())

View File

@ -188,6 +188,7 @@ func (s *Server) Setup() *Server {
// 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)

View File

@ -39,6 +39,20 @@ func (s *EngagementService) ViewRecipe(recipeId int) (domain.Engagement, error)
return s.engagementRepository.AddEntityEngagement(recipeId, message, domain.EngagementViewed)
}
// ShareRecipe 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 view engagement to the
// database.
func (s *EngagementService) ShareRecipe(recipeId int) (domain.Engagement, error) {
recipe, err := s.recipeRepository.GetRecipe(recipeId, nil)
if err != nil {
return domain.Engagement{}, err
}
message := fmt.Sprintf("Shared \"%s\"", recipe.Title)
return s.engagementRepository.AddEntityEngagement(recipeId, message, domain.EngagementShared)
}
// UserViewRecipe 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 view engagement to the
// database.
@ -94,6 +108,20 @@ func (s *EngagementService) UserMakeRecipe(userId, recipeId int) (domain.Engagem
return s.engagementRepository.AddUserEntityEngagement(userId, recipeId, message, domain.EngagementMade)
}
// UserShareRecipe 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 make engagement to the
// database.
func (s *EngagementService) UserShareRecipe(userId, recipeId int) (domain.Engagement, error) {
recipe, err := s.recipeRepository.GetRecipe(recipeId, &userId)
if err != nil {
return domain.Engagement{}, err
}
message := fmt.Sprintf("Shared \"%s\"", recipe.Title)
return s.engagementRepository.AddUserEntityEngagement(userId, recipeId, message, domain.EngagementShared)
}
// 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) {

View File

@ -2,8 +2,10 @@ package domain
type EngagementService interface {
ViewRecipe(recipeId int) (Engagement, error)
ShareRecipe(recipeId int) (Engagement, error)
UserViewRecipe(userId, recipeId int) (Engagement, error)
UserFavoriteRecipe(userId, recipeId int) (Engagement, error)
UserMakeRecipe(userId, recipeId int) (Engagement, error)
UserShareRecipe(userId, recipeId int) (Engagement, error)
GetUserEngagement(userId, limit int) ([]Engagement, error)
}

View File

@ -26,6 +26,7 @@ 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_SHARE = VERSION + API + "/engagement/share/%d"
const API_ENGAGEMENT_FAVORITE = VERSION + API + "/engagement/favorite/%d"
const API_ENGAGEMENT_MAKE = VERSION + API + "/engagement/make/%d"

View File

@ -189,11 +189,11 @@ templ favoriteButton(favorited bool, id int, loggedIn bool) {
hx-post={ fmt.Sprintf(domainServer.API_ENGAGEMENT_FAVORITE, id) }
hx-trigger="click"
hx-swap="none"
if loggedIn {
hx-on:click="favoriteButtonHandler();"
}
if loggedIn {
hx-on:click="favoriteButtonHandler();"
}
class="flex items-center justify-center gap-x-1 rounded-lg border border-blue-300 bg-blue-50 text-gray-800 px-6 py-3 flex-grow hover:bg-blue-100 hover:border-blue-500 duration-300"
id="favorite-button"
id="favorite-button"
>
<svg class="h-6 text-red-500" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
@ -208,11 +208,11 @@ templ favoriteButton(favorited bool, id int, loggedIn bool) {
hx-post={ fmt.Sprintf(domainServer.API_ENGAGEMENT_FAVORITE, id) }
hx-trigger="click"
hx-swap="none"
if loggedIn {
hx-on:click="favoriteButtonHandler();"
}
if loggedIn {
hx-on:click="favoriteButtonHandler();"
}
class="flex items-center justify-center gap-x-1 rounded-lg border border-gray-300 text-gray-800 px-6 py-3 flex-grow hover:bg-gray-50 hover:border-blue-300 duration-300"
id="favorite-button"
id="favorite-button"
>
<svg class="h-6" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
@ -236,9 +236,9 @@ templ madeButton(id int, loggedIn bool) {
hx-trigger="click"
hx-swap="none"
id="make-button"
if loggedIn {
hx-on:click="makeButtonHandler();"
}
if loggedIn {
hx-on:click="makeButtonHandler();"
}
class="flex items-center justify-center gap-x-1 rounded-lg border border-gray-300 text-gray-800 px-6 py-3 flex-grow hover:bg-gray-50 hover:border-blue-300 duration-300"
>
<svg
@ -262,10 +262,13 @@ templ madeButton(id int, loggedIn bool) {
</button>
}
templ shareButton() {
templ shareButton(id int) {
<button
id="share-button"
onclick="shareButtonHandler();"
hx-post={ fmt.Sprintf(domainServer.API_ENGAGEMENT_SHARE, id) }
hx-trigger="click"
hx-swap="none"
hx-on:click="shareButtonHandler();"
class="flex items-center justify-center gap-x-1 rounded-lg border border-gray-300 text-gray-800 px-6 py-3 flex-grow hover:bg-gray-50 hover:border-blue-300 duration-300"
>
<svg class="h-7" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
@ -284,7 +287,7 @@ templ buttonSection(favorited bool, id int, loggedIn bool) {
<section class="w-full flex flex-col md:flex-row gap-x-4 gap-y-2 py-8 px-4 md:px-8">
@favoriteButton(favorited, id, loggedIn)
@madeButton(id, loggedIn)
@shareButton()
@shareButton(id)
</section>
}

File diff suppressed because one or more lines are too long