package service import ( "errors" "fmt" "net/http" "strconv" "strings" "time" "github.com/gin-gonic/gin" domain "github.com/haydenhargreaves/Potion/internal/domain/recipe" domainServer "github.com/haydenhargreaves/Potion/internal/domain/server" ) // RecipeService implements the domain.RecipeService defined in the domain module. type RecipeService struct { recipeRepository domain.RecipeRepository } // Compile-time check to ensure the RecipeService implements domain.RecipeService var _ domain.RecipeService = (*RecipeService)(nil) // NewRecipeService creates a user service object which can be passed into the context. The service // requires a recipe repository which it will use to hit the database when needed. func NewRecipeService(recipeRepository domain.RecipeRepository) domain.RecipeService { 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) { return nil, fmt.Errorf("User is not logged in.") } title := ctx.PostForm("title") description := ctx.PostForm("description") preparation := ctx.PostForm("preparation-time") cook := ctx.PostForm("cook-time") serving := ctx.PostForm("serving-size") category := ctx.PostForm("category") difficulty := ctx.PostForm("difficulty") ingredients := ctx.PostFormArray("ingredients") quantity := ctx.PostFormArray("quantity") instructions := ctx.PostFormArray("instructions") tags := strings.Split(ctx.PostForm("tags"), ",") userId := ctx.MustGet("userId").(int) // Have to get the image differently image, err := ctx.FormFile("image") if err != nil && !errors.Is(err, http.ErrMissingFile) { // Error getting image } // Convert to proper values servingInt, _ := strconv.Atoi(serving) difficultyInt, _ := strconv.Atoi(difficulty) prepInt, _ := strconv.Atoi(preparation) cookInt, _ := strconv.Atoi(cook) var ingredientSlice []domain.RecipeIngredient for i := range len(ingredients) { if strings.TrimSpace(ingredients[i]) != "" { ins := domain.RecipeIngredient{ Name: ingredients[i], Quantity: quantity[i], } ingredientSlice = append(ingredientSlice, ins) } } var instructionSlice []string for _, ins := range instructions { if ins != "" { instructionSlice = append(instructionSlice, ins) } } // Create the recipe recipe := domain.Recipe{ Title: title, Description: description, Instructions: instructionSlice, Serves: servingInt, Difficulty: difficultyInt, Duration: domain.RecipeDuration{ Total: prepInt + cookInt, Prep: prepInt, Cook: cookInt, }, Category: domain.RecipeMeal(category), Ingredients: ingredientSlice, UserId: userId, Created: time.Now(), } if err := s.recipeRepository.CreateRecipe(&recipe); err != nil { return &recipe, err } // TODO: Upload the image if image != nil { } // TODO: Create the tags in the database if len(tags) > 0 { } 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 }