2024-01-16 21:10:29 +02:00
package postgresql
import (
2024-01-18 18:48:27 +02:00
"context"
"fmt"
2024-01-19 14:49:26 +02:00
"recipes/internal/domain/models"
2024-01-18 18:48:27 +02:00
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 {
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 )
}
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
}