Merge pull request '(DB/FEAT): Recipe tags have been implemented!' (#16) from feature/tags into master

Reviewed-on: #16
This commit is contained in:
Hayden Hargreaves 2025-07-12 13:38:52 -07:00
commit a7490a2667
8 changed files with 203 additions and 59 deletions

View File

@ -275,16 +275,16 @@ found in **OTHER** section.
- [ ] RecipeId (FK: Recipe.Id, Required) Serial
- [ ] Created (Required) date/time stamp
- [ ] Tags: Represents a single tag that can be had by many recipes.
- [ ] ID (PK) Serial
- [ ] Name (Unique, Required) string(32)
- [ ] Created (Required) date/time stamp
- [x] Tags: Represents a single tag that can be had by many recipes.
- [x] ID (PK) Serial
- [x] Name (Unique, Required) string(32)
- [x] Created (Required) date/time stamp
- [ ] RecipeTags: **Many-to-many** table to represent a list of tags on a recipe.
- [ ] ID (PK) Serial
- [ ] RecipeId (FK: Recipe.Id, Required) Serial
- [ ] TagId (FK: Tag.Id, Required) Serial
- [ ] Created (Required) date/time stamp
- [x] RecipeTags: **Many-to-many** table to represent a list of tags on a recipe.
- [x] ID (PK) Serial
- [x] RecipeId (FK: Recipe.Id, Required) Serial
- [x] TagId (FK: Tag.Id, Required) Serial
- [x] Created (Required) date/time stamp
- [ ] Lists: Represents a single users shopping list.
- [ ] ID (PK) Serial

View File

@ -112,6 +112,9 @@ func (s *RecipeService) CreateRecipe(ctx *gin.Context) (*domain.Recipe, error) {
// TODO: Create the tags in the database
if len(tags) > 0 {
if err := s.recipeRepository.CreateRecipeTags(recipe, tags); err != nil {
return &recipe, fmt.Errorf("Failed to attach/create tags. %s\n", err.Error())
}
}
return &recipe, nil

View File

@ -54,7 +54,8 @@ type RecipeIngredient struct {
}
// Recipe is the database model of a recipe. There is no need to map to a different model so
// this will remain in the domain.
// this will remain in the domain. The Tags field should be loaded from the external Tags table,
// but is still attached to this domain object.
type Recipe struct {
Id int
Title string
@ -68,6 +69,7 @@ type Recipe struct {
UserId int
Modified *time.Time // Pointer to allow null
Created time.Time
Tags []Tag
}
// SearchFilters is a model which represents the required filters to complete a recipe search.
@ -80,3 +82,20 @@ type SearchFilters struct {
Difficulty int
ServingSize int
}
// Tag is a model which represents a single tag in the Tags table. A tag is mapped to a recipe
// using the RecipeTag data model and can be accessed via their ID from the DB.
type Tag struct {
Id int
Name string
Created time.Time
}
// RecipeTag is a model which represents a single mapping in the RecipeTags table. This model
// is a many-to-many mapping for tags to recipes.
type RecipeTag struct {
Id int
RecipeId int
TagId int
Created time.Time
}

View File

@ -4,4 +4,5 @@ type RecipeRepository interface {
CreateRecipe(recipe *Recipe) error
GetRecipe(id int) (*Recipe, error)
SearchRecipes(filters SearchFilters) ([]Recipe, error)
CreateRecipeTags(recipe Recipe, tags []string) error
}

View File

@ -0,0 +1,26 @@
-- Author: Hayden Hargreaves (hhargreaves2006@gmail.com)
-- Desc: Create tables for the recipe tags and mapping tags to recipes.
-- Date: 07/12/2025
BEGIN;
-- Create the tags table itself
CREATE TABLE IF NOT EXISTS Tags (
Id SERIAL PRIMARY KEY NOT NULL,
Name VARCHAR(32) UNIQUE NOT NULL,
Created TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Create the recipe tag table to map tags to a recipe. This DB will be dynamically
-- cleared when recipes or tags are deleted. This will prevent issues when deleting
-- tags or recipes.
CREATE TABLE IF NOT EXISTS RecipeTags (
Id SERIAL PRIMARY KEY NOT NULL,
RecipeId INT NOT NULL,
TagId INT NOT NULL,
Created TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT fk_recipe_id FOREIGN KEY (RecipeId) REFERENCES Recipes (Id) ON DELETE CASCADE,
CONSTRAINT fk_tag_id FOREIGN KEY (TagId) REFERENCES Tags (Id) ON DELETE CASCADE
);
COMMIT;

View File

@ -128,6 +128,29 @@ func (r *RecipeRepository) GetRecipe(id int) (*domain.Recipe, error) {
return nil, fmt.Errorf("Failed to location recipe in database: %s", err.Error())
}
// Get tags from external tables
query = `
SELECT t.* FROM tags t
JOIN recipetags rt ON rt.tagid = t.id
WHERE rt.recipeid = $1;
`
rows, err := tx.Query(query, recipe.Id)
if err != nil {
return nil, fmt.Errorf("Failed to get tags for recipe. %s\n", err.Error())
}
defer rows.Close()
for rows.Next() {
var tag domain.Tag
err := rows.Scan(&tag.Id, &tag.Name, &tag.Created)
if err != nil {
return nil, fmt.Errorf("Failed to scan tag onto domain model. %s\n", err.Error())
}
recipe.Tags = append(recipe.Tags, tag)
}
if err := tx.Commit(); err != nil {
tx.Rollback()
return nil, err
@ -374,3 +397,62 @@ func (r *RecipeRepository) SearchRecipes(filters domain.SearchFilters) ([]domain
return recipes, nil
}
// CreateRecipeTags accepts a list of tags (names) and a recipe (already created by the DB) and
// creates the tags that do not exists, and adds those that do exist to the mapping table for the
// recipe. The result is records in the RecipeTags mapping table that represent all of the new
// and existing tags provided to this function. The recipe object must only contain an ID to call
// this function successfully, therefore, it must be an existing recipe. Any errors will be bubbled
// to the caller.
func (r *RecipeRepository) CreateRecipeTags(recipe domain.Recipe, tags []string) error {
tx, err := r.db.Begin()
if err != nil {
tx.Rollback()
return err
}
// Normalize the tag names (lower case with trimmed space)
normalized := make(map[string]struct{}) // Use map to disallow duplicates
for _, tag := range tags {
normalized[strings.ToLower(strings.TrimSpace(tag))] = struct{}{}
}
// Insert the tags into the DB and return their IDS into the tag ID list
var tagIds []int
for tag := range normalized {
var tagId int
query := `
INSERT INTO tags (name) VALUES ($1)
ON CONFLICT (name) DO UPDATE SET name = EXCLUDED.name
RETURNING id;
`
err := tx.QueryRow(query, tag).Scan(&tagId)
if err != nil {
return fmt.Errorf("Failed to retrieve or create tag. %s\n", err.Error())
}
tagIds = append(tagIds, tagId)
}
// Using a prepared statement, execute the mapping insertions one-by-one
for _, id := range tagIds {
stmt, err := tx.Prepare("INSERT INTO RecipeTags (RecipeId, TagId) VALUES ($1, $2);")
if err != nil {
return fmt.Errorf("Failed to create statement for recipe tag mapping. %s\n", err.Error())
}
defer stmt.Close()
if _, err := stmt.Exec(recipe.Id, id); err != nil {
return fmt.Errorf("Failed to insert tag-recipe-mapping. %s\n", err.Error())
}
}
if err := tx.Commit(); err != nil {
tx.Rollback()
return err
}
return nil
}

View File

@ -80,7 +80,7 @@ templ RecipePage(recipe domain.Recipe, user domainUser.User) {
</div>
@ingredientList(recipe.Ingredients)
@instructionList(recipe.Instructions)
@tagList(recipe.Created, recipe.Modified)
@tagList(recipe.Tags, recipe.Created, recipe.Modified)
</div>
</div>
}
@ -150,14 +150,17 @@ templ instructionList(instructions []string) {
</div>
}
templ tagList(created time.Time, modified *time.Time) {
templ tagList(tags []domain.Tag, created time.Time, modified *time.Time) {
<div class="px-4 py-4 md:px-8">
<h2 class="text-2xl text-gray-800 font-semibold mb-2">Tags</h2>
<hr class="text-gray-300"/>
<ul id="tag-list" class="my-4 flex gap-1 flex-wrap">
@tagListItem("healthy")
@tagListItem("high-protein")
</ul>
if len(tags) > 0 {
<h2 class="text-2xl text-gray-800 font-semibold mb-2">Tags</h2>
<hr class="text-gray-300"/>
<ul id="tag-list" class="my-4 flex gap-1 flex-wrap">
for _, tag := range tags {
@tagListItem(tag.Name)
}
</ul>
}
<hr class="text-gray-300"/>
<p class="my-4 mb-1.5 text-sm text-gray-700">Created: { created.Format("January 2, 2006") }</p>
if modified != nil {
@ -190,11 +193,13 @@ templ ingredientListItem(name, quantity string, odd bool) {
}
templ instructionListItem(num int, content string) {
<li if num % 2==0 {
class="p-4 flex items-start gap-x-4 bg-[#f8f8f8]"
} else {
class="p-4 flex items-start gap-x-4"
}>
<li
if num % 2==0 {
class="p-4 flex items-start gap-x-4 bg-[#f8f8f8]"
} else {
class="p-4 flex items-start gap-x-4"
}
>
<div class="size-10 bg-blue-50 rounded-full flex items-center justify-center flex-shrink-0">
<h3 class="text-blue-600 font-semibold">{ num }</h3>
</div>

View File

@ -206,7 +206,7 @@ func RecipePage(recipe domain.Recipe, user domainUser.User) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = tagList(recipe.Created, recipe.Modified).Render(ctx, templ_7745c5c3_Buffer)
templ_7745c5c3_Err = tagList(recipe.Tags, recipe.Created, recipe.Modified).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -427,7 +427,7 @@ func instructionList(instructions []string) templ.Component {
})
}
func tagList(created time.Time, modified *time.Time) templ.Component {
func tagList(tags []domain.Tag, 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 {
@ -448,55 +448,63 @@ func tagList(created time.Time, modified *time.Time) templ.Component {
templ_7745c5c3_Var15 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "<div class=\"px-4 py-4 md:px-8\"><h2 class=\"text-2xl text-gray-800 font-semibold mb-2\">Tags</h2><hr class=\"text-gray-300\"><ul id=\"tag-list\" class=\"my-4 flex gap-1 flex-wrap\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "<div class=\"px-4 py-4 md:px-8\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = tagListItem("healthy").Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
if len(tags) > 0 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "<h2 class=\"text-2xl text-gray-800 font-semibold mb-2\">Tags</h2><hr class=\"text-gray-300\"><ul id=\"tag-list\" class=\"my-4 flex gap-1 flex-wrap\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, tag := range tags {
templ_7745c5c3_Err = tagListItem(tag.Name).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "</ul>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = tagListItem("high-protein").Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "</ul><hr class=\"text-gray-300\"><p class=\"my-4 mb-1.5 text-sm text-gray-700\">Created: ")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "<hr class=\"text-gray-300\"><p class=\"my-4 mb-1.5 text-sm text-gray-700\">Created: ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var16 string
templ_7745c5c3_Var16, 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: 162, Col: 91}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 165, Col: 91}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "</p>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if modified != nil {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "<p class=\"mb-4 text-sm text-gray-700\">Last Modified: ")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "<p class=\"mb-4 text-sm text-gray-700\">Last Modified: ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var17 string
templ_7745c5c3_Var17, 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: 164, Col: 92}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 167, Col: 92}
}
_, 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, 33, "</p>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "</div>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -525,48 +533,48 @@ func ingredientListItem(name, quantity string, odd bool) templ.Component {
templ_7745c5c3_Var18 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "<li")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "<li")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if odd {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, " class=\"p-2 hover:bg-gray-100 transition-all duration-300 rounded-sm flex items-center justify-start bg-[#f8f8f8]\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, " class=\"p-2 hover:bg-gray-100 transition-all duration-300 rounded-sm flex items-center justify-start bg-[#f8f8f8]\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, " class=\"p-2 hover:bg-gray-100 transition-all duration-300 rounded-sm flex items-center justify-start\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, " class=\"p-2 hover:bg-gray-100 transition-all duration-300 rounded-sm flex items-center justify-start\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "><span class=\"mr-4\"><svg class=\"h-4 text-gray-400\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12Z\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"></path></svg></span> <span class=\"font-semibold mr-2\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "><span class=\"mr-4\"><svg class=\"h-4 text-gray-400\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12Z\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"></path></svg></span> <span class=\"font-semibold mr-2\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var19 string
templ_7745c5c3_Var19, 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: 188, Col: 45}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 191, Col: 45}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, ": </span> ")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, ": </span> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var20 string
templ_7745c5c3_Var20, 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: 188, Col: 63}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 191, Col: 63}
}
_, 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, 40, "</li>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "</li>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -595,48 +603,48 @@ func instructionListItem(num int, content string) templ.Component {
templ_7745c5c3_Var21 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "<li")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "<li")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if num%2 == 0 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, " class=\"p-4 flex items-start gap-x-4 bg-[#f8f8f8]\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, " class=\"p-4 flex items-start gap-x-4 bg-[#f8f8f8]\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, " class=\"p-4 flex items-start gap-x-4\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, " class=\"p-4 flex items-start gap-x-4\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "><div class=\"size-10 bg-blue-50 rounded-full flex items-center justify-center flex-shrink-0\"><h3 class=\"text-blue-600 font-semibold\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "><div class=\"size-10 bg-blue-50 rounded-full flex items-center justify-center flex-shrink-0\"><h3 class=\"text-blue-600 font-semibold\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var22 string
templ_7745c5c3_Var22, 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: 199, Col: 48}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 204, Col: 48}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "</h3></div><p class=\"\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "</h3></div><p class=\"\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var23 string
templ_7745c5c3_Var23, 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: 201, Col: 23}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 206, Col: 23}
}
_, 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, 46, "</p></li>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "</p></li>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -665,20 +673,20 @@ func tagListItem(content string) templ.Component {
templ_7745c5c3_Var24 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "<li class=\"text-sm items-center bg-blue-100 text-blue-700 w-fit px-3 py-1.5 rounded-full\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "<li class=\"text-sm items-center bg-blue-100 text-blue-700 w-fit px-3 py-1.5 rounded-full\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var25 string
templ_7745c5c3_Var25, 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: 207, Col: 11}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 212, Col: 11}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "</li>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "</li>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}