refactors data access layer to use pop ORM for eager loading, adds book titles rented to renters in response body
This commit is contained in:
@@ -5,6 +5,6 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNotFound = errors.New("no renter found")
|
||||
ErrNotFound = errors.New("empty result")
|
||||
ErrAlreadyExists = errors.New("the provided value already exists")
|
||||
)
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
// IsBookAvailable queries the database for available books.
|
||||
func (s *store) IsBookAvailable(title string) (available bool, err error) {
|
||||
q := `
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
rentals
|
||||
WHERE
|
||||
book_title = ?
|
||||
AND return_date IS NULL;
|
||||
`
|
||||
|
||||
var rr []struct {
|
||||
ID int `db:"id"`
|
||||
RenterID int `db:"renter_id"`
|
||||
RentalDate time.Time `db:"rental_date"`
|
||||
ReturnDate sql.NullTime `db:"return_date"`
|
||||
BookTitle string `db:"book_title"`
|
||||
}
|
||||
|
||||
err = s.Select(&rr, q, title)
|
||||
if err != nil {
|
||||
// If no records are returned then the book is available for rent.
|
||||
if errors.Is(sql.ErrNoRows, err) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, err
|
||||
}
|
||||
|
||||
// If 5 books are rented out this book is not available.
|
||||
if len(rr) > 4 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// RenterAlreadyCheckedOut queries the database for the given renterId and title. If a record is returned checkedOut will be true.
|
||||
func (s *store) RenterAlreadyCheckedOut(renterId int, title string) (checkedOut bool, err error) {
|
||||
q := `
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
rentals
|
||||
WHERE
|
||||
renter_id = ?
|
||||
AND book_title = ?
|
||||
AND return_date IS NULL;
|
||||
`
|
||||
|
||||
var rr []struct {
|
||||
ID int `db:"id"`
|
||||
RenterID int `db:"renter_id"`
|
||||
RentalDate time.Time `db:"rental_date"`
|
||||
ReturnDate sql.NullTime `db:"return_date"`
|
||||
BookTitle string `db:"book_title"`
|
||||
}
|
||||
|
||||
err = s.Select(&rr, q, renterId, title)
|
||||
if err != nil {
|
||||
if errors.Is(sql.ErrNoRows, err) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return false, err
|
||||
}
|
||||
|
||||
if len(rr) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// CheckoutBook inserts a record indicating the book is checked out to the renter.
|
||||
func (s *store) CheckoutBook(renterId int, title string) error {
|
||||
q := `
|
||||
INSERT INTO
|
||||
rentals (renter_id, rental_date, book_title)
|
||||
VALUES
|
||||
(?, ?, ?);
|
||||
`
|
||||
|
||||
_, err := s.Exec(q, renterId, time.Now(), title)
|
||||
return err
|
||||
}
|
||||
|
||||
// ReturnBook sets the return_date field to now indicating the book has been returned.
|
||||
func (s *store) ReturnBook(renterId int, title string) error {
|
||||
q := `
|
||||
UPDATE
|
||||
rentals
|
||||
SET
|
||||
return_date = ?
|
||||
WHERE
|
||||
renter_id = ?
|
||||
AND book_title = ?;
|
||||
`
|
||||
|
||||
_, err := s.Exec(q, time.Now(), renterId, title)
|
||||
return err
|
||||
}
|
||||
@@ -1,19 +1,19 @@
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/gobuffalo/pop"
|
||||
)
|
||||
|
||||
// store holds a connection to the application database.
|
||||
type store struct {
|
||||
*sqlx.DB
|
||||
*pop.Connection
|
||||
}
|
||||
|
||||
// NewStore creates a new connection to the database and returns a store containing the connection.
|
||||
func NewStore(dsn string) (s *store, err error) {
|
||||
db, err := sqlx.Open("mysql", dsn)
|
||||
db, err := pop.Connect("development")
|
||||
if err != nil {
|
||||
return s, err
|
||||
return &store{}, err
|
||||
}
|
||||
|
||||
return &store{db}, nil
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/davidlick/bookish/bookish-server/models"
|
||||
"github.com/gobuffalo/nulls"
|
||||
"github.com/gobuffalo/uuid"
|
||||
)
|
||||
|
||||
// IsBookAvailable queries the database for available books.
|
||||
func (s *store) IsBookAvailable(title string) (available bool, err error) {
|
||||
rr := []models.Rental{}
|
||||
err = s.Where(fmt.Sprintf("book_title = '%s'", title)).Where("return_date IS NULL").All(&rr)
|
||||
if err != nil {
|
||||
// If no records are returned then the book is available for rent.
|
||||
if errors.Is(sql.ErrNoRows, err) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, err
|
||||
}
|
||||
|
||||
// If 5 books are rented out this book is not available.
|
||||
if len(rr) > 4 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// RenterAlreadyCheckedOut queries the database for the given renterId and title. If a record is returned checkedOut will be true.
|
||||
func (s *store) RenterAlreadyCheckedOut(renterId uuid.UUID, title string) (checkedOut bool, err error) {
|
||||
rr := []models.Rental{}
|
||||
err = s.Where(fmt.Sprintf("renter_id = '%s'", renterId)).
|
||||
Where(fmt.Sprintf("book_title = '%s'", title)).
|
||||
Where("return_date IS NULL").
|
||||
All(&rr)
|
||||
if err != nil {
|
||||
// If no records are returned then the book is available for rent.
|
||||
if errors.Is(sql.ErrNoRows, err) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return false, err
|
||||
}
|
||||
|
||||
// If 5 books are rented out this book is not available.
|
||||
if len(rr) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// CheckoutBook inserts a record indicating the book is checked out to the renter.
|
||||
func (s *store) CheckoutBook(renterId uuid.UUID, title string) error {
|
||||
r := models.Rental{BookTitle: title, RenterID: renterId, RentalDate: time.Now()}
|
||||
err := s.Create(&r)
|
||||
return err
|
||||
}
|
||||
|
||||
// ReturnBook sets the return_date field to now indicating the book has been returned.
|
||||
func (s *store) ReturnBook(renterId uuid.UUID, title string) error {
|
||||
r := models.Rental{}
|
||||
err := s.Where(fmt.Sprintf("renter_id = '%s'", renterId)).
|
||||
Where(fmt.Sprintf("book_title = '%s'", title)).
|
||||
Where("return_date IS NULL").
|
||||
First(&r)
|
||||
if err != nil {
|
||||
return ErrNotFound
|
||||
}
|
||||
|
||||
r.ReturnDate = nulls.Time{Time: time.Now(), Valid: true}
|
||||
|
||||
err = s.Save(&r)
|
||||
return err
|
||||
}
|
||||
@@ -4,63 +4,43 @@ import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
|
||||
bookish "github.com/davidlick/bookish/bookish-server"
|
||||
"github.com/go-sql-driver/mysql"
|
||||
"github.com/davidlick/bookish/bookish-server/models"
|
||||
"github.com/gofrs/uuid"
|
||||
)
|
||||
|
||||
// ListAll returns all renter records.
|
||||
func (s *store) ListAll() (rr []bookish.Renter, err error) {
|
||||
q := `
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
renters
|
||||
`
|
||||
|
||||
err = s.Select(&rr, q)
|
||||
func (s *store) ListAll() (rr []models.Renter, err error) {
|
||||
err = s.Eager().All(&rr)
|
||||
if err != nil && errors.Is(sql.ErrNoRows, err) {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// FetchDetails queries the database for a specific renter.
|
||||
func (s *store) FetchDetails(id int) (r bookish.Renter, err error) {
|
||||
q := `
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
renters
|
||||
WHERE
|
||||
id = ?
|
||||
`
|
||||
|
||||
err = s.Get(&r, q, id)
|
||||
if err != nil && errors.Is(sql.ErrNoRows, err) {
|
||||
return bookish.Renter{}, ErrNotFound
|
||||
}
|
||||
func (s *store) FetchDetails(id string) (r models.Renter, err error) {
|
||||
err = s.Eager().Find(&r, id)
|
||||
return
|
||||
}
|
||||
|
||||
// New inserts a record for a new renter. If that renter already exists it returns an ErrAlreadyExists.
|
||||
func (s *store) New(name string, address string, email string, phoneNumber string) (id int, err error) {
|
||||
q := `
|
||||
INSERT INTO
|
||||
renters (name, address, email, phone_number)
|
||||
VALUES
|
||||
(?, ?, ?, ?)
|
||||
`
|
||||
|
||||
result, err := s.Exec(q, name, address, email, phoneNumber)
|
||||
if err != nil {
|
||||
if _, ok := err.(*mysql.MySQLError); ok {
|
||||
return 0, ErrAlreadyExists
|
||||
}
|
||||
func (s *store) New(name string, address string, email string, phoneNumber string) (id uuid.UUID, err error) {
|
||||
r := models.Renter{
|
||||
Name: name,
|
||||
Address: address,
|
||||
Email: email,
|
||||
PhoneNumber: phoneNumber,
|
||||
}
|
||||
|
||||
lastId, err := result.LastInsertId()
|
||||
// Create the new renter.
|
||||
err = s.Create(&r)
|
||||
if err != nil {
|
||||
return id, err
|
||||
return
|
||||
}
|
||||
return int(lastId), nil
|
||||
|
||||
// Get the last renter created.
|
||||
r = models.Renter{}
|
||||
err = s.Last(&r)
|
||||
return r.ID, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user