(FEAT): Finished fixing up the search page!
This commit is contained in:
parent
14b41d204f
commit
54c557bec5
@ -193,7 +193,12 @@ func (s *RecipeService) GetRecipe(id int, userId *int) (*domain.Recipe, error) {
|
||||
//
|
||||
// The favorites parameter is used to only return filters favorited by the userId provided.
|
||||
func (s *RecipeService) SearchRecipes(filters domain.SearchFilters, userId *int, favorites bool) ([]domain.Recipe, error) {
|
||||
return s.recipeRepository.SearchRecipes(filters, userId, favorites)
|
||||
ids, err := s.recipeRepository.SearchRecipes(filters, userId, favorites)
|
||||
if err != nil {
|
||||
return []domain.Recipe{}, err
|
||||
}
|
||||
|
||||
return s.recipeRepository.GetRecipes(ids, userId)
|
||||
}
|
||||
|
||||
// GetUserRecipes returns a list of the recipes that the user has created. The user's
|
||||
|
||||
@ -4,7 +4,7 @@ type RecipeRepository interface {
|
||||
CreateRecipe(recipe *Recipe) error
|
||||
GetRecipe(id int, userId *int) (*Recipe, error)
|
||||
GetRecipes(ids []int, userId *int) ([]Recipe, error)
|
||||
SearchRecipes(filters SearchFilters, userId *int, favorites bool) ([]Recipe, error)
|
||||
SearchRecipes(filters SearchFilters, userId *int, favorites bool) ([]int, error)
|
||||
CreateRecipeTags(recipe Recipe, tags []string) error
|
||||
GetUserRecipes(id int) ([]Recipe, error)
|
||||
GetUserFavoriteRecipes(id int) ([]Recipe, error)
|
||||
|
||||
@ -221,7 +221,10 @@ func isBitActive(bits, pos int) bool {
|
||||
// TODO: Pagination is required, to provide infinite scroll.
|
||||
//
|
||||
// TODO: This does not work in the current build, the DB does not return valid values.
|
||||
func (r *RecipeRepository) SearchRecipes(filters domain.SearchFilters, userId *int, favorites bool) ([]domain.Recipe, error) {
|
||||
//
|
||||
// 12/28/25: This function has changed, now longer returns the recipes, but their IDs for fetching
|
||||
// elsewhere.
|
||||
func (r *RecipeRepository) SearchRecipes(filters domain.SearchFilters, userId *int, favorites bool) ([]int, error) {
|
||||
// Compute meals type filters (there are 7 bits)
|
||||
var mealConditions []string
|
||||
for i := range 7 {
|
||||
@ -305,17 +308,6 @@ func (r *RecipeRepository) SearchRecipes(filters domain.SearchFilters, userId *i
|
||||
// Define columns to select. More fields can be added if the full text search is required
|
||||
columns := []string{
|
||||
"r.id",
|
||||
"r.title",
|
||||
"r.description",
|
||||
"r.instructions",
|
||||
"r.serves",
|
||||
"r.difficulty",
|
||||
"r.duration",
|
||||
"r.category",
|
||||
"r.ingredients",
|
||||
"r.userid",
|
||||
"r.modified",
|
||||
"r.created",
|
||||
}
|
||||
|
||||
// TODO: Need to add these to the query
|
||||
@ -392,76 +384,21 @@ func (r *RecipeRepository) SearchRecipes(filters domain.SearchFilters, userId *i
|
||||
// Execute the query
|
||||
rows, err := r.db.Query(query)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query recipes: %w", err)
|
||||
return []int{}, fmt.Errorf("failed to query recipes: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var recipes []domain.Recipe
|
||||
var ids []int
|
||||
for rows.Next() {
|
||||
// Parsed values location
|
||||
var recipe domain.Recipe
|
||||
var durationBytes []byte
|
||||
var ingredientBytes []byte
|
||||
|
||||
if err := rows.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 scan recipe row: %w", err)
|
||||
var id int
|
||||
if err := rows.Scan(&id); err != nil {
|
||||
return []int{}, fmt.Errorf("failed to extract ID: %s\n", err.Error())
|
||||
}
|
||||
|
||||
// Parse duration from bytes
|
||||
if len(durationBytes) > 0 {
|
||||
var duration domain.RecipeDuration
|
||||
if err := json.Unmarshal(durationBytes, &duration); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse duration for recipe ID %d: %w", recipe.Id, err)
|
||||
}
|
||||
recipe.Duration = duration
|
||||
} else {
|
||||
recipe.Duration = domain.RecipeDuration{}
|
||||
ids = append(ids, id)
|
||||
}
|
||||
|
||||
// Parse ingredients from bytes
|
||||
if len(ingredientBytes) > 0 {
|
||||
var ingredients []domain.RecipeIngredient
|
||||
if err := json.Unmarshal(ingredientBytes, &ingredients); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse ingredients for recipe ID %d: %w", recipe.Id, err)
|
||||
}
|
||||
recipe.Ingredients = ingredients
|
||||
} else {
|
||||
recipe.Ingredients = []domain.RecipeIngredient{}
|
||||
}
|
||||
|
||||
// Add tags
|
||||
if err := r.GetRecipeTags(&recipe); err != nil {
|
||||
fmt.Printf("ERROR getting recipe tags. %s\n", err.Error())
|
||||
}
|
||||
|
||||
// Add recipe if not a favorite search
|
||||
if !favorites && userId != nil {
|
||||
if err := r.GetRecipeFavorite(&recipe, *userId); err != nil {
|
||||
fmt.Printf("ERROR getting recipe tags. %s\n", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if favorites {
|
||||
recipe.Favorite = true
|
||||
}
|
||||
|
||||
recipes = append(recipes, recipe)
|
||||
}
|
||||
|
||||
return recipes, nil
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
// CreateRecipeTags accepts a list of tags (names) and a recipe (already created by the DB) and
|
||||
|
||||
@ -40,10 +40,10 @@ export default function RecipeMetaData({ recipe }: RecipeMetaDataProps) {
|
||||
>
|
||||
<div className="flex gap-x-1 my-2">
|
||||
{Array.from({ length: recipe?.Difficulty ?? 0 }).map((_, i) => (
|
||||
<StarIcon key={`${recipe?.Id}-filled-${i}`} filled={true} />
|
||||
<StarIcon key={`${recipe?.Id}-filled-${i}`} size={6} filled={true} />
|
||||
))}
|
||||
{Array.from({ length: 5 - (recipe?.Difficulty ?? 0) }).map((_, i) => (
|
||||
<StarIcon key={`${recipe?.Id}-unfilled-${i}`} filled={false} />
|
||||
<StarIcon key={`${recipe?.Id}-unfilled-${i}`} size={6} filled={false} />
|
||||
))}
|
||||
</div>
|
||||
<p>{displayDifficulty(recipe?.Difficulty ?? 0)}</p>
|
||||
|
||||
@ -1,19 +1,20 @@
|
||||
interface StarIconProps {
|
||||
filled: boolean;
|
||||
size: number;
|
||||
};
|
||||
|
||||
export default function StarIcon({ filled }: StarIconProps) {
|
||||
export default function StarIcon({ filled, size = 6 }: StarIconProps) {
|
||||
|
||||
return <>
|
||||
{filled ? (
|
||||
<svg className="h-6 text-blue-600" fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg className={`h-${size} text-blue-600`} fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M23.632 9.201a.628.628 0 0 1-.22.678l-5.726 4.96 1.727 7.394a.606.606 0 0 1-.935.676l-6.503-3.953-6.503 3.953a.713.713 0 0 1-.374.112.57.57 0 0 1-.34-.109.629.629 0 0 1-.222-.679l1.729-7.393L.539 9.879A.607.607 0 0 1 .897 8.78l7.536-.635 2.965-7.083a.62.62 0 0 1 1.155.001l2.965 7.082 7.536.635a.63.63 0 0 1 .578.42z"
|
||||
></path>
|
||||
<path fill="none" d="M0 0h24v24H0z"></path>
|
||||
</svg>
|
||||
) : (
|
||||
<svg className="h-6 text-gray-500" fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg className={`h-${size} text-gray-500`} fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M23.054 8.781l-7.536-.635-2.965-7.082a.619.619 0 0 0-1.155 0L8.433 8.145.896 8.78a.607.607 0 0 0-.357 1.1l5.726 4.96-1.729 7.395a.63.63 0 0 0 .223.679.573.573 0 0 0 .339.108.717.717 0 0 0 .374-.111l6.503-3.954 6.503 3.953a.606.606 0 0 0 .935-.677l-1.727-7.392 5.725-4.96a.607.607 0 0 0-.357-1.099zm-6.48 5.698l1.662 7.113-6.261-3.806-6.262 3.807 1.663-7.114-5.513-4.776 7.257-.611 2.855-6.817 2.855 6.817 7.257.611z"
|
||||
></path>
|
||||
|
||||
@ -41,10 +41,10 @@ export default function RecipeSearchResult({ recipe }: RecipeSearchResultProps)
|
||||
</span>
|
||||
<span className="flex gap-x-1 align-center">
|
||||
{Array.from({ length: recipe.Difficulty }).map((_, i) => (
|
||||
<StarIcon key={`${recipe.Id}-filled-${i}`} filled={true} />
|
||||
<StarIcon key={`${recipe.Id}-filled-${i}`} size={4} filled={true} />
|
||||
))}
|
||||
{Array.from({ length: 5 - (recipe.Difficulty) }).map((_, i) => (
|
||||
<StarIcon key={`${recipe.Id}-unfilled-${i}`} filled={false} />
|
||||
<StarIcon key={`${recipe.Id}-unfilled-${i}`} size={4} filled={false} />
|
||||
))}
|
||||
</span>
|
||||
<span className="flex gap-x-1 align-center">
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user