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:
David Lick
2020-05-31 00:40:32 -04:00
parent a06b4d70ab
commit 28e52a4e08
29 changed files with 732 additions and 287 deletions
Vendored
BIN
View File
Binary file not shown.
+1
View File
@@ -1 +1,2 @@
.env
.DS_Store
+6 -5
View File
@@ -10,9 +10,10 @@ import (
"time"
"github.com/davidlick/bookish/bookish-server/cmd/http"
"github.com/davidlick/bookish/bookish-server/inventory"
"github.com/davidlick/bookish/bookish-server/mysql"
"github.com/davidlick/bookish/bookish-server/rental"
"github.com/davidlick/bookish/bookish-server/renter"
_ "github.com/go-sql-driver/mysql"
"github.com/sirupsen/logrus"
)
@@ -43,7 +44,7 @@ func init() {
func main() {
// Connect to application database and create store.
appDSN := fmt.Sprintf("%s:%s@tcp(%s:%s)/bookish?parseTime=True&loc=Local",
appDSN := fmt.Sprintf("mysql://%s:%s@tcp(%s:%s)/bookish?parseTime=true&loc=local&multiStatements=true",
cfg.DBUser,
cfg.DBPass,
cfg.DBHost,
@@ -56,15 +57,15 @@ func main() {
// Build domain services.
renterService := renter.NewService(appDB)
inventoryService := inventory.NewService(appDB)
rentalService := rental.NewService(appDB)
// Initialize server.
server := http.Server{
Port: cfg.APIPort,
BooksHost: cfg.BooksHost,
Logger: logger,
Renter: renterService,
Inventory: inventoryService,
Renters: renterService,
Rentals: rentalService,
}
// Create channels to listen for OS signals.
+3 -3
View File
@@ -9,7 +9,7 @@ import (
"time"
bookish "github.com/davidlick/bookish/bookish-server"
"github.com/davidlick/bookish/bookish-server/inventory"
"github.com/davidlick/bookish/bookish-server/rental"
"github.com/davidlick/bookish/bookish-server/renter"
"github.com/go-chi/chi"
"github.com/go-chi/chi/middleware"
@@ -21,8 +21,8 @@ type Server struct {
Port string
BooksHost string
Logger *logrus.Logger
Renter renter.Service
Inventory inventory.Service
Renters renter.Service
Rentals rental.Service
// LibraryMap holds a map of values that are returned by the books API. This is used to check
// if a book exists in the library.
LibraryMap struct {
+1 -8
View File
@@ -6,7 +6,6 @@ import (
"errors"
"io/ioutil"
"net/http"
"strconv"
"github.com/davidlick/bookish/bookish-server/internal"
"github.com/davidlick/bookish/bookish-server/mysql"
@@ -36,13 +35,7 @@ func (s *Server) renterCtx(next http.Handler) http.Handler {
ctx := r.Context()
id := chi.URLParam(r, "renterId")
renterId, err := strconv.Atoi(id)
if err != nil {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
renter, err := s.Renter.FetchRenter(renterId)
renter, err := s.Renters.FetchRenter(id)
if err != nil && errors.Is(mysql.ErrNotFound, err) {
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
return
@@ -2,12 +2,13 @@ package http
import (
"errors"
"fmt"
"net/http"
bookish "github.com/davidlick/bookish/bookish-server"
"github.com/davidlick/bookish/bookish-server/internal"
"github.com/davidlick/bookish/bookish-server/inventory"
"github.com/davidlick/bookish/bookish-server/mysql"
"github.com/davidlick/bookish/bookish-server/rental"
"github.com/gobuffalo/uuid"
)
// checkoutBook is used to checkout a book for a renter.
@@ -25,10 +26,16 @@ func (s *Server) checkoutBook(w http.ResponseWriter, r *http.Request) {
return
}
err := s.Inventory.CheckoutBook(renter.ID, bookTitle)
id, err := uuid.FromString(renter.ID)
if err != nil {
if errors.Is(inventory.ErrUnavailableBook, err) {
http.Error(w, err.Error(), http.StatusForbidden)
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
return
}
err = s.Rentals.CheckoutBook(id, bookTitle)
if err != nil {
if errors.Is(rental.ErrUnavailableBook, err) {
http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
return
}
@@ -52,9 +59,17 @@ func (s *Server) returnBook(w http.ResponseWriter, r *http.Request) {
return
}
err := s.Inventory.ReturnBook(renter.ID, bookTitle)
id, err := uuid.FromString(renter.ID)
if err != nil {
fmt.Println(err)
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
return
}
err = s.Rentals.ReturnBook(id, bookTitle)
if err != nil && errors.Is(mysql.ErrNotFound, err) {
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
return
} else if err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
+4 -3
View File
@@ -7,6 +7,7 @@ import (
bookish "github.com/davidlick/bookish/bookish-server"
"github.com/davidlick/bookish/bookish-server/internal"
"github.com/gofrs/uuid"
)
// registerRenter creates a record of a new renter.
@@ -24,14 +25,14 @@ func (s *Server) registerRenter(w http.ResponseWriter, r *http.Request) {
return
}
id, err := s.Renter.RegisterRenter(renter.Name, renter.Address, renter.Email, renter.PhoneNumber)
id, err := s.Renters.RegisterRenter(renter.Name, renter.Address, renter.Email, renter.PhoneNumber)
if err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
resp := struct {
ID int `json:"renterId"`
ID uuid.UUID `json:"renterId"`
}{ID: id}
json.NewEncoder(w).Encode(resp)
@@ -39,7 +40,7 @@ func (s *Server) registerRenter(w http.ResponseWriter, r *http.Request) {
// listRenters lists all renters registered in the API.
func (s *Server) listRenters(w http.ResponseWriter, r *http.Request) {
rr, err := s.Renter.ListRenters()
rr, err := s.Renters.ListRenters()
if err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
+6
View File
@@ -0,0 +1,6 @@
development:
dialect: mysql
database: bookish
user: root
password: 123pass
host: db
+17 -2
View File
@@ -3,13 +3,28 @@ module github.com/davidlick/bookish/bookish-server
go 1.14
require (
github.com/cockroachdb/cockroach-go v0.0.0-20200528011702-6cc4ff37a11e // indirect
github.com/fatih/color v1.9.0 // indirect
github.com/go-chi/chi v4.1.1+incompatible
github.com/go-sql-driver/mysql v1.5.0
github.com/gobuffalo/envy v1.9.0 // indirect
github.com/gobuffalo/fizz v1.9.10 // indirect
github.com/gobuffalo/flect v0.2.1 // indirect
github.com/gobuffalo/genny v0.6.0 // indirect
github.com/gobuffalo/nulls v0.4.0
github.com/gobuffalo/packd v1.0.0 // indirect
github.com/gobuffalo/pop v4.13.1+incompatible
github.com/gobuffalo/pop/v5 v5.1.3
github.com/gobuffalo/uuid v2.0.5+incompatible
github.com/gobuffalo/validate v2.0.4+incompatible // indirect
github.com/gobuffalo/validate/v3 v3.1.0
github.com/gofrs/uuid v3.3.0+incompatible
github.com/golang/mock v1.4.3
github.com/jmoiron/sqlx v1.2.0
github.com/kelseyhightower/envconfig v1.4.0
github.com/pkg/errors v0.9.1 // indirect
github.com/sirupsen/logrus v1.6.0
github.com/stretchr/testify v1.5.1
golang.org/x/net v0.0.0-20190603091049-60506f45cf65 // indirect
github.com/stretchr/testify v1.6.0
golang.org/x/net v0.0.0-20200528225125-3c3fba18258b // indirect
golang.org/x/text v0.3.2 // indirect
)
+288
View File
@@ -1,41 +1,301 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Masterminds/semver/v3 v3.0.3 h1:znjIyLfpXEDQjOIEWh+ehwpTU14UzUPub3c3sm36u14=
github.com/Masterminds/semver/v3 v3.0.3/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/cockroachdb/cockroach-go v0.0.0-20190925194419-606b3d062051/go.mod h1:XGLbWH/ujMcbPbhZq52Nv6UrCghb1yGn//133kEsvDk=
github.com/cockroachdb/cockroach-go v0.0.0-20200528011702-6cc4ff37a11e h1:U/LmtPG8RXOORWPJbJWJeSMW1DrnpkovQTuW/Rya/SA=
github.com/cockroachdb/cockroach-go v0.0.0-20200528011702-6cc4ff37a11e/go.mod h1:XGLbWH/ujMcbPbhZq52Nv6UrCghb1yGn//133kEsvDk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/davidlick/bookish v0.0.0-20200529020827-a06b4d70abb9 h1:4xzRnQvEl1tG3ypktRZHO6zoifkZefklaiBv4e1cTZc=
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/go-chi/chi v4.1.1+incompatible h1:MmTgB0R8Bt/jccxp+t6S/1VGIKdJw5J74CK/c9tTfA4=
github.com/go-chi/chi v4.1.1+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/go-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx+opk=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gobuffalo/attrs v0.1.0/go.mod h1:fmNpaWyHM0tRm8gCZWKx8yY9fvaNLo2PyzBNSrBZ5Hw=
github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
github.com/gobuffalo/envy v1.7.1/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w=
github.com/gobuffalo/envy v1.8.1/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w=
github.com/gobuffalo/envy v1.9.0 h1:eZR0DuEgVLfeIb1zIKt3bT4YovIMf9O9LXQeCZLXpqE=
github.com/gobuffalo/envy v1.9.0/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w=
github.com/gobuffalo/fizz v1.9.8/go.mod h1:w1FEn1yKNVCc49KnADGyYGRPH7jFON3ak4Bj1yUudHo=
github.com/gobuffalo/fizz v1.9.10 h1:B/p28Qy2VxFq6zr1EXWuxwI4dl0PUA/xW2dY+XdaDMY=
github.com/gobuffalo/fizz v1.9.10/go.mod h1:J2XGPO0AfJ1zKw7+2BA+6FEGAkyEsdCOLvN93WCT2WI=
github.com/gobuffalo/flect v0.1.5/go.mod h1:W3K3X9ksuZfir8f/LrfVtWmCDQFfayuylOJ7sz/Fj80=
github.com/gobuffalo/flect v0.2.0/go.mod h1:W3K3X9ksuZfir8f/LrfVtWmCDQFfayuylOJ7sz/Fj80=
github.com/gobuffalo/flect v0.2.1 h1:GPoRjEN0QObosV4XwuoWvSd5uSiL0N3e91/xqyY4crQ=
github.com/gobuffalo/flect v0.2.1/go.mod h1:vmkQwuZYhN5Pc4ljYQZzP+1sq+NEkK+lh20jmEmX3jc=
github.com/gobuffalo/genny v0.2.0/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk=
github.com/gobuffalo/genny v0.3.0/go.mod h1:ywJ2CoXrTZj7rbS8HTbzv7uybnLKlsNSBhEQ+yFI3E8=
github.com/gobuffalo/genny v0.6.0 h1:d7c6d66ZrTHHty01hDX1/TcTWvAJQxRZl885KWX5kHY=
github.com/gobuffalo/genny v0.6.0/go.mod h1:Vigx9VDiNscYpa/LwrURqGXLSIbzTfapt9+K6gF1kTA=
github.com/gobuffalo/genny/v2 v2.0.5/go.mod h1:kRkJuAw9mdI37AiEYjV4Dl+TgkBDYf8HZVjLkqe5eBg=
github.com/gobuffalo/github_flavored_markdown v1.0.7/go.mod h1:w93Pd9Lz6LvyQXEG6DktTPHkOtCbr+arAD5mkwMzXLI=
github.com/gobuffalo/github_flavored_markdown v1.1.0 h1:8Zzj4fTRl/OP2R7sGerzSf6g2nEJnaBEJe7UAOiEvbQ=
github.com/gobuffalo/github_flavored_markdown v1.1.0/go.mod h1:TSpTKWcRTI0+v7W3x8dkSKMLJSUpuVitlptCkpeY8ic=
github.com/gobuffalo/gogen v0.2.0/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360=
github.com/gobuffalo/helpers v0.2.2/go.mod h1:xYbzUdCUpVzLwLnqV8HIjT6hmG0Cs7YIBCJkNM597jw=
github.com/gobuffalo/helpers v0.2.4/go.mod h1:NX7v27yxPDOPTgUFYmJ5ow37EbxdoLraucOGvMNawyk=
github.com/gobuffalo/helpers v0.5.0/go.mod h1:stpgxJ2C7T99NLyAxGUnYMM2zAtBk5NKQR0SIbd05j4=
github.com/gobuffalo/helpers v0.6.0 h1:CL1xOSGeKCaKD1IUpo4RfrkDU83kmkMG4H3dXAS7dw0=
github.com/gobuffalo/helpers v0.6.0/go.mod h1:pncVrer7x/KRvnL5aJABLAuT/RhKRR9klL6dkUOhyv8=
github.com/gobuffalo/helpers v0.6.1 h1:LLcL4BsiyDQYtMRUUpyFdBFvFXQ6hNYOpwrcYeilVWM=
github.com/gobuffalo/helpers v0.6.1/go.mod h1:wInbDi0vTJKZBviURTLRMFLE4+nF2uRuuL2fnlYo7w4=
github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM=
github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8=
github.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs=
github.com/gobuffalo/logger v1.0.1/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs=
github.com/gobuffalo/logger v1.0.3 h1:YaXOTHNPCvkqqA7w05A4v0k2tCdpr+sgFlgINbQ6gqc=
github.com/gobuffalo/logger v1.0.3/go.mod h1:SoeejUwldiS7ZsyCBphOGURmWdwUFXs0J7TCjEhjKxM=
github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc=
github.com/gobuffalo/mapi v1.1.0/go.mod h1:pqQ1XAqvpy/JYtRwoieNps2yU8MFiMxBUpAm2FBtQ50=
github.com/gobuffalo/nulls v0.2.0/go.mod h1:w4q8RoSCEt87Q0K0sRIZWYeIxkxog5mh3eN3C/n+dUc=
github.com/gobuffalo/nulls v0.4.0 h1:xi+JHGWIetYqLmS520dSWc8Ifj1P0aNXKTVDMVsPXmw=
github.com/gobuffalo/nulls v0.4.0/go.mod h1:2KmsoLnMrxpwPLN5LmBbm6tmttHSIZr/v/OdGsATM3M=
github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4=
github.com/gobuffalo/packd v0.2.0/go.mod h1:k2CkHP3bjbqL2GwxwhxUy1DgnlbW644hkLC9iIUvZwY=
github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q=
github.com/gobuffalo/packd v1.0.0 h1:6ERZvJHfe24rfFmA9OaoKBdC7+c9sydrytMg8SdFGBM=
github.com/gobuffalo/packd v1.0.0/go.mod h1:6VTc4htmJRFB7u1m/4LeMTWjFoYrUiBkU9Fdec9hrhI=
github.com/gobuffalo/packr/v2 v2.4.0/go.mod h1:ra341gygw9/61nSjAbfwcwh8IrYL4WmR4IsPkPBhQiY=
github.com/gobuffalo/packr/v2 v2.5.2/go.mod h1:sgEE1xNZ6G0FNN5xn9pevVu4nywaxHvgup67xisti08=
github.com/gobuffalo/packr/v2 v2.7.1/go.mod h1:qYEvAazPaVxy7Y7KR0W8qYEE+RymX74kETFqjFoFlOc=
github.com/gobuffalo/plush v3.8.2+incompatible/go.mod h1:rQ4zdtUUyZNqULlc6bqd5scsPfLKfT0+TGMChgduDvI=
github.com/gobuffalo/plush v3.8.3+incompatible h1:kzvUTnFPhwyfPEsx7U7LI05/IIslZVGnAlMA1heWub8=
github.com/gobuffalo/plush v3.8.3+incompatible/go.mod h1:rQ4zdtUUyZNqULlc6bqd5scsPfLKfT0+TGMChgduDvI=
github.com/gobuffalo/plush/v4 v4.0.0 h1:ZHdmfr2R7DQ77XzWZK2PGKJOXm9NRy21EZ6Rw7FhuNw=
github.com/gobuffalo/plush/v4 v4.0.0/go.mod h1:ErFS3UxKqEb8fpFJT7lYErfN/Nw6vHGiDMTjxpk5bQ0=
github.com/gobuffalo/plushgen v0.1.2/go.mod h1:3U71v6HWZpVER1nInTXeAwdoRNsRd4W8aeIa1Lyp+Bk=
github.com/gobuffalo/pop v4.13.1+incompatible h1:AhbqPxNOBN/DBb2DBaiBqzOXIBQXxEYzngHHJ+ytP4g=
github.com/gobuffalo/pop v4.13.1+incompatible/go.mod h1:DwBz3SD5SsHpTZiTubcsFWcVDpJWGsxjVjMPnkiThWg=
github.com/gobuffalo/pop/v5 v5.1.3 h1:XwXm2sJScNqc6dJOaMTAILECw6nqsf3cooNdleEcoWw=
github.com/gobuffalo/pop/v5 v5.1.3/go.mod h1:fzUpBhQE48+kPczDbuJIuOCSS7OMqChoaGR6wj2j7Nc=
github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw=
github.com/gobuffalo/tags v2.1.0+incompatible/go.mod h1:9XmhOkyaB7UzvuY4UoZO4s67q8/xRMVJEaakauVQYeY=
github.com/gobuffalo/tags v2.1.7+incompatible h1:GUxxh34f9SI4U0Pj3ZqvopO9SlzuqSf+g4ZGSPSszt4=
github.com/gobuffalo/tags v2.1.7+incompatible/go.mod h1:9XmhOkyaB7UzvuY4UoZO4s67q8/xRMVJEaakauVQYeY=
github.com/gobuffalo/tags/v3 v3.0.2 h1:gxE6c6fA5radwQeg59aPIeYgCG8YA8AZd3Oh6fh5UXA=
github.com/gobuffalo/tags/v3 v3.0.2/go.mod h1:ZQeN6TCTiwAFnS0dNcbDtSgZDwNKSpqajvVtt6mlYpA=
github.com/gobuffalo/tags/v3 v3.1.0 h1:mzdCYooN2VsLRr8KIAdEZ1lh1Py7JSMsiEGCGata2AQ=
github.com/gobuffalo/tags/v3 v3.1.0/go.mod h1:ZQeN6TCTiwAFnS0dNcbDtSgZDwNKSpqajvVtt6mlYpA=
github.com/gobuffalo/uuid v2.0.5+incompatible h1:c5uWRuEnYggYCrT9AJm0U2v1QTG7OVDAvxhj8tIV5Gc=
github.com/gobuffalo/uuid v2.0.5+incompatible/go.mod h1:ErhIzkRhm0FtRuiE/PeORqcw4cVi1RtSpnwYrxuvkfE=
github.com/gobuffalo/validate v1.0.0 h1:Xdf2irctxenMaXZrgA13Aeo1sUD9+sgaKKVOaMauZ2U=
github.com/gobuffalo/validate v2.0.3+incompatible/go.mod h1:N+EtDe0J8252BgfzQUChBgfd6L93m9weay53EWFVsMM=
github.com/gobuffalo/validate v2.0.4+incompatible h1:ZTxozrIw8qQ5nfhShmc4izjYPTsPhfdXTdhXOd5OS9o=
github.com/gobuffalo/validate v2.0.4+incompatible/go.mod h1:N+EtDe0J8252BgfzQUChBgfd6L93m9weay53EWFVsMM=
github.com/gobuffalo/validate/v3 v3.0.0 h1:dF7Bg8NMF9Zv8bZvUMXYJXxZdj+eSZ8z/lGM7/jVFUE=
github.com/gobuffalo/validate/v3 v3.0.0/go.mod h1:HFpjq+AIiA2RHoQnQVTFKF/ZpUPXwyw82LgyDPxQ9r0=
github.com/gobuffalo/validate/v3 v3.1.0 h1:/QQN920PciCfBs3aywtJTvDTHmBFMKoiwkshUWa/HLQ=
github.com/gobuffalo/validate/v3 v3.1.0/go.mod h1:HFpjq+AIiA2RHoQnQVTFKF/ZpUPXwyw82LgyDPxQ9r0=
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gofrs/uuid v3.3.0+incompatible h1:8K4tyRfvU1CYPgJsveYFQMhpFd/wXNM7iK6rR7UHz84=
github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/golang/mock v1.4.3 h1:GV+pQPG/EUUbkh47niozDcADz6go/dUwhVzdUQHIVRw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
github.com/jackc/pgconn v1.3.2/go.mod h1:LvCquS3HbBKwgl7KbX9KyqEIumJAbm1UMcTvGaIf3bM=
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA=
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU=
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2aSZ0mcI=
github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4=
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
github.com/microcosm-cc/bluemonday v1.0.2 h1:5lPfLTTAvAbtS0VqT+94yOtFnGfUWYyx0+iToC3Os3s=
github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.9.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.6.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.4.0 h1:LUa41nrWTQNGhzdsZ5lTnkwbNjj6rXTdazA1cSdjkOY=
github.com/rogpeppe/go-internal v1.4.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.5.2 h1:qLvObTrvO/XRCqmkKxUlOBc48bI3efyDuAZe25QiF0w=
github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/serenize/snaker v0.0.0-20171204205717-a683aaf2d516/go.mod h1:Yow6lPLSAXx2ifx470yD/nUe22Dv5vBvxK/UK9UUTVs=
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d h1:yKm7XZV6j9Ev6lojP2XaIshpT4ymkqhMeSghO5Ps00E=
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e h1:qpG93cPwA5f7s/ZPBJnGOYQNK/vKsaDaseuKT5Asee8=
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.0 h1:jlIyCplCJFULU/01vCkhKuTyc3OorI3bJFuw6obfgho=
github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c h1:/nJuwDLoL/zrqY6gf57vxC+Pi+pZ8bfhpPkicO5H7W4=
golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65 h1:+rhAzEzT3f4JtomfC371qB+0Ola2caSKcY69NUBZrRQ=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200219183655-46282727080f/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200528225125-3c3fba18258b h1:IYiJPiJfzktmDAO1HQiwjMjwjlYKHAL7KzeD544RJPs=
golang.org/x/net v0.0.0-20200528225125-3c3fba18258b/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
@@ -43,9 +303,37 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262 h1:qsl9y/CJx34tuA7QCPNp86JNJe4spst6Ff8MjvPUdPg=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190613204242-ed0dc450797f/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191004055002-72853e10c5a3/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191224055732-dd894d0a8a40/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117220505-0cba7a3a9ee9/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
+19
View File
@@ -0,0 +1,19 @@
package models
import (
"time"
"github.com/gobuffalo/nulls"
"github.com/gobuffalo/uuid"
)
type Rental struct {
ID uuid.UUID `db:"id"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
BookTitle string `db:"book_title"`
RenterID uuid.UUID `db:"renter_id"`
RentalDate time.Time `db:"rental_date"`
ReturnDate nulls.Time `db:"return_date"`
Renter *Renter `belongs_to:"renter"`
}
+18
View File
@@ -0,0 +1,18 @@
package models
import (
"time"
"github.com/gobuffalo/uuid"
)
type Renter struct {
ID uuid.UUID `json:"id" db:"id"`
CreatedAt time.Time `json:"-" db:"created_at"`
UpdatedAt time.Time `json:"-" db:"updated_at"`
Name string `json:"name" db:"name"`
Address string `json:"address" db:"address"`
Email string `json:"email" db:"email"`
PhoneNumber string `json:"phoneNumber" db:"phone_number"`
Rentals []Rental `json:"rentals" has_many:"rentals"`
}
+1 -1
View File
@@ -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")
)
-111
View File
@@ -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
}
+4 -4
View File
@@ -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
+81
View File
@@ -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
}
+21 -41
View File
@@ -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
}
@@ -1,4 +1,4 @@
package inventory
package rental
import "errors"
@@ -1,11 +1,17 @@
package inventory
package rental
import (
"fmt"
"github.com/gofrs/uuid"
)
// Storage defines behavior for interacting with the inventory store.
type Storage interface {
IsBookAvailable(title string) (available bool, err error)
RenterAlreadyCheckedOut(renterId int, title string) (checkedOut bool, err error)
CheckoutBook(renterId int, title string) error
ReturnBook(renterId int, title string) error
RenterAlreadyCheckedOut(renterId uuid.UUID, title string) (checkedOut bool, err error)
CheckoutBook(renterId uuid.UUID, title string) error
ReturnBook(renterId uuid.UUID, title string) error
}
type service struct {
@@ -15,8 +21,8 @@ type service struct {
// Service defines behavior for interacting with the inventory service.
type Service interface {
IsBookAvailable(title string) (available bool, err error)
CheckoutBook(renterId int, title string) error
ReturnBook(renterId int, title string) error
CheckoutBook(renterId uuid.UUID, title string) error
ReturnBook(renterId uuid.UUID, title string) error
}
// NewService creates a new inventory service.
@@ -32,7 +38,7 @@ func (s *service) IsBookAvailable(title string) (available bool, err error) {
}
// CheckoutBook checks a book out to a renter.
func (s *service) CheckoutBook(renterId int, title string) error {
func (s *service) CheckoutBook(renterId uuid.UUID, title string) error {
available, err := s.IsBookAvailable(title)
if err != nil {
return err
@@ -46,6 +52,7 @@ func (s *service) CheckoutBook(renterId int, title string) error {
// If there was an error or the book is checked out by the renter already we'll
// say the book is unavailable.
if err != nil || checkedOut {
fmt.Println("unavailable book", err, checkedOut)
return ErrUnavailableBook
}
@@ -53,6 +60,6 @@ func (s *service) CheckoutBook(renterId int, title string) error {
}
// ReturnBook returns a book for a renter.
func (s *service) ReturnBook(renterId int, title string) error {
func (s *service) ReturnBook(renterId uuid.UUID, title string) error {
return s.store.ReturnBook(renterId, title)
}
@@ -1,10 +1,11 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: inventory.go
// Source: rental.go
// Package inventory is a generated GoMock package.
package inventory
// Package rental is a generated GoMock package.
package rental
import (
uuid "github.com/gofrs/uuid"
gomock "github.com/golang/mock/gomock"
reflect "reflect"
)
@@ -48,7 +49,7 @@ func (mr *MockStorageMockRecorder) IsBookAvailable(title interface{}) *gomock.Ca
}
// RenterAlreadyCheckedOut mocks base method
func (m *MockStorage) RenterAlreadyCheckedOut(renterId int, title string) (bool, error) {
func (m *MockStorage) RenterAlreadyCheckedOut(renterId uuid.UUID, title string) (bool, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "RenterAlreadyCheckedOut", renterId, title)
ret0, _ := ret[0].(bool)
@@ -63,7 +64,7 @@ func (mr *MockStorageMockRecorder) RenterAlreadyCheckedOut(renterId, title inter
}
// CheckoutBook mocks base method
func (m *MockStorage) CheckoutBook(renterId int, title string) error {
func (m *MockStorage) CheckoutBook(renterId uuid.UUID, title string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CheckoutBook", renterId, title)
ret0, _ := ret[0].(error)
@@ -77,7 +78,7 @@ func (mr *MockStorageMockRecorder) CheckoutBook(renterId, title interface{}) *go
}
// ReturnBook mocks base method
func (m *MockStorage) ReturnBook(renterId int, title string) error {
func (m *MockStorage) ReturnBook(renterId uuid.UUID, title string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ReturnBook", renterId, title)
ret0, _ := ret[0].(error)
@@ -129,7 +130,7 @@ func (mr *MockServiceMockRecorder) IsBookAvailable(title interface{}) *gomock.Ca
}
// CheckoutBook mocks base method
func (m *MockService) CheckoutBook(renterId int, title string) error {
func (m *MockService) CheckoutBook(renterId uuid.UUID, title string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CheckoutBook", renterId, title)
ret0, _ := ret[0].(error)
@@ -143,7 +144,7 @@ func (mr *MockServiceMockRecorder) CheckoutBook(renterId, title interface{}) *go
}
// ReturnBook mocks base method
func (m *MockService) ReturnBook(renterId int, title string) error {
func (m *MockService) ReturnBook(renterId uuid.UUID, title string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ReturnBook", renterId, title)
ret0, _ := ret[0].(error)
@@ -1,18 +1,18 @@
package inventory
package rental
import (
"errors"
"testing"
"github.com/gofrs/uuid"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
)
func TestInventory_IsBookAvailable(t *testing.T) {
func TestRental_IsBookAvailable(t *testing.T) {
testCases := []struct {
TestName string
Title string
RenterId int
Available bool
Error error
}{
@@ -50,10 +50,9 @@ func TestInventory_IsBookAvailable(t *testing.T) {
}
}
func TestInventory_CheckoutBook(t *testing.T) {
func TestRental_CheckoutBook(t *testing.T) {
testCases := []struct {
TestName string
RenterId int
Title string
Available bool
AvailableError error
@@ -63,27 +62,23 @@ func TestInventory_CheckoutBook(t *testing.T) {
}{
{
TestName: "success",
RenterId: 1,
Title: "Book #1",
Available: true,
},
{
TestName: "unavailable",
RenterId: 2,
Title: "Book #2",
Available: false,
Error: ErrUnavailableBook,
},
{
TestName: "availability_error",
RenterId: 3,
Title: "Book #3",
AvailableError: errors.New("test error"),
Error: errors.New("test error"),
},
{
TestName: "checked-out",
RenterId: 4,
Title: "Book #4",
Available: true,
CheckedOut: true,
@@ -91,7 +86,6 @@ func TestInventory_CheckoutBook(t *testing.T) {
},
{
TestName: "failure",
RenterId: 4,
Title: "Book #4",
Available: true,
Error: errors.New("test error"),
@@ -103,38 +97,40 @@ func TestInventory_CheckoutBook(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
renterId, err := uuid.NewV4()
if err != nil {
t.Error(err)
}
mockStorage := NewMockStorage(ctrl)
mockStorage.EXPECT().IsBookAvailable(tc.Title).Return(tc.Available, tc.AvailableError)
if tc.Available && tc.AvailableError == nil {
mockStorage.EXPECT().RenterAlreadyCheckedOut(tc.RenterId, tc.Title).Return(tc.CheckedOut, tc.CheckedOutError)
mockStorage.EXPECT().RenterAlreadyCheckedOut(renterId, tc.Title).Return(tc.CheckedOut, tc.CheckedOutError)
if !tc.CheckedOut && tc.CheckedOutError == nil {
mockStorage.EXPECT().CheckoutBook(tc.RenterId, tc.Title).Return(tc.Error)
mockStorage.EXPECT().CheckoutBook(renterId, tc.Title).Return(tc.Error)
}
}
service := NewService(mockStorage)
err := service.CheckoutBook(tc.RenterId, tc.Title)
err = service.CheckoutBook(renterId, tc.Title)
assert.Equal(t, tc.Error, err)
})
}
}
func TestInventory_ReturnBook(t *testing.T) {
func TestRental_ReturnBook(t *testing.T) {
testCases := []struct {
TestName string
RenterId int
Title string
Error error
}{
{
TestName: "success",
RenterId: 1,
Title: "Book #1",
},
{
TestName: "failure",
RenterId: 2,
Title: "Book #2",
Error: errors.New("test error"),
},
@@ -145,11 +141,16 @@ func TestInventory_ReturnBook(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
renterId, err := uuid.NewV4()
if err != nil {
t.Error(err)
}
mockStorage := NewMockStorage(ctrl)
mockStorage.EXPECT().ReturnBook(tc.RenterId, tc.Title).Return(tc.Error)
mockStorage.EXPECT().ReturnBook(renterId, tc.Title).Return(tc.Error)
service := NewService(mockStorage)
err := service.ReturnBook(tc.RenterId, tc.Title)
err = service.ReturnBook(renterId, tc.Title)
assert.Equal(t, tc.Error, err)
})
+6 -5
View File
@@ -2,9 +2,10 @@ package bookish_server
// Renter is an entity in the Bookish API wish rents books.
type Renter struct {
ID int `db:"id" json:"id"`
Name string `db:"name" json:"name"`
Address string `db:"address" json:"address"`
Email string `db:"email" json:"email"`
PhoneNumber string `db:"phone_number" json:"phoneNumber"`
ID string `db:"id" json:"id"`
Name string `db:"name" json:"name"`
Address string `db:"address" json:"address"`
Email string `db:"email" json:"email"`
PhoneNumber string `db:"phone_number" json:"phoneNumber"`
Rentals []string `json:"rentals"`
}
+46 -9
View File
@@ -2,13 +2,15 @@ package renter
import (
bookish "github.com/davidlick/bookish/bookish-server"
"github.com/davidlick/bookish/bookish-server/models"
"github.com/gobuffalo/uuid"
)
// Storage defines behavior for interacting with the renter store.
type Storage interface {
ListAll() (rr []bookish.Renter, err error)
FetchDetails(id int) (r bookish.Renter, err error)
New(name string, address string, email string, phoneNumber string) (id int, err error)
ListAll() (rr []models.Renter, err error)
FetchDetails(id string) (r models.Renter, err error)
New(name string, address string, email string, phoneNumber string) (id uuid.UUID, err error)
}
type service struct {
@@ -18,8 +20,8 @@ type service struct {
// Service defines behavior for interacting with the renter service.
type Service interface {
ListRenters() (rr []bookish.Renter, err error)
FetchRenter(id int) (r bookish.Renter, err error)
RegisterRenter(fullName string, address string, email string, phoneNumber string) (id int, err error)
FetchRenter(id string) (r bookish.Renter, err error)
RegisterRenter(fullName string, address string, email string, phoneNumber string) (id uuid.UUID, err error)
}
// NewService creates a new renter service.
@@ -31,15 +33,50 @@ func NewService(s Storage) *service {
// ListRenters returns all renters from the renter store.
func (s *service) ListRenters() (rr []bookish.Renter, err error) {
return s.store.ListAll()
renters, err := s.store.ListAll()
if err != nil {
return nil, err
}
for _, renter := range renters {
r := MutateRenterModel(renter)
rr = append(rr, r)
}
return rr, nil
}
// FetchRenter returns a specific renter from the renter store.
func (s *service) FetchRenter(id int) (r bookish.Renter, err error) {
return s.store.FetchDetails(id)
func (s *service) FetchRenter(id string) (r bookish.Renter, err error) {
renter, err := s.store.FetchDetails(id)
if err != nil {
return r, err
}
r = MutateRenterModel(renter)
return r, nil
}
// RegisterRenter registers a new renter in the renter store.
func (s *service) RegisterRenter(fullName string, address string, email string, phoneNumber string) (id int, err error) {
func (s *service) RegisterRenter(fullName string, address string, email string, phoneNumber string) (id uuid.UUID, err error) {
return s.store.New(fullName, address, email, phoneNumber)
}
// MutateRenterModel is a convenience function to mutate a models.Renter into a bookish.Renter.
func MutateRenterModel(m models.Renter) bookish.Renter {
r := bookish.Renter{}
r.ID = m.ID.String()
r.Name = m.Name
r.Address = m.Address
r.Email = m.Email
r.PhoneNumber = m.PhoneNumber
for _, rental := range m.Rentals {
if !rental.ReturnDate.Valid {
r.Rentals = append(r.Rentals, rental.BookTitle)
}
}
return r
}
+77 -7
View File
@@ -1,11 +1,13 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: ./renter.go
// Source: renter.go
// Package renter is a generated GoMock package.
package renter
import (
bookish_server "github.com/davidlick/bookish/bookish-server"
models "github.com/davidlick/bookish/bookish-server/models"
uuid "github.com/gobuffalo/uuid"
gomock "github.com/golang/mock/gomock"
reflect "reflect"
)
@@ -34,10 +36,10 @@ func (m *MockStorage) EXPECT() *MockStorageMockRecorder {
}
// ListAll mocks base method
func (m *MockStorage) ListAll() ([]bookish_server.Renter, error) {
func (m *MockStorage) ListAll() ([]models.Renter, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ListAll")
ret0, _ := ret[0].([]bookish_server.Renter)
ret0, _ := ret[0].([]models.Renter)
ret1, _ := ret[1].(error)
return ret0, ret1
}
@@ -49,10 +51,10 @@ func (mr *MockStorageMockRecorder) ListAll() *gomock.Call {
}
// FetchDetails mocks base method
func (m *MockStorage) FetchDetails(id int) (bookish_server.Renter, error) {
func (m *MockStorage) FetchDetails(id string) (models.Renter, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "FetchDetails", id)
ret0, _ := ret[0].(bookish_server.Renter)
ret0, _ := ret[0].(models.Renter)
ret1, _ := ret[1].(error)
return ret0, ret1
}
@@ -64,10 +66,10 @@ func (mr *MockStorageMockRecorder) FetchDetails(id interface{}) *gomock.Call {
}
// New mocks base method
func (m *MockStorage) New(name, address, email, phoneNumber string) (int, error) {
func (m *MockStorage) New(name, address, email, phoneNumber string) (uuid.UUID, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "New", name, address, email, phoneNumber)
ret0, _ := ret[0].(int)
ret0, _ := ret[0].(uuid.UUID)
ret1, _ := ret[1].(error)
return ret0, ret1
}
@@ -77,3 +79,71 @@ func (mr *MockStorageMockRecorder) New(name, address, email, phoneNumber interfa
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "New", reflect.TypeOf((*MockStorage)(nil).New), name, address, email, phoneNumber)
}
// MockService is a mock of Service interface
type MockService struct {
ctrl *gomock.Controller
recorder *MockServiceMockRecorder
}
// MockServiceMockRecorder is the mock recorder for MockService
type MockServiceMockRecorder struct {
mock *MockService
}
// NewMockService creates a new mock instance
func NewMockService(ctrl *gomock.Controller) *MockService {
mock := &MockService{ctrl: ctrl}
mock.recorder = &MockServiceMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockService) EXPECT() *MockServiceMockRecorder {
return m.recorder
}
// ListRenters mocks base method
func (m *MockService) ListRenters() ([]bookish_server.Renter, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ListRenters")
ret0, _ := ret[0].([]bookish_server.Renter)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ListRenters indicates an expected call of ListRenters
func (mr *MockServiceMockRecorder) ListRenters() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListRenters", reflect.TypeOf((*MockService)(nil).ListRenters))
}
// FetchRenter mocks base method
func (m *MockService) FetchRenter(id string) (bookish_server.Renter, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "FetchRenter", id)
ret0, _ := ret[0].(bookish_server.Renter)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// FetchRenter indicates an expected call of FetchRenter
func (mr *MockServiceMockRecorder) FetchRenter(id interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchRenter", reflect.TypeOf((*MockService)(nil).FetchRenter), id)
}
// RegisterRenter mocks base method
func (m *MockService) RegisterRenter(fullName, address, email, phoneNumber string) (uuid.UUID, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "RegisterRenter", fullName, address, email, phoneNumber)
ret0, _ := ret[0].(uuid.UUID)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// RegisterRenter indicates an expected call of RegisterRenter
func (mr *MockServiceMockRecorder) RegisterRenter(fullName, address, email, phoneNumber interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RegisterRenter", reflect.TypeOf((*MockService)(nil).RegisterRenter), fullName, address, email, phoneNumber)
}
+33 -13
View File
@@ -5,6 +5,8 @@ import (
"testing"
bookish "github.com/davidlick/bookish/bookish-server"
"github.com/davidlick/bookish/bookish-server/models"
"github.com/gofrs/uuid"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
)
@@ -12,12 +14,12 @@ import (
func TestRenter_ListRenters(t *testing.T) {
testCases := []struct {
TestName string
Renters []bookish.Renter
Renters []models.Renter
Error error
}{
{
TestName: "success",
Renters: []bookish.Renter{
Renters: []models.Renter{
{
Name: "John Doe",
Address: "123 Main Way",
@@ -49,7 +51,13 @@ func TestRenter_ListRenters(t *testing.T) {
service := NewService(mockStorage)
renters, err := service.ListRenters()
assert.Equal(t, tc.Renters, renters)
var expectedRenters []bookish.Renter
for _, renter := range tc.Renters {
r := MutateRenterModel(renter)
expectedRenters = append(expectedRenters, r)
}
assert.Equal(t, expectedRenters, renters)
assert.Equal(t, tc.Error, err)
})
}
@@ -59,13 +67,12 @@ func TestRenter_FetchRenter(t *testing.T) {
testCases := []struct {
TestName string
ID int
Renter bookish.Renter
Renter models.Renter
Error error
}{
{
TestName: "success",
ID: 1,
Renter: bookish.Renter{
Renter: models.Renter{
Name: "John Smith",
Address: "123 Main Way",
Email: "john@smith.com",
@@ -74,7 +81,6 @@ func TestRenter_FetchRenter(t *testing.T) {
},
{
TestName: "error",
ID: 1,
Error: errors.New("test error"),
},
}
@@ -84,13 +90,23 @@ func TestRenter_FetchRenter(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
renterId, err := uuid.NewV4()
if err != nil {
t.Error(err)
}
mockStorage := NewMockStorage(ctrl)
mockStorage.EXPECT().FetchDetails(tc.ID).Return(tc.Renter, tc.Error)
mockStorage.EXPECT().FetchDetails(renterId.String()).Return(tc.Renter, tc.Error)
service := NewService(mockStorage)
renter, err := service.FetchRenter(tc.ID)
renter, err := service.FetchRenter(renterId.String())
assert.Equal(t, tc.Renter, renter)
expectedRenter := MutateRenterModel(tc.Renter)
if tc.Error != nil {
renter.ID = "00000000-0000-0000-0000-000000000000"
}
assert.Equal(t, expectedRenter, renter)
assert.Equal(t, tc.Error, err)
})
}
@@ -103,7 +119,6 @@ func TestRenter_RegisterRenter(t *testing.T) {
Address string
Email string
PhoneNumber string
ReturnID int
Error error
}{
{
@@ -116,13 +131,18 @@ func TestRenter_RegisterRenter(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
returnUuid, err := uuid.NewV4()
if err != nil {
t.Error(err)
}
mockStorage := NewMockStorage(ctrl)
mockStorage.EXPECT().New(tc.Name, tc.Address, tc.Email, tc.PhoneNumber).Return(tc.ReturnID, tc.Error)
mockStorage.EXPECT().New(tc.Name, tc.Address, tc.Email, tc.PhoneNumber).Return(returnUuid, tc.Error)
service := NewService(mockStorage)
id, err := service.RegisterRenter(tc.Name, tc.Address, tc.Email, tc.PhoneNumber)
assert.Equal(t, tc.ReturnID, id)
assert.Equal(t, returnUuid, id)
assert.Equal(t, tc.Error, err)
})
}
+10 -10
View File
@@ -14,14 +14,14 @@ services:
- DBUSER=root
- DBPASS=123pass
- DBPORT=3306
- BOOKSHOST=<ADD BOOKS API HOSTNAME HERE>
- BOOKSHOST=https://servicepros-test-api.herokuapp.com
db:
image: mysql
ports:
- 3306:3306
restart: always
volumes:
- ./init:/docker-entrypoint-initdb.d
environment:
MYSQL_DATABASE: bookish
MYSQL_ROOT_PASSWORD: 123pass
image: mysql
ports:
- 3306:3306
restart: always
volumes:
- ./init:/docker-entrypoint-initdb.d
environment:
MYSQL_DATABASE: bookish
MYSQL_ROOT_PASSWORD: 123pass
-21
View File
@@ -1,21 +0,0 @@
create table renters
(
id int auto_increment primary key,
name varchar(255) not null,
address varchar(255) not null,
email varchar(255) not null,
phone_number varchar(255) not null
);
create unique index renters_id_uindex
on renters (id);
create table rentals
(
id int auto_increment primary key,
renter_id int not null,
rental_date datetime not null,
return_date datetime null,
book_title varchar(255) not null,
foreign key (renter_id) references renters(id)
);
+22
View File
@@ -0,0 +1,22 @@
create table if not exists renters
(
id varchar(255) not null primary key,
created_at datetime not null,
updated_at datetime not null,
name varchar(255) not null,
address varchar(255) not null,
email varchar(255) not null,
phone_number varchar(255) not null
);
create table if not exists rentals
(
id varchar(255) not null primary key,
created_at datetime not null,
updated_at datetime not null,
book_title varchar(255) not null,
renter_id varchar(255) not null,
rental_date datetime not null,
return_date datetime null,
foreign key (renter_id) references renters(id)
);