recipes2/internal/storage/postgresql/postgresql.go

243 lines
5.3 KiB
Go

package postgresql
import (
"context"
"fmt"
"recipes/internal/domain/models"
"github.com/jackc/pgx/v5/pgxpool"
)
type Storage struct {
db *pgxpool.Pool
}
func New(ctx context.Context, user, password, addr, dbname string) (*Storage, error) {
const op = "storage.postgresql.New"
pool, err := pgxpool.New(ctx, fmt.Sprintf("postgres://%s:%s@%s/%s?sslmode=disable", user, password, addr, dbname))
if err != nil {
return nil, fmt.Errorf("%s: %w", op, err)
}
return &Storage{db: pool}, nil
}
// AddRecipe adds recipe to db.
func (s *Storage) AddRecipe(ctx context.Context, recipe models.Recipe) error {
const op = "storage.postgresql.AddRecipe"
// open transaction
tx, err := s.db.Begin(ctx)
if err != nil {
return fmt.Errorf("%s: %w", op, err)
}
// refactor error handling
// rollback transaction on return
defer tx.Rollback(ctx)
var id uint
// insert recipe
err = s.db.QueryRow(
ctx,
"insert into recipe (title, description, image, cooking_time, servings, cal) values ($1, $2, $3, $4, $5, $6) returning id",
recipe.Title, recipe.Description, recipe.Image, recipe.CookingTime, recipe.ServingsNum, recipe.Calories,
).Scan(&id)
// insert ingredients
for _, r := range recipe.Ingredients {
var ing_id uint
err = s.db.QueryRow(
ctx,
"insert into recipe_ingredients_group (recipe_id, title) values ($1, $2) returning id",
id, r.Title,
).Scan(&ing_id)
if err != nil {
return fmt.Errorf("%s: %w", op, err)
}
for _, i := range r.Ingredients {
_, err = s.db.Exec(
ctx,
"insert into recipe_ingredients (recipe_ingredients_group_id, ingredient) values ($1, $2)",
ing_id, i,
)
if err != nil {
return fmt.Errorf("%s: %w", op, err)
}
}
}
// insert steps
for i, step := range recipe.Recipe_steps {
s.db.Exec(
ctx,
"insert into recipe_steps (recipe_id, step_num, step_text) values ($1, $2, $3)",
id, i, step,
)
}
// insert advices
for _, a := range recipe.Advices {
s.db.Exec(
ctx,
"insert into recipe_advices (recipe_id, advice) values ($1, $2)",
id, a,
)
}
// insert categories
for _, c := range recipe.Categories {
s.db.Exec(
ctx,
"insert into recipe_categories (recipe_id, category) values ($1, $2)",
id, c,
)
}
// commit transaction
if err := tx.Commit(ctx); err != nil {
return fmt.Errorf("%s: %w", op, err)
}
return nil
}
// RecipeExists returns true if recipe is exists.
func (s *Storage) RecipeExists(ctx context.Context, title string) (bool, error) {
const op = "storage.postgresql.RecipeExists"
var exists bool
err := s.db.QueryRow(
ctx,
"select exists(select id from recipe where title = $1)",
title,
).Scan(&exists)
if err != nil {
return false, fmt.Errorf("%s: %w", op, err)
}
return exists, nil
}
// GetRecipes gets recipes by offset and limit.
func (s *Storage) GetRecipes(ctx context.Context, offset, limit int) ([]models.Recipe, error) {
const op = "storage.postgresql.GetRecipes"
rows, err := s.db.Query(
ctx,
"select id, title, image, cooking_time, cal from recipe order by id desc offset $1 limit $2",
offset, limit,
)
if err != nil {
return nil, fmt.Errorf("%s: %w", op, err)
}
defer rows.Close()
var recipes []models.Recipe = make([]models.Recipe, 0, limit)
for rows.Next() {
var r models.Recipe
err = rows.Scan(
&r.ID,
&r.Title,
&r.Image,
&r.CookingTime,
&r.Calories,
)
if err != nil {
return nil, fmt.Errorf("%s: %w", op, err)
}
recipes = append(recipes, r)
}
return recipes, nil
}
// GetRecipe gets recipe by id.
func (s *Storage) GetRecipe(ctx context.Context, r_id uint) (models.Recipe, error) {
const op = "storage.postgresql.GetRecipe"
var recipe models.Recipe
err := s.db.QueryRow(
ctx,
"select title, description, image, cooking_time, servings, cal from recipe where id = $1",
r_id,
).Scan(&recipe)
if err != nil {
return models.Recipe{}, fmt.Errorf("%s: %w", op, err)
}
err = s.AddRecipeInformation(ctx, &recipe)
if err != nil {
return models.Recipe{}, fmt.Errorf("%s: %w", op, err)
}
return recipe, nil
}
// TODO
// handling special errors
// AddRecipeInformation
// AddRecipeInformation adds to recipe struct info about ingredients, steps, advices, categories.
func (s *Storage) AddRecipeInformation(ctx context.Context, r *models.Recipe) error {
const op = "storage.postgresql.AddRecipeInformation"
// select ingredients
// select steps
// select advices
// select categories
return nil
}
// GetRecipesByCategory gets recipes by category, offset and limit.
func (s *Storage) GetRecipesByCategory(ctx context.Context, offset, limit int, category string) ([]models.Recipe, error) {
const op = "storage.postgresql.GetRecipesByCategory"
rows, err := s.db.Query(
ctx,
"select r.id, r.title, r.image, r.cooking_time, r.cal from recipe r inner join recipe_categories c on r.id = c.recipe_id where c.category = $1 order by r.id limit $2 offset $3",
category, limit, offset,
)
if err != nil {
return nil, fmt.Errorf("%s: %w", op, err)
}
defer rows.Close()
var recipes []models.Recipe = make([]models.Recipe, 0, limit)
for rows.Next() {
var r models.Recipe
err = rows.Scan(
&r.ID,
&r.Title,
&r.Image,
&r.CookingTime,
&r.Calories,
)
if err != nil {
return nil, fmt.Errorf("%s: %w", op, err)
}
s.AddRecipeInformation(ctx, &r)
recipes = append(recipes, r)
}
return recipes, nil
}