170 lines
3.1 KiB
Go
170 lines
3.1 KiB
Go
package haberdasherserver
|
|
|
|
import (
|
|
"context"
|
|
"encoding/binary"
|
|
"encoding/json"
|
|
"fmt"
|
|
"math/rand"
|
|
"os"
|
|
|
|
"github.com/boltdb/bolt"
|
|
"github.com/twitchtv/twirp"
|
|
|
|
pb "haberdasher-twirp/haberdasher"
|
|
)
|
|
|
|
const dbBucket = "hats"
|
|
|
|
type Server struct {
|
|
DBPath string
|
|
}
|
|
|
|
type Hat struct {
|
|
Inches int32 `json:"inches"`
|
|
Color string `json:"color"`
|
|
Name string `json:"name"`
|
|
}
|
|
|
|
type HatQuery struct {
|
|
Limit int32
|
|
}
|
|
|
|
type Store struct {
|
|
db *bolt.DB
|
|
}
|
|
|
|
func HatToHatModel(h *pb.Hat) Hat {
|
|
return Hat{
|
|
Inches: h.Inches,
|
|
Color: h.Color,
|
|
Name: h.Name,
|
|
}
|
|
}
|
|
|
|
func HatModelToHat(h Hat) *pb.Hat {
|
|
return &pb.Hat{
|
|
Inches: h.Inches,
|
|
Color: h.Color,
|
|
Name: h.Name,
|
|
}
|
|
}
|
|
|
|
func HatsModelToHats(hs []Hat) (hats *pb.Hats) {
|
|
hats = &pb.Hats{}
|
|
for _, h := range hs {
|
|
hat := HatModelToHat(h)
|
|
hats.Hats = append(hats.Hats, hat)
|
|
}
|
|
return hats
|
|
}
|
|
|
|
func HatQueryToHatQueryModel(q *pb.HatQuery) HatQuery {
|
|
return HatQuery{
|
|
Limit: q.Limit,
|
|
}
|
|
}
|
|
|
|
func (s *Server) MakeHat(ctx context.Context, size *pb.Size) (hat *pb.Hat, err error) {
|
|
var colors []string = []string{"white", "black", "red", "blue", "white"}
|
|
var names []string = []string{"bowler", "baseball cap", "top hat", "derby", "tricorne"}
|
|
|
|
st, _ := NewStore(s.DBPath, 0600, nil)
|
|
defer st.Close()
|
|
|
|
if size.Inches <= 0 {
|
|
return nil, twirp.InvalidArgumentError("inches", "I can't make a hat that small!")
|
|
}
|
|
|
|
h := Hat{
|
|
Inches: size.Inches,
|
|
Color: colors[rand.Intn(len(colors))],
|
|
Name: names[rand.Intn(len(names))],
|
|
}
|
|
|
|
fmt.Printf("made hat: %+v\n", h)
|
|
|
|
st.SaveHat(h)
|
|
|
|
return HatModelToHat(h), nil
|
|
}
|
|
|
|
func (s *Server) ListHats(ctx context.Context, q *pb.HatQuery) (hats *pb.Hats, err error) {
|
|
st, _ := NewStore(s.DBPath, 0600, nil)
|
|
defer st.Close()
|
|
|
|
hs, err := st.ListHats(HatQueryToHatQueryModel(q))
|
|
|
|
return HatsModelToHats(hs), nil
|
|
}
|
|
|
|
func itob(v uint64) []byte {
|
|
b := make([]byte, 8)
|
|
binary.BigEndian.PutUint64(b, uint64(v))
|
|
return b
|
|
}
|
|
|
|
func btoi(b []byte) uint64 {
|
|
return binary.BigEndian.Uint64(b)
|
|
}
|
|
|
|
func NewStore(path string, mode os.FileMode, options *bolt.Options) (s *Store, err error) {
|
|
s = &Store{}
|
|
s.db, err = bolt.Open(path, mode, options)
|
|
if err != nil {
|
|
fmt.Printf("bolt open: %s", err)
|
|
}
|
|
|
|
s.db.Update(func(tx *bolt.Tx) error {
|
|
if _, err := tx.CreateBucket([]byte(dbBucket)); err != nil {
|
|
return fmt.Errorf("create bucket: %s", err)
|
|
}
|
|
return nil
|
|
})
|
|
|
|
return s, err
|
|
}
|
|
|
|
func (s *Store) SaveHat(h Hat) (err error) {
|
|
return s.db.Update(func(tx *bolt.Tx) error {
|
|
b := tx.Bucket([]byte("hats"))
|
|
|
|
id, _ := b.NextSequence()
|
|
|
|
buf, err := json.Marshal(h)
|
|
if err != nil {
|
|
return fmt.Errorf("json marshal: %s", err)
|
|
}
|
|
|
|
return b.Put(itob(id), buf)
|
|
})
|
|
}
|
|
|
|
func (s *Store) ListHats(q HatQuery) (hats []Hat, err error) {
|
|
var hat Hat
|
|
var i int
|
|
|
|
s.db.View(func(tx *bolt.Tx) error {
|
|
b := tx.Bucket([]byte("hats"))
|
|
|
|
c := b.Cursor()
|
|
|
|
for k, v := c.Last(); k != nil && i < int(q.Limit); k, v = c.Prev() {
|
|
if err := json.Unmarshal(v, &hat); err != nil {
|
|
return fmt.Errorf("json unmarshal: %s", err)
|
|
}
|
|
h := hat
|
|
hats = append(hats, h)
|
|
i++
|
|
}
|
|
|
|
return nil
|
|
})
|
|
|
|
return hats, nil
|
|
}
|
|
|
|
func (s *Store) Close() (err error) {
|
|
return s.db.Close()
|
|
}
|