From cec183b416fdc2c1449a2dcec84287996f9740c1 Mon Sep 17 00:00:00 2001 From: vinchent Date: Wed, 31 Jul 2024 22:15:37 +0200 Subject: [PATCH] finish pgc impl --- internal/repository/db/query.sql.go | 104 +++++++ internal/repository/dbrepo/dbrepo.go | 13 + internal/repository/dbrepo/pgc.go | 414 +++++++++++++++++++++++++++ sql/query.sql | 33 +++ 4 files changed, 564 insertions(+) create mode 100644 internal/repository/dbrepo/pgc.go diff --git a/internal/repository/db/query.sql.go b/internal/repository/db/query.sql.go index da5a38d..7996268 100644 --- a/internal/repository/db/query.sql.go +++ b/internal/repository/db/query.sql.go @@ -194,6 +194,17 @@ func (q *Queries) AllRooms(ctx context.Context) ([]Room, error) { return items, nil } +const deleteBlockByID = `-- name: DeleteBlockByID :exec +DELETE FROM room_restrictions +WHERE + id = $1 +` + +func (q *Queries) DeleteBlockByID(ctx context.Context, id int32) error { + _, err := q.db.Exec(ctx, deleteBlockByID, id) + return err +} + const deleteReservation = `-- name: DeleteReservation :exec DELETE FROM reservations WHERE @@ -264,6 +275,64 @@ func (q *Queries) GetReservationByID(ctx context.Context, id int32) (GetReservat return i, err } +const getRestrictionsForRoomByDate = `-- name: GetRestrictionsForRoomByDate :many +SELECT + id, + coalesce(reservation_id, 0), + restriction_id, + room_id, + start_date, + end_date +FROM + room_restrictions +WHERE + $1 < end_date + AND $2 >= start_date + AND room_id = $3 +` + +type GetRestrictionsForRoomByDateParams struct { + EndDate pgtype.Date + StartDate pgtype.Date + RoomID int32 +} + +type GetRestrictionsForRoomByDateRow struct { + ID int32 + ReservationID int32 + RestrictionID int32 + RoomID int32 + StartDate pgtype.Date + EndDate pgtype.Date +} + +func (q *Queries) GetRestrictionsForRoomByDate(ctx context.Context, arg GetRestrictionsForRoomByDateParams) ([]GetRestrictionsForRoomByDateRow, error) { + rows, err := q.db.Query(ctx, getRestrictionsForRoomByDate, arg.EndDate, arg.StartDate, arg.RoomID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetRestrictionsForRoomByDateRow + for rows.Next() { + var i GetRestrictionsForRoomByDateRow + if err := rows.Scan( + &i.ID, + &i.ReservationID, + &i.RestrictionID, + &i.RoomID, + &i.StartDate, + &i.EndDate, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const getRoomById = `-- name: GetRoomById :one SELECT id, @@ -342,6 +411,41 @@ func (q *Queries) GetUserCred(ctx context.Context, email string) (GetUserCredRow return i, err } +const insertBlockForRoom = `-- name: InsertBlockForRoom :exec +INSERT INTO + room_restrictions ( + start_date, + end_date, + room_id, + restriction_id, + created_at, + updated_at + ) +VALUES + ($1, $2, $3, $4, $5, $6) +` + +type InsertBlockForRoomParams struct { + StartDate pgtype.Date + EndDate pgtype.Date + RoomID int32 + RestrictionID int32 + CreatedAt pgtype.Timestamp + UpdatedAt pgtype.Timestamp +} + +func (q *Queries) InsertBlockForRoom(ctx context.Context, arg InsertBlockForRoomParams) error { + _, err := q.db.Exec(ctx, insertBlockForRoom, + arg.StartDate, + arg.EndDate, + arg.RoomID, + arg.RestrictionID, + arg.CreatedAt, + arg.UpdatedAt, + ) + return err +} + const insertReservation = `-- name: InsertReservation :one INSERT INTO reservations ( diff --git a/internal/repository/dbrepo/dbrepo.go b/internal/repository/dbrepo/dbrepo.go index 10c9873..7510aa4 100644 --- a/internal/repository/dbrepo/dbrepo.go +++ b/internal/repository/dbrepo/dbrepo.go @@ -4,6 +4,7 @@ import ( "database/sql" "go-udemy-web-1/internal/config" "go-udemy-web-1/internal/repository" + "go-udemy-web-1/internal/repository/db" ) type postgresDBRepo struct { @@ -11,6 +12,11 @@ type postgresDBRepo struct { DB *sql.DB } +type pgcDBRepo struct { + App *config.AppConfig + Q *db.Queries +} + type testDBRepo struct { App *config.AppConfig DB *sql.DB @@ -28,3 +34,10 @@ func NewTestingRepo(a *config.AppConfig) repository.DatabaseRepo { App: a, } } + +func NewPgcRepo(q *db.Queries, a *config.AppConfig) repository.DatabaseRepo { + return &pgcDBRepo{ + App: a, + Q: q, + } +} diff --git a/internal/repository/dbrepo/pgc.go b/internal/repository/dbrepo/pgc.go new file mode 100644 index 0000000..0583a44 --- /dev/null +++ b/internal/repository/dbrepo/pgc.go @@ -0,0 +1,414 @@ +package dbrepo + +import ( + "context" + "errors" + "go-udemy-web-1/internal/models" + "go-udemy-web-1/internal/repository/db" + "time" + + "github.com/jackc/pgx/v5/pgtype" + "golang.org/x/crypto/bcrypt" +) + +func (m *pgcDBRepo) AllUsers() bool { + return true +} + +// InsertReservation inserts a reservation into the database +func (m *pgcDBRepo) InsertReservation(res models.Reservation) (int, error) { + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + + var newId int32 + newId, err := m.Q.InsertReservation(ctx, db.InsertReservationParams{ + FirstName: res.FirstName, + LastName: res.LastName, + Email: res.Email, + Phone: res.Phone, + StartDate: pgtype.Date{Time: res.StartDate, Valid: true}, + EndDate: pgtype.Date{Time: res.EndDate, Valid: true}, + RoomID: int32(res.Room.ID), + CreatedAt: pgtype.Timestamp{Time: res.CreatedAt, Valid: true}, + UpdatedAt: pgtype.Timestamp{Time: res.UpdatedAt, Valid: true}, + }) + if err != nil { + return 0, err + } + + return int(newId), nil +} + +// InsertRoomRestriction inserts a room restriction into the database +func (m *pgcDBRepo) InsertRoomRestriction(r models.RoomRestriction) error { + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + + err := m.Q.InsertRoomRestriction(ctx, db.InsertRoomRestrictionParams{ + StartDate: pgtype.Date{Time: r.StartDate, Valid: true}, + EndDate: pgtype.Date{Time: r.EndDate, Valid: true}, + RoomID: int32(r.Room.ID), + ReservationID: pgtype.Int4{Int32: int32(r.ID), Valid: true}, + RestrictionID: int32(r.RestrictionID), + CreatedAt: pgtype.Timestamp{Time: r.CreatedAt, Valid: true}, + UpdatedAt: pgtype.Timestamp{Time: r.UpdatedAt, Valid: true}, + }) + if err != nil { + return err + } + return nil +} + +// SearchAvailabilityByDatesByRoomID returns true if availability exists for roomID, and false if no availability +func (m *pgcDBRepo) SearchAvailabilityByDatesByRoomID(start, end time.Time, roomID int) (bool, error) { + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + + numRows, err := m.Q.SearchAvailabilityByDatesByRoomID(ctx, db.SearchAvailabilityByDatesByRoomIDParams{ + RoomID: int32(roomID), + EndDate: pgtype.Date{Time: start, Valid: true}, + StartDate: pgtype.Date{Time: end, Valid: false}, + }) + if err != nil { + return false, err + } + + if numRows == 0 { + return true, nil + } + + return false, nil +} + +// SearchAvailabilityForAllRooms returns a slice of rooms, if any, for given date range +func (m *pgcDBRepo) SearchAvailabilityForAllRooms(start, end time.Time) ([]models.Room, error) { + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + + var rooms []models.Room + + rows, err := m.Q.SearchAvailabilityForAllRooms(ctx, db.SearchAvailabilityForAllRoomsParams{ + EndDate: pgtype.Date{Time: start, Valid: true}, + StartDate: pgtype.Date{Time: start, Valid: true}, + }) + if err != nil { + return nil, err + } + for _, row := range rows { + room := models.Room{ + RoomName: row.RoomName, + ID: int(row.ID), + } + rooms = append(rooms, room) + } + + return rooms, nil +} + +// GetRoomById gets a room by id +func (m *pgcDBRepo) GetRoomById(id int) (models.Room, error) { + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + + var room models.Room + + row, err := m.Q.GetRoomById(ctx, int32(id)) + if err != nil { + return room, err + } + room = models.Room{ + ID: int(row.ID), + RoomName: row.RoomName, + } + + return room, nil +} + +// GetUserByID gets a user by id +func (m *pgcDBRepo) GetUserByID(id int) (models.User, error) { + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + + var u models.User + + row, err := m.Q.GetUserByID(ctx, int32(id)) + if err != nil { + return u, err + } + + u = models.User{ + ID: int(row.ID), + FirstName: row.FirstName, + LastName: row.LastName, + Email: row.Email, + Password: row.Password, + AccessLevel: int(row.AccessLevel), + } + + return u, nil +} + +// UpdateUser updates a user in the database +func (m *pgcDBRepo) UpdateUser(u models.User) error { + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + + err := m.Q.UpdateUser(ctx, db.UpdateUserParams{ + FirstName: u.FirstName, + LastName: u.LastName, + Email: u.Email, + AccessLevel: int32(u.AccessLevel), + UpdatedAt: pgtype.Timestamp{Time: u.UpdatedAt, Valid: true}, + }) + if err != nil { + return err + } + return nil +} + +// Authenticate authenticates a user +func (m *pgcDBRepo) Authenticate(email, testPassword string) (int, string, error) { + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + + var id int + var hashedPassword string + + row, err := m.Q.GetUserCred(ctx, email) + if err != nil { + return id, "", err + } + id = int(row.ID) + hashedPassword = row.Password + + err = bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(testPassword)) + if err == bcrypt.ErrMismatchedHashAndPassword { + return 0, "", errors.New("incorrect password") + } else if err != nil { + return 0, "", err + } + + return id, hashedPassword, nil +} + +// AllReservations returns a slice of all reservations +func (m *pgcDBRepo) AllReservations() ([]models.Reservation, error) { + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + + var reservations []models.Reservation + + rows, err := m.Q.AllReservations(ctx) + if err != nil { + return nil, err + } + + for _, row := range rows { + r := models.Reservation{ + StartDate: row.StartDate.Time, + EndDate: row.EndDate.Time, + FirstName: row.FirstName, + LastName: row.LastName, + Email: row.Email, + Phone: row.Phone, + Room: models.Room{ID: int(row.RoomID), RoomName: row.RoomName.String}, + ID: int(row.ID), + RoomID: int(row.RoomID), + Processed: int(row.Processed), + } + + reservations = append(reservations, r) + } + + return reservations, nil +} + +// AllNewReservations returns a slice of all new reservations +func (m *pgcDBRepo) AllNewReservations() ([]models.Reservation, error) { + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + + var reservations []models.Reservation + + rows, err := m.Q.AllNewReservations(ctx) + if err != nil { + return nil, err + } + + for _, row := range rows { + r := models.Reservation{ + StartDate: row.StartDate.Time, + EndDate: row.EndDate.Time, + FirstName: row.FirstName, + LastName: row.LastName, + Email: row.Email, + Phone: row.Phone, + Room: models.Room{ID: int(row.RoomID), RoomName: row.RoomName.String}, + ID: int(row.ID), + RoomID: int(row.RoomID), + Processed: int(row.Processed), + } + + reservations = append(reservations, r) + } + + return reservations, nil +} + +// GetReservationByID returns one reservation by ID +func (m *pgcDBRepo) GetReservationByID(id int) (models.Reservation, error) { + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + + var res models.Reservation + + row, err := m.Q.GetReservationByID(ctx, int32(id)) + if err != nil { + return res, err + } + + res = models.Reservation{ + StartDate: row.StartDate.Time, + EndDate: row.EndDate.Time, + FirstName: row.FirstName, + LastName: row.LastName, + Email: row.Email, + Phone: row.Phone, + Room: models.Room{ID: int(row.RoomID), RoomName: row.RoomName.String}, + ID: int(row.ID), + RoomID: int(row.RoomID), + Processed: int(row.Processed), + } + + return res, nil +} + +// UpdateReservation updates a user in the database +func (m *pgcDBRepo) UpdateReservation(r models.Reservation) error { + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + + err := m.Q.UpdateReservation(ctx, db.UpdateReservationParams{ + ID: int32(r.ID), + FirstName: r.FirstName, + LastName: r.LastName, + Email: r.Email, + Phone: r.Phone, + UpdatedAt: pgtype.Timestamp{Time: r.UpdatedAt, Valid: true}, + }) + if err != nil { + return err + } + + return nil +} + +func (m *pgcDBRepo) DeleteReservation(id int) error { + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + + err := m.Q.DeleteReservation(ctx, int32(id)) + if err != nil { + return err + } + + return nil +} + +// UpdateProcessedForReservation set processed for a reservation +func (m *pgcDBRepo) UpdateProcessedForReservation(id, processed int) error { + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + + err := m.Q.UpdateProcessedForReservation(ctx, db.UpdateProcessedForReservationParams{ + Processed: int32(processed), + ID: int32(id), + }) + if err != nil { + return err + } + + return nil +} + +func (m *pgcDBRepo) AllRooms() ([]models.Room, error) { + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + + var rooms []models.Room + + rows, err := m.Q.AllRooms(ctx) + if err != nil { + return nil, err + } + for _, row := range rows { + room := models.Room{ + RoomName: row.RoomName, + ID: int(row.ID), + } + rooms = append(rooms, room) + } + return rooms, nil +} + +// GetRestrictionsForRoomByDate returns restrictions for a room by date range +func (m *pgcDBRepo) GetRestrictionsForRoomByDate(roomId int, start, end time.Time) ([]models.RoomRestriction, error) { + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + + var restrictions []models.RoomRestriction + + rows, err := m.Q.GetRestrictionsForRoomByDate(ctx, db.GetRestrictionsForRoomByDateParams{ + EndDate: pgtype.Date{Time: end, Valid: true}, + StartDate: pgtype.Date{Time: start, Valid: true}, + RoomID: int32(roomId), + }) + if err != nil { + return restrictions, err + } + for _, row := range rows { + r := models.RoomRestriction{ + StartDate: row.StartDate.Time, + EndDate: row.EndDate.Time, + ID: int(row.ID), + RoomID: int(row.RoomID), + ReservationID: int(row.ReservationID), + RestrictionID: int(row.RestrictionID), + } + restrictions = append(restrictions, r) + } + + return restrictions, nil +} + +// InsertBlockForRoom inserts a room restriction +func (m *pgcDBRepo) InsertBlockForRoom(id int, startDate time.Time) error { + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + + err := m.Q.InsertBlockForRoom(ctx, db.InsertBlockForRoomParams{ + StartDate: pgtype.Date{Time: startDate, Valid: true}, + EndDate: pgtype.Date{Time: startDate.AddDate(0, 0, 1)}, + RoomID: int32(id), + RestrictionID: 2, + CreatedAt: pgtype.Timestamp{Time: time.Now(), Valid: true}, + UpdatedAt: pgtype.Timestamp{Time: time.Now(), Valid: true}, + }) + if err != nil { + return err + } + return nil +} + +// DeleteBlockByID deletes a block by ID +func (m *pgcDBRepo) DeleteBlockByID(id int) error { + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + + err := m.Q.DeleteBlockByID(ctx, int32(id)) + if err != nil { + return err + } + + return nil +} diff --git a/sql/query.sql b/sql/query.sql index e619cae..c824d23 100644 --- a/sql/query.sql +++ b/sql/query.sql @@ -199,3 +199,36 @@ FROM rooms ORDER BY room_name; + +-- name: GetRestrictionsForRoomByDate :many +SELECT + id, + coalesce(reservation_id, 0), + restriction_id, + room_id, + start_date, + end_date +FROM + room_restrictions +WHERE + $1 < end_date + AND $2 >= start_date + AND room_id = $3; + +-- name: InsertBlockForRoom :exec +INSERT INTO + room_restrictions ( + start_date, + end_date, + room_id, + restriction_id, + created_at, + updated_at + ) +VALUES + ($1, $2, $3, $4, $5, $6); + +-- name: DeleteBlockByID :exec +DELETE FROM room_restrictions +WHERE + id = $1;