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() {