diff --git a/db/migrations/01_up.sql b/db/migrations/01_up.sql index 297f6f1..c7195a1 100644 --- a/db/migrations/01_up.sql +++ b/db/migrations/01_up.sql @@ -1,81 +1,80 @@ -CREATE TABLE IF NOT EXISTS users +CREATE TABLE IF NOT EXISTS "user" ( - id SERIAL PRIMARY KEY, -- Уникальный идентификатор пользователя - email VARCHAR(255) NOT NULL UNIQUE, -- Email пользователя - login VARCHAR(255) NOT NULL UNIQUE, -- Логин пользователя - email TEXT NOT NULL, - password VARCHAR(255) NOT NULL, -- Хэш пароля + id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, -- Уникальный идентификатор пользователя + login TEXT UNIQUE NOT NULL, -- Логин пользователя + email TEXT NOT NULL, -- Email пользователя + password TEXT NOT NULL, -- Хэш пароля created_at TIMESTAMP NOT NULL DEFAULT NOW() -- Дата создания пользователя ); -CREATE TABLE IF NOT EXISTS cities +CREATE TABLE IF NOT EXISTS city ( - id SERIAL PRIMARY KEY, -- Уникальный идентификатор города - name VARCHAR(255) NOT NULL, -- Название города + id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, -- Уникальный идентификатор города + name TEXT NOT NULL, -- Название города created_at TIMESTAMP NOT NULL DEFAULT NOW() -- Дата создания города ); -CREATE TABLE IF NOT EXISTS places +CREATE TABLE IF NOT EXISTS place ( - id SERIAL PRIMARY KEY, - name VARCHAR(255) NOT NULL, -- название места - imagePath VARCHAR(255) NOT NULL, -- путь к картинке - description TEXT NOT NULL, -- описание места - rating INT NOT NULL, -- рейтинг места - numberOfReviews INT NOT NULL, -- количество отзывов - address VARCHAR(255) NOT NULL, -- адрес места + id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + name TEXT NOT NULL, -- название места + imagePath TEXT NOT NULL DEFAULT '', -- путь к картинке + description TEXT NOT NULL DEFAULT '', -- описание места + rating INT NOT NULL DEFAULT 0, -- рейтинг места + numberOfReviews INT NOT NULL DEFAULT 0, -- количество отзывов + address TEXT NOT NULL DEFAULT '', -- адрес места cityId INT NOT NULL, -- город, где находится место - phoneNumber VARCHAR(10), -- номер телефона - FOREIGN KEY (cityId) REFERENCES cities(id) ON DELETE CASCADE + phoneNumber TEXT DEFAULT '', -- номер телефона + FOREIGN KEY (cityId) REFERENCES city(id) ON DELETE CASCADE ); -CREATE TABLE IF NOT EXISTS reviews +CREATE TABLE IF NOT EXISTS review ( - id SERIAL PRIMARY KEY, -- Уникальный идентификатор отзыва - user_id INT REFERENCES users(id), -- Идентификатор пользователя, который оставил отзыв - place_id INT REFERENCES places(id), -- Идентификатор места, к которому относится отзыв - rating INT NOT NULL CHECK (rating BETWEEN 1 AND 5), -- Рейтинг места (от 1 до 5) - review_text TEXT, -- Текст отзыва + id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, -- Уникальный идентификатор отзыва + user_id INT REFERENCES "user"(id) NOT NULL, -- Идентификатор пользователя, который оставил отзыв + place_id INT REFERENCES place(id) NOT NULL, -- Идентификатор места, к которому относится отзыв + rating INT NOT NULL CHECK (rating BETWEEN 1 AND 5) DEFAULT 0, -- Рейтинг места (от 1 до 5) + review_text TEXT DEFAULT '', -- Текст отзыва created_at TIMESTAMP NOT NULL DEFAULT NOW(), -- Дата создания отзыва - CONSTRAINT fk_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, - CONSTRAINT fk_place FOREIGN KEY (place_id) REFERENCES places(id) ON DELETE CASCADE + CONSTRAINT fk_user FOREIGN KEY (user_id) REFERENCES "user"(id) ON DELETE CASCADE, + CONSTRAINT fk_place FOREIGN KEY (place_id) REFERENCES place(id) ON DELETE CASCADE ); -CREATE TABLE IF NOT EXISTS categories +CREATE TABLE IF NOT EXISTS category ( - id SERIAL PRIMARY KEY, - name VARCHAR(255) NOT NULL + id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + name TEXT NOT NULL DEFAULT '' ); -CREATE TABLE IF NOT EXISTS places_categories +CREATE TABLE IF NOT EXISTS place_category ( place_id INT NOT NULL, category_id INT NOT NULL, PRIMARY KEY(place_id, category_id), - FOREIGN KEY (place_id) REFERENCES places(id) ON DELETE CASCADE, - FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE CASCADE + FOREIGN KEY (place_id) REFERENCES place(id) ON DELETE CASCADE, + FOREIGN KEY (category_id) REFERENCES category(id) ON DELETE CASCADE ); -CREATE TABLE IF NOT EXISTS trips +CREATE TABLE IF NOT EXISTS trip ( - id SERIAL PRIMARY KEY, -- Уникальный идентификатор поездки - name VARCHAR(255) NOT NULL, -- Название поездки - description VARCHAR(1000), -- Описание поездки + id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, -- Уникальный идентификатор поездки + name TEXT NOT NULL, -- Название поездки + description TEXT DEFAULT '', -- Описание поездки city_id INTEGER NOT NULL, -- Направление поездки - start_date DATE, -- Дата начала поездки - end_date DATE, -- Дата окончания поездки - private BOOLEAN DEFAULT TRUE, -- Кому видна поездка (всем или выбранным пользователям) + start_date DATE NOT NULL, -- Дата начала поездки + end_date DATE NOT NULL, -- Дата окончания поездки + private BOOLEAN NOT NULL DEFAULT TRUE, -- Кому видна поездка (всем или выбранным пользователям) created_at TIMESTAMP NOT NULL DEFAULT NOW(), -- Дата создания поездки user_id INTEGER NOT NULL, -- Идентификатор пользователя-создателя поездки - CONSTRAINT fk_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, - CONSTRAINT fk_city FOREIGN KEY (city_id) REFERENCES cities(id) ON DELETE CASCADE + CONSTRAINT fk_user FOREIGN KEY (user_id) REFERENCES "user"(id) ON DELETE CASCADE, + CONSTRAINT fk_city FOREIGN KEY (city_id) REFERENCES city(id) ON DELETE CASCADE ); -CREATE TABLE IF NOT EXISTS trips_places ( --таблица для сопоставления поездки и достопримечательности, которая в нее входит - id SERIAL PRIMARY KEY, +CREATE TABLE IF NOT EXISTS trip_place ( --таблица для сопоставления поездки и достопримечательности, которая в нее входит + id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, trip_id INT NOT NULL, -- Идентификатор поездки place_id INT NOT NULL, -- Идентификатор города created_at TIMESTAMP NOT NULL DEFAULT NOW(), -- Дата создания записи - FOREIGN KEY (trip_id) REFERENCES trips(id) ON DELETE CASCADE, - FOREIGN KEY (place_id) REFERENCES places(id) ON DELETE CASCADE + FOREIGN KEY (trip_id) REFERENCES trip(id) ON DELETE CASCADE, + FOREIGN KEY (place_id) REFERENCES place(id) ON DELETE CASCADE ); diff --git a/go.mod b/go.mod index 88270eb..4cc49cc 100644 --- a/go.mod +++ b/go.mod @@ -18,12 +18,15 @@ require ( require ( github.com/KyleBanks/depth v1.2.1 // indirect + github.com/Masterminds/squirrel v1.5.4 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/spec v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect github.com/josharian/intern v1.0.0 // indirect + github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect + github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/swaggo/files v1.0.1 // indirect @@ -31,4 +34,5 @@ require ( golang.org/x/net v0.30.0 // indirect golang.org/x/tools v0.26.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + github.com/Masterminds/squirrel v1.5.4 ) diff --git a/go.sum b/go.sum index 9fec4c9..2a2dfc0 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7Oputl github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= +github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= @@ -25,6 +27,10 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= @@ -33,6 +39,7 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE= diff --git a/internal/pkg/auth/repo/auth_repository.go b/internal/pkg/auth/repo/auth_repository.go index cfc1915..ff9ec40 100644 --- a/internal/pkg/auth/repo/auth_repository.go +++ b/internal/pkg/auth/repo/auth_repository.go @@ -6,6 +6,8 @@ import ( "database/sql" "errors" "fmt" + + "github.com/Masterminds/squirrel" "github.com/lib/pq" _ "github.com/lib/pq" ) @@ -19,8 +21,18 @@ func NewAuthRepository(db *sql.DB) *RepositoryImpl { } func (r *RepositoryImpl) CreateUser(ctx context.Context, user models.User) error { - query := "INSERT INTO users (login, email, password, created_at) VALUES ($1, $2, $3, NOW())" - _, err := r.db.ExecContext(ctx, query, user.Login, user.Email, user.Password) + query, args, err := squirrel.Insert(`"user"`). + Columns("login", "email", "password", "created_at"). + Values(user.Login, user.Email, user.Password, squirrel.Expr("NOW()")). + PlaceholderFormat(squirrel.Dollar). + ToSql() + if err != nil { + return fmt.Errorf("couldn't build query: %w", err) + } + + _, err = r.db.ExecContext(ctx, query, args...) + // query := "INSERT INTO user (login, email, password, created_at) VALUES ($1, $2, $3, NOW())" + // _, err := r.db.ExecContext(ctx, query, user.Login, user.Email, user.Password) if err != nil { var pqErr *pq.Error @@ -34,9 +46,19 @@ func (r *RepositoryImpl) CreateUser(ctx context.Context, user models.User) error func (r *RepositoryImpl) GetUserByEmail(ctx context.Context, email string) (models.User, error) { var user models.User - query := "SELECT id, login, email, password, created_at FROM users WHERE email = $1" - row := r.db.QueryRowContext(ctx, query, email) - err := row.Scan(&user.ID, &user.Login, &user.Email, &user.Password, &user.CreatedAt) + query, args, err := squirrel.Select("id", "login", "email", "password", "created_at"). + From(`"user"`). + Where(squirrel.Eq{"email": email}). + PlaceholderFormat(squirrel.Dollar). + ToSql() + if err != nil { + return models.User{}, fmt.Errorf("couldn't build query: %w", err) + } + + row := r.db.QueryRowContext(ctx, query, args...) + // query := "SELECT id, login, email, password, created_at FROM user WHERE email = $1" + // row := r.db.QueryRowContext(ctx, query, email) + err = row.Scan(&user.ID, &user.Login, &user.Email, &user.Password, &user.CreatedAt) if err != nil { if err == sql.ErrNoRows { return models.User{}, fmt.Errorf("user not found with email: %s", email) @@ -47,20 +69,52 @@ func (r *RepositoryImpl) GetUserByEmail(ctx context.Context, email string) (mode } func (r *RepositoryImpl) UpdateUser(ctx context.Context, user models.User) error { - query := "UPDATE users SET login = $1, email=$2, password = $3 WHERE id = $4" - _, err := r.db.ExecContext(ctx, query, user.Login, user.Email, user.Password, user.ID) + query, args, err := squirrel.Update(`"user"`). + Set("login", user.Login). + Set("email", user.Email). + Set("password", user.Password). + Where("id = ?", user.ID). + PlaceholderFormat(squirrel.Dollar). + ToSql() + if err != nil { + return fmt.Errorf("couldn't build query: %w", err) + } + + _, err = r.db.ExecContext(ctx, query, args...) + // query := "UPDATE user SET login = $1, email=$2, password = $3 WHERE id = $4" + // _, err := r.db.ExecContext(ctx, query, user.Login, user.Email, user.Password, user.ID) return err } func (r *RepositoryImpl) DeleteUser(ctx context.Context, id string) error { - query := "DELETE FROM users WHERE id = $1" - _, err := r.db.ExecContext(ctx, query, id) + query, args, err := squirrel.Delete(`"user"`). + Where("id = ?", id). + PlaceholderFormat(squirrel.Dollar). + ToSql() + if err != nil { + return fmt.Errorf("couldn't build query: %w", err) + } + + _, err = r.db.ExecContext(ctx, query, args...) + // query := "DELETE FROM user WHERE id = $1" + // _, err := r.db.ExecContext(ctx, query, id) return err } func (r *RepositoryImpl) GetUsers(ctx context.Context, count, offset int64) ([]models.User, error) { - query := "SELECT id, login, email, created_at FROM users LIMIT $1 OFFSET $2" - rows, err := r.db.QueryContext(ctx, query, count, offset) + query, args, err := squirrel.Select("id", "login", "email", "created_at"). + From(`"user"`). + Limit(uint64(count)). + Offset(uint64(offset)). + PlaceholderFormat(squirrel.Dollar). + ToSql() + if err != nil { + return nil, fmt.Errorf("couldn't build query: %w", err) + } + + rows, err := r.db.QueryContext(ctx, query, args...) + // query := "SELECT id, login, email, created_at FROM user LIMIT $1 OFFSET $2" + // rows, err := r.db.QueryContext(ctx, query, count, offset) if err != nil { return nil, err } diff --git a/internal/pkg/places/repo/place_repository.go b/internal/pkg/places/repo/place_repository.go index 8872e3f..024feaf 100644 --- a/internal/pkg/places/repo/place_repository.go +++ b/internal/pkg/places/repo/place_repository.go @@ -7,6 +7,8 @@ import ( _ "embed" "errors" "fmt" + + "github.com/Masterminds/squirrel" "github.com/lib/pq" ) @@ -19,8 +21,24 @@ func NewPLaceRepository(db *sql.DB) *PlaceRepository { } func (r *PlaceRepository) GetPlaces(ctx context.Context, limit, offset int) ([]models.GetPlace, error) { - query := "SELECT p.id, p.name, p.imagePath, p.description, p.rating, p.numberOfReviews, p.address, p.phoneNumber, c.name AS city_name, ARRAY_AGG(ca.name) AS categories FROM places p JOIN cities c ON p.cityId = c.id JOIN places_categories pc ON p.id = pc.place_id JOIN categories ca ON pc.category_id = ca.id GROUP BY p.id, c.name ORDER BY p.id LIMIT $1 OFFSET $2" - rows, err := r.db.QueryContext(ctx, query, limit, offset) + query, args, err := squirrel.Select("p.id", "p.name", "p.imagePath", "p.description", "p.rating", "p.numberOfReviews", "p.address", "p.phoneNumber", "c.name AS city_name", "ARRAY_AGG(ca.name) AS categories"). + From("place p"). + Join("city c ON p.cityId = c.id"). + Join("place_category pc ON p.id = pc.place_id"). + Join("category ca ON pc.category_id = ca.id"). + GroupBy("p.id", "c.name"). + OrderBy("p.id"). + Limit(uint64(limit)). + Offset(uint64(offset)). + PlaceholderFormat(squirrel.Dollar). // Используем формат для PostgreSQL + ToSql() + if err != nil { + return nil, fmt.Errorf("couldn't build query: %w", err) + } + + rows, err := r.db.QueryContext(ctx, query, args...) + // query := "SELECT p.id, p.name, p.imagePath, p.description, p.rating, p.numberOfReviews, p.address, p.phoneNumber, c.name AS city_name, ARRAY_AGG(ca.name) AS categories FROM place p JOIN city c ON p.cityId = c.id JOIN place_category pc ON p.id = pc.place_id JOIN category ca ON pc.category_id = ca.id GROUP BY p.id, c.name ORDER BY p.id LIMIT $1 OFFSET $2" + // rows, err := r.db.QueryContext(ctx, query, limit, offset) if err != nil { return nil, fmt.Errorf("couldn't get places: %w", err) } @@ -38,14 +56,26 @@ func (r *PlaceRepository) GetPlaces(ctx context.Context, limit, offset int) ([]m } func (r *PlaceRepository) CreatePlace(ctx context.Context, place models.CreatePlace) error { - query := "INSERT INTO places (name, imagePath, description, rating, numberOfReviews, address, cityId, phoneNumber) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id" + query, args, err := squirrel.Insert("place"). + Columns("name", "imagePath", "description", "rating", "numberOfReviews", "address", "cityId", "phoneNumber"). + Values(place.Name, place.ImagePath, place.Description, place.Rating, place.NumberOfReviews, place.Address, place.CityId, place.PhoneNumber). + Suffix("RETURNING id"). + PlaceholderFormat(squirrel.Dollar). // Используем формат для PostgreSQL + ToSql() + if err != nil { + return fmt.Errorf("couldn't build insert query: %w", err) + } + var id int - err := r.db.QueryRowContext(ctx, query, place.Name, place.ImagePath, place.Description, place.Rating, place.NumberOfReviews, place.Address, place.CityId, place.PhoneNumber).Scan(&id) + err = r.db.QueryRowContext(ctx, query, args...).Scan(&id) + // query := "INSERT INTO place (name, imagePath, description, rating, numberOfReviews, address, cityId, phoneNumber) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id" + // var id int + // err := r.db.QueryRowContext(ctx, query, place.Name, place.ImagePath, place.Description, place.Rating, place.NumberOfReviews, place.Address, place.CityId, place.PhoneNumber).Scan(&id) if err != nil { - return fmt.Errorf("coldn't create place: %w", err) + return fmt.Errorf("couldn't create place: %w", err) } for _, categoryId := range place.CategoriesId { - query = "INSERT INTO places_categories (place_id, category_id) VALUES ($1, $2)" + query = "INSERT INTO place_category (place_id, category_id) VALUES ($1, $2)" result, err := r.db.ExecContext(ctx, query, id, categoryId) if err != nil { return fmt.Errorf("coldn't create place_categories: %w", err) @@ -59,9 +89,24 @@ func (r *PlaceRepository) CreatePlace(ctx context.Context, place models.CreatePl func (r *PlaceRepository) GetPlace(ctx context.Context, id uint) (models.GetPlace, error) { var place models.GetPlace - query := "SELECT p.id, p.name, p.imagePath, p.description, p.rating, p.numberOfReviews, p.address, p.phoneNumber, c.name AS city_name, ARRAY_AGG(ca.name) AS categories FROM places p JOIN cities c ON p.cityId = c.id JOIN places_categories pc ON p.id = pc.place_id JOIN categories ca ON pc.category_id = ca.id WHERE p.id = $1 GROUP BY p.id, c.name ORDER BY p.id" - row := r.db.QueryRowContext(ctx, query, id) - err := row.Scan(&place.ID, &place.Name, &place.ImagePath, &place.Description, &place.Rating, &place.NumberOfReviews, &place.Address, &place.PhoneNumber, &place.City, pq.Array(&place.Categories)) + query, args, err := squirrel.Select("p.id", "p.name", "p.imagePath", "p.description", "p.rating", "p.numberOfReviews", "p.address", "p.phoneNumber", "c.name AS city_name", "ARRAY_AGG(ca.name) AS categories"). + From("place p"). + Join("city c ON p.cityId = c.id"). + Join("place_category pc ON p.id = pc.place_id"). + Join("category ca ON pc.category_id = ca.id"). + Where("p.id = ?", id). + GroupBy("p.id", "c.name"). + OrderBy("p.id"). + PlaceholderFormat(squirrel.Dollar). // Используем формат для PostgreSQL + ToSql() + if err != nil { + return models.GetPlace{}, fmt.Errorf("couldn't build query: %w", err) + } + + row := r.db.QueryRowContext(ctx, query, args...) + // query := "SELECT p.id, p.name, p.imagePath, p.description, p.rating, p.numberOfReviews, p.address, p.phoneNumber, c.name AS city_name, ARRAY_AGG(ca.name) AS categories FROM place p JOIN city c ON p.cityId = c.id JOIN place_category pc ON p.id = pc.place_id JOIN category ca ON pc.category_id = ca.id WHERE p.id = $1 GROUP BY p.id, c.name ORDER BY p.id" + // row := r.db.QueryRowContext(ctx, query, id) + err = row.Scan(&place.ID, &place.Name, &place.ImagePath, &place.Description, &place.Rating, &place.NumberOfReviews, &place.Address, &place.PhoneNumber, &place.City, pq.Array(&place.Categories)) if err != nil { if errors.Is(err, sql.ErrNoRows) { return models.GetPlace{}, fmt.Errorf("place not found") @@ -72,8 +117,24 @@ func (r *PlaceRepository) GetPlace(ctx context.Context, id uint) (models.GetPlac } func (r *PlaceRepository) UpdatePlace(ctx context.Context, place models.UpdatePlace) error { - query := "UPDATE places SET name = $1, imagePath = $2, description = $3, rating = $4, numberOfReviews = $5, address = $6, phoneNumber = $7 WHERE id=$8" - result, err := r.db.ExecContext(ctx, query, place.Name, place.ImagePath, place.Description, place.Rating, place.NumberOfReviews, place.Address, place.PhoneNumber, place.ID) + query, args, err := squirrel.Update("place"). + Set("name", place.Name). + Set("imagePath", place.ImagePath). + Set("description", place.Description). + Set("rating", place.Rating). + Set("numberOfReviews", place.NumberOfReviews). + Set("address", place.Address). + Set("phoneNumber", place.PhoneNumber). + Where(squirrel.Eq{"id": place.ID}). + PlaceholderFormat(squirrel.Dollar). // Используем формат для PostgreSQL + ToSql() + if err != nil { + return fmt.Errorf("couldn't build update query: %w", err) + } + + result, err := r.db.ExecContext(ctx, query, args...) + // query := "UPDATE place SET name = $1, imagePath = $2, description = $3, rating = $4, numberOfReviews = $5, address = $6, phoneNumber = $7 WHERE id=$8" + // result, err := r.db.ExecContext(ctx, query, place.Name, place.ImagePath, place.Description, place.Rating, place.NumberOfReviews, place.Address, place.PhoneNumber, place.ID) if err != nil { return fmt.Errorf("couldn't update place: %w", err) } @@ -85,7 +146,7 @@ func (r *PlaceRepository) UpdatePlace(ctx context.Context, place models.UpdatePl return fmt.Errorf("no rows were updated") } - query = "DELETE FROM places_categories WHERE place_id=$1" + query = "DELETE FROM place_category WHERE place_id=$1" result, err = r.db.ExecContext(ctx, query, place.ID) if err != nil { return fmt.Errorf("couldn't delete places_categories: %w", err) @@ -99,7 +160,7 @@ func (r *PlaceRepository) UpdatePlace(ctx context.Context, place models.UpdatePl } for _, categoryId := range place.CategoriesId { - query = "INSERT INTO places_categories (place_id, category_id) VALUES ($1, $2)" + query = "INSERT INTO place_category (place_id, category_id) VALUES ($1, $2)" result, err := r.db.ExecContext(ctx, query, place.ID, categoryId) if err != nil { return fmt.Errorf("coldn't create place_categories: %w", err) @@ -112,8 +173,17 @@ func (r *PlaceRepository) UpdatePlace(ctx context.Context, place models.UpdatePl } func (r *PlaceRepository) DeletePlace(ctx context.Context, id uint) error { - query := "DELETE FROM places WHERE id=$1" - result, err := r.db.ExecContext(ctx, query, id) + query, args, err := squirrel.Delete("place"). + Where("id = ?", id). + PlaceholderFormat(squirrel.Dollar). // Используем формат для PostgreSQL + ToSql() + if err != nil { + return fmt.Errorf("couldn't build delete query: %w", err) + } + + result, err := r.db.ExecContext(ctx, query, args...) + // query := "DELETE FROM place WHERE id=$1" + // result, err := r.db.ExecContext(ctx, query, id) if err != nil { return fmt.Errorf("couldn't delete place: %w", err) } @@ -129,18 +199,26 @@ func (r *PlaceRepository) DeletePlace(ctx context.Context, id uint) error { func (r *PlaceRepository) SearchPlaces(ctx context.Context, name string, limit, offset int) ([]models.GetPlace, error) { var places []models.GetPlace - query := "SELECT p.id, p.name, p.imagePath, p.description, p.rating, p.numberOfReviews, p.address, p.phoneNumber, c.name AS city_name, ARRAY_AGG(ca.name) AS categories FROM places p JOIN cities c ON p.cityId = c.id JOIN places_categories pc ON p.id = pc.place_id JOIN categories ca ON pc.category_id = ca.id WHERE p.name LIKE '%' || $1 || '%' GROUP BY p.id, c.name ORDER BY p.id LIMIT $2 OFFSET $3" - stmt, err := r.db.PrepareContext(ctx, query) + query, args, err := squirrel.Select("p.id", "p.name", "p.imagePath", "p.description", "p.rating", "p.numberOfReviews", "p.address", "p.phoneNumber", "c.name AS city_name", "ARRAY_AGG(ca.name) AS categories"). + From("place p"). + Join("city c ON p.cityId = c.id"). + Join("place_category pc ON p.id = pc.place_id"). + Join("category ca ON pc.category_id = ca.id"). + Where("p.name LIKE '%' || ? || '%'", name). + GroupBy("p.id", "c.name"). + OrderBy("p.id"). + Limit(uint64(limit)). + Offset(uint64(offset)). + PlaceholderFormat(squirrel.Dollar). // Используем формат для PostgreSQL + ToSql() if err != nil { - return nil, fmt.Errorf("couldn't prepare query: %w", err) + return nil, fmt.Errorf("couldn't build query: %w", err) } - defer stmt.Close() - rows, err := stmt.QueryContext(ctx, name, limit, offset) + rows, err := r.db.QueryContext(ctx, query, args...) if err != nil { return nil, fmt.Errorf("couldn't get places: %w", err) } defer rows.Close() - for rows.Next() { var place models.GetPlace err := rows.Scan(&place.ID, &place.Name, &place.ImagePath, &place.Description, &place.Rating, &place.NumberOfReviews, &place.Address, &place.PhoneNumber, &place.City, pq.Array(&place.Categories)) @@ -149,5 +227,28 @@ func (r *PlaceRepository) SearchPlaces(ctx context.Context, name string, limit, } places = append(places, place) } + return places, nil + + //query := "SELECT p.id, p.name, p.imagePath, p.description, p.rating, p.numberOfReviews, p.address, p.phoneNumber, c.name AS city_name, ARRAY_AGG(ca.name) AS categories FROM place p JOIN city c ON p.cityId = c.id JOIN place_category pc ON p.id = pc.place_id JOIN category ca ON pc.category_id = ca.id WHERE p.name LIKE '%' || $1 || '%' GROUP BY p.id, c.name ORDER BY p.id LIMIT $2 OFFSET $3" + // stmt, err := r.db.PrepareContext(ctx, query) + // if err != nil { + // return nil, fmt.Errorf("couldn't prepare query: %w", err) + // } + // defer stmt.Close() + // rows, err := stmt.QueryContext(ctx, name, limit, offset) + // if err != nil { + // return nil, fmt.Errorf("couldn't get places: %w", err) + // } + // defer rows.Close() + + // for rows.Next() { + // var place models.GetPlace + // err := rows.Scan(&place.ID, &place.Name, &place.ImagePath, &place.Description, &place.Rating, &place.NumberOfReviews, &place.Address, &place.PhoneNumber, &place.City, pq.Array(&place.Categories)) + // if err != nil { + // return nil, fmt.Errorf("couldn't unmarshal list of places: %w", err) + // } + // places = append(places, place) + // } + // return places, nil } diff --git a/internal/pkg/reviews/repo/reviews_repository.go b/internal/pkg/reviews/repo/reviews_repository.go index 37a2fa5..64dc301 100644 --- a/internal/pkg/reviews/repo/reviews_repository.go +++ b/internal/pkg/reviews/repo/reviews_repository.go @@ -6,6 +6,7 @@ import ( "database/sql" "fmt" + "github.com/Masterminds/squirrel" _ "github.com/lib/pq" ) @@ -18,10 +19,20 @@ func NewReviewRepository(db *sql.DB) *ReviewRepository { } func (r *ReviewRepository) CreateReview(ctx context.Context, review models.Review) error { - query := `INSERT INTO reviews (user_id, place_id, rating, review_text, created_at) - VALUES ($1, $2, $3, $4, NOW())` + query, args, err := squirrel.Insert("review"). + Columns("user_id", "place_id", "rating", "review_text", "created_at"). + Values(review.UserID, review.PlaceID, review.Rating, review.ReviewText, squirrel.Expr("NOW()")). + PlaceholderFormat(squirrel.Dollar). + ToSql() + if err != nil { + return fmt.Errorf("failed to build insert query: %w", models.ErrInternal) + } - result, err := r.db.ExecContext(ctx, query, review.UserID, review.PlaceID, review.Rating, review.ReviewText) + result, err := r.db.ExecContext(ctx, query, args...) + // query := `INSERT INTO review (user_id, place_id, rating, review_text, created_at) + // VALUES ($1, $2, $3, $4, NOW())` + + // result, err := r.db.ExecContext(ctx, query, review.UserID, review.PlaceID, review.Rating, review.ReviewText) if err != nil { return fmt.Errorf("failed to create review: %w", models.ErrInternal) } @@ -38,11 +49,22 @@ func (r *ReviewRepository) CreateReview(ctx context.Context, review models.Revie } func (r *ReviewRepository) UpdateReview(ctx context.Context, review models.Review) error { - query := `UPDATE reviews - SET rating = $1, review_text = $2 - WHERE id = $3` + query, args, err := squirrel.Update("review"). + Set("rating", review.Rating). + Set("review_text", review.ReviewText). + Where("id = ?", review.ID). + PlaceholderFormat(squirrel.Dollar). + ToSql() + if err != nil { + return fmt.Errorf("failed to build update query: %w", models.ErrInternal) + } + + result, err := r.db.ExecContext(ctx, query, args...) + // query := `UPDATE review + // SET rating = $1, review_text = $2 + // WHERE id = $3` - result, err := r.db.ExecContext(ctx, query, review.Rating, review.ReviewText, review.ID) + // result, err := r.db.ExecContext(ctx, query, review.Rating, review.ReviewText, review.ID) if err != nil { return fmt.Errorf("failed to update review: %w", models.ErrInternal) } @@ -60,9 +82,18 @@ func (r *ReviewRepository) UpdateReview(ctx context.Context, review models.Revie } func (r *ReviewRepository) DeleteReview(ctx context.Context, reviewID uint) error { - query := `DELETE FROM reviews WHERE id = $1` + query, args, err := squirrel.Delete("review"). + Where("id = ?", reviewID). + PlaceholderFormat(squirrel.Dollar). + ToSql() + if err != nil { + return fmt.Errorf("failed to build delete query: %w", models.ErrInternal) + } - result, err := r.db.ExecContext(ctx, query, reviewID) + result, err := r.db.ExecContext(ctx, query, args...) + // query := `DELETE FROM review WHERE id = $1` + + // result, err := r.db.ExecContext(ctx, query, reviewID) if err != nil { return fmt.Errorf("failed to delete review: %w", models.ErrInternal) } @@ -80,13 +111,26 @@ func (r *ReviewRepository) DeleteReview(ctx context.Context, reviewID uint) erro } func (r *ReviewRepository) GetReviewsByPlaceID(ctx context.Context, placeID uint, limit, offset int) ([]models.Review, error) { - query := `SELECT id, user_id, place_id, rating, review_text, created_at - FROM reviews - WHERE place_id = $1 - ORDER BY created_at DESC - LIMIT $2 OFFSET $3` + query, args, err := squirrel.Select("id", "user_id", "place_id", "rating", "review_text", "created_at"). + From("review"). + Where("place_id = ?", placeID). + OrderBy("created_at DESC"). + Limit(uint64(limit)). + Offset(uint64(offset)). + PlaceholderFormat(squirrel.Dollar). + ToSql() + if err != nil { + return nil, fmt.Errorf("failed to build select query: %w", models.ErrInternal) + } + + rows, err := r.db.QueryContext(ctx, query, args...) + // query := `SELECT id, user_id, place_id, rating, review_text, created_at + // FROM review + // WHERE place_id = $1 + // ORDER BY created_at DESC + // LIMIT $2 OFFSET $3` - rows, err := r.db.QueryContext(ctx, query, placeID, limit, offset) + // rows, err := r.db.QueryContext(ctx, query, placeID, limit, offset) if err != nil { return nil, fmt.Errorf("failed to retrieve reviews: %w", models.ErrInternal) } @@ -109,14 +153,24 @@ func (r *ReviewRepository) GetReviewsByPlaceID(ctx context.Context, placeID uint } func (r *ReviewRepository) GetReview(ctx context.Context, reviewID uint) (models.Review, error) { - query := `SELECT id, user_id, place_id, rating, review_text, created_at - FROM reviews - WHERE id = $1` + query, args, err := squirrel.Select("id", "user_id", "place_id", "rating", "review_text", "created_at"). + From("review"). + Where("id = ?", reviewID). + PlaceholderFormat(squirrel.Dollar). + ToSql() + if err != nil { + return models.Review{}, fmt.Errorf("failed to build select query: %w", models.ErrInternal) + } + + row := r.db.QueryRowContext(ctx, query, args...) + // query := `SELECT id, user_id, place_id, rating, review_text, created_at + // FROM review + // WHERE id = $1` - row := r.db.QueryRowContext(ctx, query, reviewID) + // row := r.db.QueryRowContext(ctx, query, reviewID) var review models.Review - err := row.Scan(&review.ID, &review.UserID, &review.PlaceID, &review.Rating, &review.ReviewText, &review.CreatedAt) + err = row.Scan(&review.ID, &review.UserID, &review.PlaceID, &review.Rating, &review.ReviewText, &review.CreatedAt) if err != nil { if err == sql.ErrNoRows { return models.Review{}, fmt.Errorf("review with ID %d did not found: %w", reviewID, models.ErrNotFound) diff --git a/internal/pkg/trips/repo/trips_repository.go b/internal/pkg/trips/repo/trips_repository.go index f035bd9..f7e437b 100644 --- a/internal/pkg/trips/repo/trips_repository.go +++ b/internal/pkg/trips/repo/trips_repository.go @@ -7,6 +7,8 @@ import ( "database/sql" "errors" "fmt" + + "github.com/Masterminds/squirrel" ) type TripRepository struct { @@ -18,10 +20,19 @@ func NewTripRepository(db *sql.DB) *TripRepository { } func (r *TripRepository) CreateTrip(ctx context.Context, trip models.Trip) error { - query := `INSERT INTO trips (user_id, name, description, city_id, start_date, end_date, private, created_at) - VALUES ($1, $2, $3, $4, $5, $6, $7, NOW())` + queryBuilder := squirrel.Insert("trip"). + Columns("user_id", "name", "description", "city_id", "start_date", "end_date", "private", "created_at"). + Values(trip.UserID, trip.Name, trip.Description, trip.CityID, trip.StartDate, trip.EndDate, trip.Private, squirrel.Expr("NOW()")). + PlaceholderFormat(squirrel.Dollar) - result, err := r.db.ExecContext(ctx, query, trip.UserID, trip.Name, trip.Description, trip.CityID, trip.StartDate, trip.EndDate, trip.Private) + query, args, err := queryBuilder.ToSql() + if err != nil { + return fmt.Errorf("failed to build query: %w", models.ErrInternal) + } + //query := `INSERT INTO trip (user_id, name, description, city_id, start_date, end_date, private, created_at) + // VALUES ($1, $2, $3, $4, $5, $6, $7, NOW())` + result, err := r.db.ExecContext(ctx, query, args...) + //result, err := r.db.ExecContext(ctx, query, trip.UserID, trip.Name, trip.Description, trip.CityID, trip.StartDate, trip.EndDate, trip.Private) if err != nil { return fmt.Errorf("failed to create a trip: %w", models.ErrInternal) } @@ -38,11 +49,27 @@ func (r *TripRepository) CreateTrip(ctx context.Context, trip models.Trip) error } func (r *TripRepository) UpdateTrip(ctx context.Context, trip models.Trip) error { - query := `UPDATE trips - SET name = $1, description = $2, city_id = $3, start_date = $4, end_date = $5, private = $6 - WHERE id = $7` + queryBuilder := squirrel.Update("trip"). + Set("name", trip.Name). + Set("description", trip.Description). + Set("city_id", trip.CityID). + Set("start_date", trip.StartDate). + Set("end_date", trip.EndDate). + Set("private", trip.Private). + Where(squirrel.Eq{"id": trip.ID}). + PlaceholderFormat(squirrel.Dollar) + + query, args, err := queryBuilder.ToSql() + if err != nil { + return fmt.Errorf("failed to build update query: %w", models.ErrInternal) + } - result, err := r.db.ExecContext(ctx, query, trip.Name, trip.Description, trip.CityID, trip.StartDate, trip.EndDate, trip.Private, trip.ID) + result, err := r.db.ExecContext(ctx, query, args...) + // query := `UPDATE trip + // SET name = $1, description = $2, city_id = $3, start_date = $4, end_date = $5, private = $6 + // WHERE id = $7` + + // result, err := r.db.ExecContext(ctx, query, trip.Name, trip.Description, trip.CityID, trip.StartDate, trip.EndDate, trip.Private, trip.ID) if err != nil { return fmt.Errorf("failed to execute update query: %w", models.ErrInternal) } @@ -58,8 +85,18 @@ func (r *TripRepository) UpdateTrip(ctx context.Context, trip models.Trip) error } func (r *TripRepository) DeleteTrip(ctx context.Context, id uint) error { - query := `DELETE FROM trips WHERE id = $1` - result, err := r.db.ExecContext(ctx, query, id) + queryBuilder := squirrel.Delete("trip"). + Where(squirrel.Eq{"id": id}). + PlaceholderFormat(squirrel.Dollar) + + query, args, err := queryBuilder.ToSql() + if err != nil { + return fmt.Errorf("failed to build delete query: %w", models.ErrInternal) + } + + result, err := r.db.ExecContext(ctx, query, args...) + // query := `DELETE FROM trip WHERE id = $1` + // result, err := r.db.ExecContext(ctx, query, id) if err != nil { return fmt.Errorf("failed to delete trip: %w", models.ErrInternal) } @@ -75,13 +112,27 @@ func (r *TripRepository) DeleteTrip(ctx context.Context, id uint) error { } func (r *TripRepository) GetTripsByUserID(ctx context.Context, userID uint, limit, offset int) ([]models.Trip, error) { - query := `SELECT id, user_id, name, description, city_id, start_date, end_date, private, created_at - FROM trips - WHERE user_id = $1 - ORDER BY created_at DESC - LIMIT $2 OFFSET $3` + queryBuilder := squirrel.Select("id", "user_id", "name", "description", "city_id", "start_date", "end_date", "private", "created_at"). + From("trip"). + Where(squirrel.Eq{"user_id": userID}). + OrderBy("created_at DESC"). + Limit(uint64(limit)). + Offset(uint64(offset)). + PlaceholderFormat(squirrel.Dollar) + + query, args, err := queryBuilder.ToSql() + if err != nil { + return nil, fmt.Errorf("failed to build query: %w", models.ErrInternal) + } + + rows, err := r.db.QueryContext(ctx, query, args...) + // query := `SELECT id, user_id, name, description, city_id, start_date, end_date, private, created_at + // FROM trip + // WHERE user_id = $1 + // ORDER BY created_at DESC + // LIMIT $2 OFFSET $3` - rows, err := r.db.QueryContext(ctx, query, userID, limit, offset) + // rows, err := r.db.QueryContext(ctx, query, userID, limit, offset) if err != nil { return nil, fmt.Errorf("failed to retrieve trips: %w", models.ErrInternal) @@ -105,14 +156,25 @@ func (r *TripRepository) GetTripsByUserID(ctx context.Context, userID uint, limi } func (r *TripRepository) GetTrip(ctx context.Context, tripID uint) (models.Trip, error) { - query := `SELECT id, user_id, name, description, city_id, start_date, end_date, private, created_at - FROM trips - WHERE id = $1` + queryBuilder := squirrel.Select("id", "user_id", "name", "description", "city_id", "start_date", "end_date", "private", "created_at"). + From("trip"). + Where(squirrel.Eq{"id": tripID}). + PlaceholderFormat(squirrel.Dollar) + + query, args, err := queryBuilder.ToSql() + if err != nil { + return models.Trip{}, fmt.Errorf("failed to build query: %w", models.ErrInternal) + } + + row := r.db.QueryRowContext(ctx, query, args...) + // query := `SELECT id, user_id, name, description, city_id, start_date, end_date, private, created_at + // FROM trip + // WHERE id = $1` - row := r.db.QueryRowContext(ctx, query, tripID) + // row := r.db.QueryRowContext(ctx, query, tripID) var trip models.Trip - err := row.Scan(&trip.ID, &trip.UserID, &trip.Name, &trip.Description, &trip.CityID, &trip.StartDate, &trip.EndDate, &trip.Private, &trip.CreatedAt) + err = row.Scan(&trip.ID, &trip.UserID, &trip.Name, &trip.Description, &trip.CityID, &trip.StartDate, &trip.EndDate, &trip.Private, &trip.CreatedAt) if err != nil { if errors.Is(err, sql.ErrNoRows) { return models.Trip{}, fmt.Errorf("trip not found: %w", models.ErrNotFound)