From 55c6a99bb1a59d83c6829436b7277486a70d6064 Mon Sep 17 00:00:00 2001 From: Hayden Hargreaves Date: Wed, 2 Jul 2025 22:53:15 -0700 Subject: [PATCH] (FEAT/DOC): Wired the backend to the UI for the recipe page! The route will now display real, live data from the DB! Errors are not handled very well, just returned as JSON for now. Need to implement an error page for states when errors occur. This commit also includes lots of documentation for the various service/repository methods. I am trying to not let docs fall through the cracks, but I am not perfect lol. --- internal/app/handlers/page_handler.go | 33 +- internal/app/service/recipe_service.go | 20 + internal/app/service/user_service.go | 22 +- internal/domain/recipe/repository.go | 2 +- internal/domain/recipe/service.go | 1 + internal/domain/user/repository.go | 2 +- internal/domain/user/service.go | 1 + .../database/repository/recipe_repository.go | 67 ++++ .../database/repository/user_repository.go | 10 +- internal/templates/pages/recipe.templ | 99 ++--- internal/templates/pages/recipe_templ.go | 379 +++++++++++------- 11 files changed, 422 insertions(+), 214 deletions(-) diff --git a/internal/app/handlers/page_handler.go b/internal/app/handlers/page_handler.go index 3d3a39b..1f87c36 100644 --- a/internal/app/handlers/page_handler.go +++ b/internal/app/handlers/page_handler.go @@ -1,7 +1,9 @@ package handlers import ( + "fmt" "net/http" + "strconv" "github.com/gin-gonic/gin" domain "github.com/haydenhargreaves/Potion/internal/domain/server" @@ -67,9 +69,38 @@ func ListPage(ctx *gin.Context) { ctx.HTML(200, "", layouts.AppLayout(title, page)) } +// TODO: Figure out how to handle errors, think we just need a simple display. func RecipePage(ctx *gin.Context) { + // Call recipe service to get via ID + deps := ctx.MustGet("deps").(*domain.InjectedDependencies) + id := ctx.Param("id") + + // Parse ID + parsed, err := strconv.Atoi(id) + if err != nil { + fmt.Printf("ERROR: %s\n", err.Error()) + ctx.JSON(400, err.Error()) + return + } + + // Get recipe + recipe, err := deps.RecipeService.GetRecipe(parsed) + if err != nil { + fmt.Printf("ERROR: %s\n", err.Error()) + ctx.JSON(400, err.Error()) + return + } + + // Get user + user, err := deps.UserService.GetUser(recipe.UserId) + if err != nil { + fmt.Printf("ERROR: %s\n", err.Error()) + ctx.JSON(400, err.Error()) + return + } + title := "Potion - View Recipe" - page := pages.RecipePage() + page := pages.RecipePage(*recipe, *user) ctx.HTML(200, "", layouts.AppLayout(title, page)) } diff --git a/internal/app/service/recipe_service.go b/internal/app/service/recipe_service.go index b0d302b..e1f9fe6 100644 --- a/internal/app/service/recipe_service.go +++ b/internal/app/service/recipe_service.go @@ -27,6 +27,13 @@ func NewRecipeService(recipeRepository domain.RecipeRepository) domain.RecipeSer return &RecipeService{recipeRepository: recipeRepository} } +// CreateRecipe creates a recipe in the database using the recipe repository. This function requires +// all the data to be present, though validation does not occur in this function. However, the UI +// will enforce validation, as will the database. Errors will be returned to the called when they +// occur. +// +// TODO: Implement validation in the API. +// TODO: Implement image creation and tag creation. func (s *RecipeService) CreateRecipe(ctx *gin.Context) (*domain.Recipe, error) { // Ensure user is logged in if !domainServer.IsLoggedIn(ctx) { @@ -109,3 +116,16 @@ func (s *RecipeService) CreateRecipe(ctx *gin.Context) (*domain.Recipe, error) { return &recipe, nil } + +// GetRecipe will get a recipe via its ID. Any errors will be bubbled to the caller. Furthermore, +// if the recipe is nil, an error will be returned, so the caller does not need to check for a nil +// recipe (e.g., if the error is nil the recipe exists) +func (s *RecipeService) GetRecipe(id int) (*domain.Recipe, error) { + recipe, err := s.recipeRepository.GetRecipe(id) + + if recipe == nil { + return nil, fmt.Errorf("Failed to get recipe from database. Nil result.") + } + + return recipe, err +} diff --git a/internal/app/service/user_service.go b/internal/app/service/user_service.go index 6bd52f5..96b257d 100644 --- a/internal/app/service/user_service.go +++ b/internal/app/service/user_service.go @@ -1,6 +1,8 @@ package service import ( + "fmt" + "github.com/gin-gonic/gin" domain "github.com/haydenhargreaves/Potion/internal/domain/user" ) @@ -19,6 +21,10 @@ func NewUserService(userRepository domain.UserRepository) domain.UserService { return &UserService{userRepository: userRepository} } +// GetAuthenicatedUser will return the user the is currently authenticated. This assumes that the +// user is actually logged in, if not, a blank user will be returned. To ensure success, call the +// `domain.IsLoggedIn()` function first to ensure the user is logged in. If that passes, this +// function should yield a result. func (s *UserService) GetAuthenicatedUser(ctx *gin.Context) domain.User { val, ok := ctx.Get("userId") if !ok { @@ -26,10 +32,24 @@ func (s *UserService) GetAuthenicatedUser(ctx *gin.Context) domain.User { } id := val.(int) - user, err := s.userRepository.GetUserById(id) + user, err := s.userRepository.GetUser(id) if err != nil { return domain.User{} } return *user } + +// GetUser will get a user from the database via its ID. This is not related to the Google ID in +// any capacity. Any errors will be bubbled to the caller. Furthermore, if the user is nil, an error +// will be returned, so the caller does not need to check for a nil user (e.g., if the error is nil +// the user exists) +func (s *UserService) GetUser(id int) (*domain.User, error) { + user, err := s.userRepository.GetUser(id) + + if user == nil { + return nil, fmt.Errorf("Failed to get user from database. Nil result.") + } + + return user, err +} diff --git a/internal/domain/recipe/repository.go b/internal/domain/recipe/repository.go index 675dbb7..d8eda0e 100644 --- a/internal/domain/recipe/repository.go +++ b/internal/domain/recipe/repository.go @@ -1,6 +1,6 @@ package domain type RecipeRepository interface { - // TODO: Not sure the input type yet CreateRecipe(recipe *Recipe) error + GetRecipe(id int) (*Recipe, error) } diff --git a/internal/domain/recipe/service.go b/internal/domain/recipe/service.go index c05649d..dc2409b 100644 --- a/internal/domain/recipe/service.go +++ b/internal/domain/recipe/service.go @@ -4,4 +4,5 @@ import "github.com/gin-gonic/gin" type RecipeService interface { CreateRecipe(ctx *gin.Context) (*Recipe, error) + GetRecipe(id int) (*Recipe, error) } diff --git a/internal/domain/user/repository.go b/internal/domain/user/repository.go index 3ab045b..4057117 100644 --- a/internal/domain/user/repository.go +++ b/internal/domain/user/repository.go @@ -3,5 +3,5 @@ package domain type UserRepository interface { CreateGoogleUser(googleUserInfo *GoogleUserInfo, googleRefreshToken string) (User, error) GetGoogleUser(googleId string) (*User, error) - GetUserById(id int) (*User, error) + GetUser(id int) (*User, error) } diff --git a/internal/domain/user/service.go b/internal/domain/user/service.go index 65359ce..16a7629 100644 --- a/internal/domain/user/service.go +++ b/internal/domain/user/service.go @@ -4,4 +4,5 @@ import "github.com/gin-gonic/gin" type UserService interface { GetAuthenicatedUser(ctx *gin.Context) User + GetUser(id int) (*User, error) } diff --git a/internal/infrastructure/database/repository/recipe_repository.go b/internal/infrastructure/database/repository/recipe_repository.go index 4024a46..cfbf04d 100644 --- a/internal/infrastructure/database/repository/recipe_repository.go +++ b/internal/infrastructure/database/repository/recipe_repository.go @@ -3,6 +3,7 @@ package repository import ( "database/sql" "encoding/json" + "fmt" domain "github.com/haydenhargreaves/Potion/internal/domain/recipe" "github.com/lib/pq" @@ -22,6 +23,11 @@ func NewRecipeRepository(db *sql.DB) domain.RecipeRepository { } // NOTE: This function modified the provided recipe with the new values, such as id and time stamp + +// CreateRecipe creates a recipe in the database. The recipe provided should contain all data except +// time stamps and the ID; the database will fill them when the operation succeeds. Any errors will +// be bubbled to the caller. The recipe parameter is passed by reference and will therefore be updated +// directly and the new fields (ID, created) can be accessed upon success. func (r *RecipeRepository) CreateRecipe(recipe *domain.Recipe) error { tx, err := r.db.Begin() if err != nil { @@ -81,3 +87,64 @@ func (r *RecipeRepository) CreateRecipe(recipe *domain.Recipe) error { return nil } + +// GetRecipe gets a recipe from the database via its ID. The operation is wrapped in a transaction +// for added safety. The repository will not check for a nil result, instead the service will. Callers +// are responsible for protecting against double nil results. Any errors will be bubbled to the caller. +func (r *RecipeRepository) GetRecipe(id int) (*domain.Recipe, error) { + tx, err := r.db.Begin() + if err != nil { + tx.Rollback() + return nil, err + } + + query := "SELECT * FROM recipes WHERE id = $1" + + var durationBytes []byte + var ingredientBytes []byte + + var recipe domain.Recipe + if err := tx.QueryRow(query, id).Scan( + &recipe.Id, + &recipe.Title, + &recipe.Description, + pq.Array(&recipe.Instructions), + &recipe.Serves, + &recipe.Difficulty, + &durationBytes, + &recipe.Category, + &ingredientBytes, + &recipe.UserId, + &recipe.Modified, + &recipe.Created, + ); err != nil { + return nil, fmt.Errorf("Failed to location recipe in database: %s", err.Error()) + } + + if err := tx.Commit(); err != nil { + tx.Rollback() + return nil, err + } + + // Parse duration + if len(durationBytes) > 0 { + var duration domain.RecipeDuration + if err := json.Unmarshal(durationBytes, &duration); err != nil { + return nil, fmt.Errorf("Failed to parse duration from database: %s", err.Error()) + } + + recipe.Duration = duration + } + + // Parse ingredient + if len(ingredientBytes) > 0 { + var ingredients []domain.RecipeIngredient + if err := json.Unmarshal(ingredientBytes, &ingredients); err != nil { + return nil, fmt.Errorf("Failed to parse ingredients from database: %s", err.Error()) + } + + recipe.Ingredients = ingredients + } + + return &recipe, nil +} diff --git a/internal/infrastructure/database/repository/user_repository.go b/internal/infrastructure/database/repository/user_repository.go index 68a31d6..1bf0b97 100644 --- a/internal/infrastructure/database/repository/user_repository.go +++ b/internal/infrastructure/database/repository/user_repository.go @@ -106,16 +106,20 @@ func (r *UserRepository) GetGoogleUser(googleId string) (*domain.User, error) { return &user, nil } -func (r *UserRepository) GetUserById(id int) (*domain.User, error) { +// GetUser gets a user from the database via its ID. The operation is wrapped in a transaction +// for added safety. The repository will not check for a nil result, instead the service will. +// Callers are responsible for protecting against double nil results. Any errors will be bubbled +// to the caller. +func (r *UserRepository) GetUser(id int) (*domain.User, error) { tx, err := r.db.Begin() if err != nil { tx.Rollback() return nil, err } - var user domain.User - query := `SELECT * FROM users WHERE id = $1` + query := "SELECT * FROM users WHERE id = $1" + var user domain.User if err := tx.QueryRow(query, id).Scan( &user.Id, &user.GoogleId, diff --git a/internal/templates/pages/recipe.templ b/internal/templates/pages/recipe.templ index 0191569..9e19b1e 100644 --- a/internal/templates/pages/recipe.templ +++ b/internal/templates/pages/recipe.templ @@ -1,6 +1,11 @@ package templates -import "github.com/haydenhargreaves/Potion/internal/templates/components" +import ( + domain "github.com/haydenhargreaves/Potion/internal/domain/recipe" + domainUser "github.com/haydenhargreaves/Potion/internal/domain/user" + "github.com/haydenhargreaves/Potion/internal/templates/components" + "time" +) templ servingIcon() {
-

Easy Chicken Alfredo

-

Author: Hayden Hargreaves

-

Category: Dinner

+

{ recipe.Title }

+

Author: { user.Name }

+

Category: { recipe.Category }

- @metadataSection() + @metadataSection(recipe)

About this recipe

-

- A rich and creamy Classic Chicken Curry that's perfect for a comforting weeknight meal. - Tender chicken pieces are simmered in a luscious, spiced sauce with a hint of coconut, making - it an irresistible dish for the whole family. This recipe is designed to be straightforward, - delivering authentic flavors without requiring extensive culinary expertise. Enjoy it with - fluffy basmati rice or warm naan bread! -

+

{ recipe.Description }

- @ingredientList() - @instructionList() - @tagList() + @ingredientList(recipe.Ingredients) + @instructionList(recipe.Instructions) + @tagList(recipe.Created, recipe.Modified)
} -templ metadataSection() { +templ metadataSection(recipe domain.Recipe) {
@timeIcon() -

Prep: 20 min

-

Cook: 45 min

+

Prep: { recipe.Duration.Prep } min

+

Cook: { recipe.Duration.Cook } min

- @starIcon(true) - @starIcon(true) - @starIcon(true) - @starIcon(false) - @starIcon(false) + for _ = range recipe.Difficulty { + @starIcon(true) + } + for _ = range (5 - recipe.Difficulty) { + @starIcon(false) + }
-

Intermediate

+

{ recipe.Difficulty }

@servingIcon() -

Serves 4

+

Serves { recipe.Serves }

} -templ ingredientList() { +templ ingredientList(ingredients []domain.RecipeIngredient) {

Ingredients


} -templ instructionList() { +templ instructionList(instructions []string) {

Instructions


} -templ tagList() { +templ tagList(created time.Time, modified *time.Time) {

Tags


@@ -169,8 +148,10 @@ templ tagList() { @tagListItem("high-protein")
-

Created: January 2, 2025

-

Last Modified: February 14, 2025

+

Created: { created.Format("January 2, 2006") }

+ if modified != nil { +

Last Modified: { modified.Format("January 2, 2006") }

+ }
} @@ -197,7 +178,7 @@ templ ingredientListItem(name, quantity string, odd bool) { } -templ instructionListItem(content string, num int) { +templ instructionListItem(num int, content string) {
  • \"\"

    Easy Chicken Alfredo

    Author: Hayden Hargreaves

    Category: Dinner

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

    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = metadataSection().Render(ctx, templ_7745c5c3_Buffer) + var templ_7745c5c3_Var5 string + templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(recipe.Title) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 72, Col: 75} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "

    About this recipe

    A rich and creamy Classic Chicken Curry that's perfect for a comforting weeknight meal. Tender chicken pieces are simmered in a luscious, spiced sauce with a hint of coconut, making it an irresistible dish for the whole family. This recipe is designed to be straightforward, delivering authentic flavors without requiring extensive culinary expertise. Enjoy it with fluffy basmati rice or warm naan bread!

    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "

    Author: ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = ingredientList().Render(ctx, templ_7745c5c3_Buffer) + var templ_7745c5c3_Var6 string + templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(user.Name) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 73, Col: 66} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = instructionList().Render(ctx, templ_7745c5c3_Buffer) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "

    Category: ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = tagList().Render(ctx, templ_7745c5c3_Buffer) + var templ_7745c5c3_Var7 string + templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(recipe.Category) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 74, Col: 69} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "

    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "

    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = metadataSection(recipe).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "

    About this recipe

    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + 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/recipe.templ`, Line: 79, Col: 49} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "

    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = ingredientList(recipe.Ingredients).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = instructionList(recipe.Instructions).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = tagList(recipe.Created, recipe.Modified).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "
    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -161,7 +218,7 @@ func RecipePage() templ.Component { }) } -func metadataSection() templ.Component { +func metadataSection(recipe domain.Recipe) templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { @@ -177,12 +234,12 @@ func metadataSection() templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var5 := templ.GetChildren(ctx) - if templ_7745c5c3_Var5 == nil { - templ_7745c5c3_Var5 = templ.NopComponent + templ_7745c5c3_Var9 := templ.GetChildren(ctx) + if templ_7745c5c3_Var9 == nil { + templ_7745c5c3_Var9 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "
    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "
    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -190,31 +247,62 @@ func metadataSection() templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "

    Prep: 20 min

    Cook: 45 min

    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "

    Prep: ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = starIcon(true).Render(ctx, templ_7745c5c3_Buffer) + var templ_7745c5c3_Var10 string + templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(recipe.Duration.Prep) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 95, Col: 34} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = starIcon(true).Render(ctx, templ_7745c5c3_Buffer) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, " min

    Cook: ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = starIcon(true).Render(ctx, templ_7745c5c3_Buffer) + var templ_7745c5c3_Var11 string + templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(recipe.Duration.Cook) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 96, Col: 34} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = starIcon(false).Render(ctx, templ_7745c5c3_Buffer) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, " min

    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = starIcon(false).Render(ctx, templ_7745c5c3_Buffer) + for _ = range recipe.Difficulty { + templ_7745c5c3_Err = starIcon(true).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + for _ = range 5 - recipe.Difficulty { + templ_7745c5c3_Err = starIcon(false).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "

    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "

    Intermediate

    ") + var templ_7745c5c3_Var12 string + templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(recipe.Difficulty) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 109, Col: 25} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "

    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -222,7 +310,20 @@ func metadataSection() templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "

    Serves 4

    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "

    Serves ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var13 string + templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(recipe.Serves) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 113, Col: 28} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "

    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -230,7 +331,7 @@ func metadataSection() templ.Component { }) } -func ingredientList() templ.Component { +func ingredientList(ingredients []domain.RecipeIngredient) templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { @@ -246,48 +347,22 @@ func ingredientList() templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var6 := templ.GetChildren(ctx) - if templ_7745c5c3_Var6 == nil { - templ_7745c5c3_Var6 = templ.NopComponent + templ_7745c5c3_Var14 := templ.GetChildren(ctx) + if templ_7745c5c3_Var14 == nil { + templ_7745c5c3_Var14 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "

    Ingredients


      ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "

      Ingredients


        ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = ingredientListItem("Chicken breast", "1lb", false).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err + for i, ingredient := range ingredients { + templ_7745c5c3_Err = ingredientListItem(ingredient.Name, ingredient.Quantity, i%2 == 1).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } } - templ_7745c5c3_Err = ingredientListItem("Butter", "1/2 cup", true).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = ingredientListItem("Heavy cream", "1 cup", false).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = ingredientListItem("Garlic powder", "1/4 tsp", true).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = ingredientListItem("Chicken breast", "1lb", false).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = ingredientListItem("Butter", "1/2 cup", true).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = ingredientListItem("Heavy cream", "1 cup", false).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = ingredientListItem("Garlic powder", "1/4 tsp", true).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "
      ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "
    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -295,7 +370,7 @@ func ingredientList() templ.Component { }) } -func instructionList() templ.Component { +func instructionList(instructions []string) templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { @@ -311,50 +386,22 @@ func instructionList() templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var7 := templ.GetChildren(ctx) - if templ_7745c5c3_Var7 == nil { - templ_7745c5c3_Var7 = templ.NopComponent + templ_7745c5c3_Var15 := templ.GetChildren(ctx) + if templ_7745c5c3_Var15 == nil { + templ_7745c5c3_Var15 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "

    Instructions


      ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "

      Instructions


        ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = instructionListItem(` - Heat vegetable oil in a large skillet or Dutch oven over medium heat. Add chopped onion - and cook until softened, about 5 minutes. - `, 1).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err + for i, instruction := range instructions { + templ_7745c5c3_Err = instructionListItem(i+1, instruction).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } } - templ_7745c5c3_Err = instructionListItem(` - Stir in minced garlic and grated ginger, cooking for another minute until fragrant. Add the curry paste and cook - for 2-3 minutes, stirring constantly, to toast the spices. - `, 2).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = instructionListItem(` - Add the chicken cubes to the pan and cook until lightly browned on all sides. - `, 3).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = instructionListItem(` - Pour in the coconut milk, stirring to combine everything. Bring to a gentle simmer, then reduce heat to low, - cover, and cook for 20-25 minutes, or until chicken is cooked through and tender. - `, 4).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = instructionListItem(` - Taste and adjust seasonings if necessary. Garnish with fresh cilantro before serving. Serve hot with basmati - rice or naan bread. - `, 5).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "
      ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "
    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -362,7 +409,7 @@ func instructionList() templ.Component { }) } -func tagList() templ.Component { +func tagList(created time.Time, modified *time.Time) templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { @@ -378,12 +425,12 @@ func tagList() templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var8 := templ.GetChildren(ctx) - if templ_7745c5c3_Var8 == nil { - templ_7745c5c3_Var8 = templ.NopComponent + templ_7745c5c3_Var16 := templ.GetChildren(ctx) + if templ_7745c5c3_Var16 == nil { + templ_7745c5c3_Var16 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "

    Tags


      ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "

      Tags


        ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -395,7 +442,43 @@ func tagList() templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "

      Created: January 2, 2025

      Last Modified: February 14, 2025

      ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "

    Created: ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var17 string + templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(created.Format("January 2, 2006")) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 151, Col: 91} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "

    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if modified != nil { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "

    Last Modified: ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var18 string + templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(modified.Format("January 2, 2006")) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 153, Col: 92} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "

    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "
    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -419,53 +502,53 @@ func ingredientListItem(name, quantity string, odd bool) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var9 := templ.GetChildren(ctx) - if templ_7745c5c3_Var9 == nil { - templ_7745c5c3_Var9 = templ.NopComponent + templ_7745c5c3_Var19 := templ.GetChildren(ctx) + if templ_7745c5c3_Var19 == nil { + templ_7745c5c3_Var19 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, " ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "> ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var10 string - templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(quantity) + var templ_7745c5c3_Var20 string + templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(quantity) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 196, Col: 45} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 177, Col: 45} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, ": ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, ": ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var11 string - templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(name) + var templ_7745c5c3_Var21 string + templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 196, Col: 63} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 177, Col: 63} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "
  • ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -473,7 +556,7 @@ func ingredientListItem(name, quantity string, odd bool) templ.Component { }) } -func instructionListItem(content string, num int) templ.Component { +func instructionListItem(num int, content string) templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { @@ -489,53 +572,53 @@ func instructionListItem(content string, num int) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var12 := templ.GetChildren(ctx) - if templ_7745c5c3_Var12 == nil { - templ_7745c5c3_Var12 = templ.NopComponent + templ_7745c5c3_Var22 := templ.GetChildren(ctx) + if templ_7745c5c3_Var22 == nil { + templ_7745c5c3_Var22 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "

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

    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var13 string - templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(num) + var templ_7745c5c3_Var23 string + templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(num) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 207, Col: 48} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 188, Col: 48} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "

    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "

    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var14 string - templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(content) + var templ_7745c5c3_Var24 string + templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(content) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 209, Col: 23} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 190, Col: 23} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "

    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "

    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -559,25 +642,25 @@ func tagListItem(content string) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var15 := templ.GetChildren(ctx) - if templ_7745c5c3_Var15 == nil { - templ_7745c5c3_Var15 = templ.NopComponent + templ_7745c5c3_Var25 := templ.GetChildren(ctx) + if templ_7745c5c3_Var25 == nil { + templ_7745c5c3_Var25 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "
  • ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "
  • ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var16 string - templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(content) + var templ_7745c5c3_Var26 string + templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(content) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 215, Col: 11} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 196, Col: 11} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "
  • ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } -- 2.47.2