2024-01-16 21:10:29 +02:00
package postgresql
import (
2024-01-18 18:48:27 +02:00
"context"
2024-01-22 17:25:45 +02:00
"errors"
2024-01-18 18:48:27 +02:00
"fmt"
2024-01-19 14:49:26 +02:00
"recipes/internal/domain/models"
2024-01-22 17:25:45 +02:00
"recipes/internal/storage"
2024-01-18 18:48:27 +02:00
2024-01-22 17:25:45 +02:00
"github.com/jackc/pgx/v5"
2024-01-16 21:10:29 +02:00
"github.com/jackc/pgx/v5/pgxpool"
)
2024-01-19 14:49:26 +02:00
type Storage struct {
2024-01-18 18:48:27 +02:00
db * pgxpool . Pool
2024-01-16 21:10:29 +02:00
}
2024-01-20 20:33:49 +02:00
func New ( ctx context . Context , user , password , addr , dbname string ) ( * Storage , error ) {
2024-01-18 18:48:27 +02:00
const op = "storage.postgresql.New"
2024-01-20 20:33:49 +02:00
pool , err := pgxpool . New ( ctx , fmt . Sprintf ( "postgres://%s:%s@%s/%s?sslmode=disable" , user , password , addr , dbname ) )
2024-01-18 18:48:27 +02:00
if err != nil {
return nil , fmt . Errorf ( "%s: %w" , op , err )
}
2024-01-16 21:10:29 +02:00
2024-01-19 14:49:26 +02:00
return & Storage { db : pool } , nil
2024-01-16 21:10:29 +02:00
}
2024-01-19 14:49:26 +02:00
// 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 )
2024-01-21 14:48:47 +02:00
if err != nil {
return fmt . Errorf ( "%s: %w" , op , err )
}
2024-01-19 14:49:26 +02:00
// 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 {
2024-01-21 14:48:47 +02:00
_ , err = s . db . Exec (
2024-01-19 14:49:26 +02:00
ctx ,
"insert into recipe_steps (recipe_id, step_num, step_text) values ($1, $2, $3)" ,
id , i , step ,
)
2024-01-21 14:48:47 +02:00
if err != nil {
return fmt . Errorf ( "%s: %w" , op , err )
}
2024-01-19 14:49:26 +02:00
}
// insert advices
for _ , a := range recipe . Advices {
2024-01-21 14:48:47 +02:00
_ , err = s . db . Exec (
2024-01-19 14:49:26 +02:00
ctx ,
"insert into recipe_advices (recipe_id, advice) values ($1, $2)" ,
id , a ,
)
2024-01-21 14:48:47 +02:00
if err != nil {
return fmt . Errorf ( "%s: %w" , op , err )
}
2024-01-19 14:49:26 +02:00
}
// insert categories
for _ , c := range recipe . Categories {
2024-01-21 14:48:47 +02:00
_ , err = s . db . Exec (
2024-01-19 14:49:26 +02:00
ctx ,
"insert into recipe_categories (recipe_id, category) values ($1, $2)" ,
id , c ,
)
2024-01-21 14:48:47 +02:00
if err != nil {
return fmt . Errorf ( "%s: %w" , op , err )
}
2024-01-19 14:49:26 +02:00
}
// 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
}
2024-01-19 21:00:03 +02:00
// GetRecipes gets recipes by offset and limit.
2024-01-19 14:49:26 +02:00
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
}
2024-01-19 21:00:03 +02:00
// GetRecipe gets recipe by id.
func ( s * Storage ) GetRecipe ( ctx context . Context , r_id uint ) ( models . Recipe , error ) {
const op = "storage.postgresql.GetRecipe"
2024-01-19 14:49:26 +02:00
2024-01-19 21:00:03 +02:00
var recipe models . Recipe
2024-01-19 14:49:26 +02:00
2024-01-19 21:00:03 +02:00
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 {
2024-01-22 17:25:45 +02:00
if errors . Is ( err , pgx . ErrNoRows ) {
return models . Recipe { } , storage . ErrRecipeNotFound
}
2024-01-19 21:00:03 +02:00
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
}
// 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
2024-01-21 16:10:21 +02:00
ingredientsg_rows , err := s . db . Query (
ctx ,
"select id, title from recipe_ingredients_group where recipe_id = $1" ,
r . ID ,
)
if err != nil {
return fmt . Errorf ( "%s: %w" , op , err )
}
defer ingredientsg_rows . Close ( )
for ingredientsg_rows . Next ( ) {
var ingredients_group_id uint
var recipe_ingredients models . RecipeIngredients
err = ingredientsg_rows . Scan (
& ingredients_group_id ,
& recipe_ingredients . Title ,
)
if err != nil {
return fmt . Errorf ( "%s: %w" , op , err )
}
ingredient_rows , err := s . db . Query (
ctx ,
"select ingredient from recipe_ingredients where recipe_ingredients_group_id = $1" ,
ingredients_group_id ,
)
if err != nil {
return fmt . Errorf ( "%s: %w" , op , err )
}
defer ingredient_rows . Close ( )
for ingredient_rows . Next ( ) {
var ingredient string
err = ingredient_rows . Scan (
& ingredient ,
)
if err != nil {
return fmt . Errorf ( "%s: %w" , op , err )
}
recipe_ingredients . Ingredients = append ( recipe_ingredients . Ingredients , ingredient )
}
r . Ingredients = append ( r . Ingredients , recipe_ingredients )
}
2024-01-19 21:00:03 +02:00
// select steps
2024-01-21 16:10:21 +02:00
step_rows , err := s . db . Query ( ctx , "select step_text from recipe_steps where recipe_id = $1 order by step_num" , r . ID )
if err != nil {
return fmt . Errorf ( "%s: %w" , op , err )
}
defer step_rows . Close ( )
for step_rows . Next ( ) {
var step string
err = step_rows . Scan (
& step ,
)
if err != nil {
return fmt . Errorf ( "%s: %w" , op , err )
}
r . Recipe_steps = append ( r . Recipe_steps , step )
}
2024-01-19 21:00:03 +02:00
// select advices
2024-01-21 16:10:21 +02:00
advice_rows , err := s . db . Query ( ctx , "select advice from recipe_advices where recipe_id = $1" , r . ID )
if err != nil {
return fmt . Errorf ( "%s: %w" , op , err )
}
defer advice_rows . Close ( )
for advice_rows . Next ( ) {
var advice string
err = advice_rows . Scan (
& advice ,
)
if err != nil {
return fmt . Errorf ( "%s: %w" , op , err )
}
r . Advices = append ( r . Advices , advice )
}
2024-01-19 21:00:03 +02:00
// select categories
2024-01-21 16:10:21 +02:00
category_rows , err := s . db . Query ( ctx , "select category from recipe_categories where recipe_id = $1" , r . ID )
if err != nil {
return fmt . Errorf ( "%s: %w" , op , err )
}
defer category_rows . Close ( )
for category_rows . Next ( ) {
var category string
err = category_rows . Scan (
& category ,
)
if err != nil {
return fmt . Errorf ( "%s: %w" , op , err )
}
r . Categories = append ( r . Categories , category )
}
2024-01-19 21:00:03 +02:00
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 {
2024-01-22 17:25:45 +02:00
if errors . Is ( err , pgx . ErrNoRows ) {
return nil , storage . ErrCategoryNotFound
}
2024-01-19 21:00:03 +02:00
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 )
}
2024-01-21 14:48:47 +02:00
err = s . AddRecipeInformation ( ctx , & r )
if err != nil {
return nil , fmt . Errorf ( "%s: %w" , op , err )
}
2024-01-19 21:00:03 +02:00
recipes = append ( recipes , r )
}
return recipes , nil
}