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) { 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: []string{"white", "black", "red", "blue"}[rand.Intn(4)], Name: []string{"bowler", "baseball cap", "top hat", "derby"}[rand.Intn(3)], } 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() }