diff --git a/internal/domain/models/recipe.go b/internal/domain/models/recipe.go new file mode 100644 index 0000000..e0072d7 --- /dev/null +++ b/internal/domain/models/recipe.go @@ -0,0 +1,21 @@ +package models + +type Recipe struct { + ID uint `json:"id,omitempty"` // id + Title string `json:"title,omitempty"` // Заголовок + Description string `json:"desc,omitempty"` // Описание + Image string `json:"img,omitempty"` // Картинка + CookingTime string `json:"ctime,omitempty"` // Время приготовления + Link string `json:"link,omitempty"` // Ссылка + ServingsNum uint `json:"snum,omitempty"` // Кол-во порций + Calories string `json:"cal,omitempty"` // Колории + Ingredients []RecipeIngredients `json:"ingredients,omitempty"` // Ингридиенты и посуда (списки) + Recipe_steps []string `json:"recipe_steps,omitempty"` // Шаги рецепта + Advices []string `json:"advices,omitempty"` // Рекомендации + Categories []string `json:"categories,omitempty"` // Категории +} + +type RecipeIngredients struct { + Title string `json:"title,omitempty"` + Ingredients []string `json:"ingredients,omitempty"` +} diff --git a/internal/storage/postgresql/postgresql.go b/internal/storage/postgresql/postgresql.go index d278fd1..6a21ccc 100644 --- a/internal/storage/postgresql/postgresql.go +++ b/internal/storage/postgresql/postgresql.go @@ -3,15 +3,16 @@ package postgresql import ( "context" "fmt" + "recipes/internal/domain/models" "github.com/jackc/pgx/v5/pgxpool" ) -type Database struct { +type Storage struct { db *pgxpool.Pool } -func New(user, password, addr, dbname string) (*Database, error) { +func New(user, password, addr, dbname string) (*Storage, error) { const op = "storage.postgresql.New" pool, err := pgxpool.New(context.Background(), fmt.Sprintf("postgres://%s:%s@%s/%s?sslmode=disable", user, password, addr, dbname)) @@ -19,5 +20,149 @@ func New(user, password, addr, dbname string) (*Database, error) { return nil, fmt.Errorf("%s: %w", op, err) } - return &Database{db: pool}, nil + 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 +} + +// func (db *PostgresqlDatabase) GetRecipes(offset, limit int) ([]structs.Recipe, error) +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 +} + +// func (db *PostgresqlDatabase) GetRecipe(r_id uint) (structs.Recipe, error) + +// add to recipe info about ingredients, steps, advices, categories +// func (db *PostgresqlDatabase) AddRecipeInformation(r *structs.Recipe) + +// get recipes by category +// func (db *PostgresqlDatabase) GetRecipesByCategory(offset, limit int, category string) ([]structs.Recipe, error) diff --git a/migrations/000001_init.down.sql b/migrations/000001_init.down.sql index ccde055..146ce14 100644 --- a/migrations/000001_init.down.sql +++ b/migrations/000001_init.down.sql @@ -1,6 +1,6 @@ -drop table if exists recipe; -drop table if exists recipe_ingredients_group; -drop table if exists recipe_ingredients; -drop table if exists recipe_steps; -drop table if exists recipe_advices; -drop table if exists recipe_categories; \ No newline at end of file +drop table if exists recipe_ingredients_group cascade; +drop table if exists recipe_ingredients cascade; +drop table if exists recipe_steps cascade; +drop table if exists recipe_advices cascade; +drop table if exists recipe_categories cascade; +drop table if exists recipe cascade; \ No newline at end of file diff --git a/migrations/000001_init.up.sql b/migrations/000001_init.up.sql index 96bf75e..72e1d29 100644 --- a/migrations/000001_init.up.sql +++ b/migrations/000001_init.up.sql @@ -1,7 +1,7 @@ -- recipe create table if not exists recipe ( id serial primary key, - title varchar(60) not null UNIQUE, + title text not null UNIQUE, description text not null default '', image varchar(42) not null, cooking_time varchar(16) not null, @@ -12,7 +12,7 @@ create table if not exists recipe ( create table if not exists recipe_ingredients_group ( id serial primary key, recipe_id integer references recipe(id) on delete cascade, - title varchar(26) default '' + title text default '' ); -- recipe ingredients create table if not exists recipe_ingredients (