ducky/subscriptions
ducky/subscriptions/subscription_memstore.go
First implementation of datastore interface. Define Subscriptions, and the SubscriptionChange type that can modify Subscriptions. Define the subscriptionStore, which describes how Subscriptions are to be created, retrieved, updated, and deleted in/from the storage backend they're stored in. Create a Memstore implementation of the subscriptionStore, which stores all our data in-memory, for use in testing. Write tests to cover the subscriptionStore interface, testing the entire Memstore. We'll plug future storage backends into these tests, to make sure they all behave the same, and to exercise each storage backend without requiring a suite of tests for each. At this point, we have 100% test coverage and no complaints from golint or go vet. I expect it's all downhill from here.
| paddy@0 | 1 package subscriptions |
| paddy@0 | 2 |
| paddy@0 | 3 import ( |
| paddy@0 | 4 "sort" |
| paddy@0 | 5 "time" |
| paddy@0 | 6 |
| paddy@0 | 7 "code.secondbit.org/uuid.hg" |
| paddy@0 | 8 ) |
| paddy@0 | 9 |
| paddy@0 | 10 func stripeCustomerInMemstore(stripeCustomer string, m *Memstore) bool { |
| paddy@0 | 11 for _, sub := range m.subscriptions { |
| paddy@0 | 12 if sub.StripeCustomer == stripeCustomer { |
| paddy@0 | 13 return true |
| paddy@0 | 14 } |
| paddy@0 | 15 } |
| paddy@0 | 16 return false |
| paddy@0 | 17 } |
| paddy@0 | 18 |
| paddy@0 | 19 func (m *Memstore) createSubscription(sub Subscription) error { |
| paddy@0 | 20 m.subscriptionLock.Lock() |
| paddy@0 | 21 defer m.subscriptionLock.Unlock() |
| paddy@0 | 22 |
| paddy@0 | 23 if _, ok := m.subscriptions[sub.ID.String()]; ok { |
| paddy@0 | 24 return ErrSubscriptionAlreadyExists |
| paddy@0 | 25 } |
| paddy@0 | 26 if stripeCustomerInMemstore(sub.StripeCustomer, m) { |
| paddy@0 | 27 return ErrStripeCustomerAlreadyExists |
| paddy@0 | 28 } |
| paddy@0 | 29 m.subscriptions[sub.ID.String()] = sub |
| paddy@0 | 30 return nil |
| paddy@0 | 31 } |
| paddy@0 | 32 |
| paddy@0 | 33 func (m *Memstore) updateSubscription(id uuid.ID, change SubscriptionChange) error { |
| paddy@0 | 34 if change.IsEmpty() { |
| paddy@0 | 35 return ErrSubscriptionChangeEmpty |
| paddy@0 | 36 } |
| paddy@0 | 37 |
| paddy@0 | 38 m.subscriptionLock.Lock() |
| paddy@0 | 39 defer m.subscriptionLock.Unlock() |
| paddy@0 | 40 |
| paddy@0 | 41 s, ok := m.subscriptions[id.String()] |
| paddy@0 | 42 if !ok { |
| paddy@0 | 43 return ErrSubscriptionNotFound |
| paddy@0 | 44 } |
| paddy@0 | 45 if change.StripeCustomer != nil { |
| paddy@0 | 46 if stripeCustomerInMemstore(*change.StripeCustomer, m) { |
| paddy@0 | 47 return ErrStripeCustomerAlreadyExists |
| paddy@0 | 48 } |
| paddy@0 | 49 } |
| paddy@0 | 50 s.ApplyChange(change) |
| paddy@0 | 51 m.subscriptions[id.String()] = s |
| paddy@0 | 52 return nil |
| paddy@0 | 53 } |
| paddy@0 | 54 |
| paddy@0 | 55 func (m *Memstore) deleteSubscription(id uuid.ID) error { |
| paddy@0 | 56 m.subscriptionLock.Lock() |
| paddy@0 | 57 defer m.subscriptionLock.Unlock() |
| paddy@0 | 58 |
| paddy@0 | 59 _, ok := m.subscriptions[id.String()] |
| paddy@0 | 60 if !ok { |
| paddy@0 | 61 return ErrSubscriptionNotFound |
| paddy@0 | 62 } |
| paddy@0 | 63 delete(m.subscriptions, id.String()) |
| paddy@0 | 64 return nil |
| paddy@0 | 65 } |
| paddy@0 | 66 |
| paddy@0 | 67 func (m *Memstore) listSubscriptionsLastChargedBefore(cutoff time.Time) ([]Subscription, error) { |
| paddy@0 | 68 m.subscriptionLock.RLock() |
| paddy@0 | 69 defer m.subscriptionLock.RUnlock() |
| paddy@0 | 70 |
| paddy@0 | 71 var result []Subscription |
| paddy@0 | 72 for _, s := range m.subscriptions { |
| paddy@0 | 73 if cutoff.Before(s.LastCharged) { |
| paddy@0 | 74 continue |
| paddy@0 | 75 } |
| paddy@0 | 76 result = append(result, s) |
| paddy@0 | 77 } |
| paddy@0 | 78 |
| paddy@0 | 79 sorted := ByLastChargeDate(result) |
| paddy@0 | 80 sort.Sort(sorted) |
| paddy@0 | 81 result = []Subscription(sorted) |
| paddy@0 | 82 |
| paddy@0 | 83 return result, nil |
| paddy@0 | 84 } |
| paddy@0 | 85 |
| paddy@0 | 86 func (m *Memstore) getSubscriptions(ids []uuid.ID) (map[string]Subscription, error) { |
| paddy@0 | 87 if len(ids) < 1 { |
| paddy@0 | 88 return map[string]Subscription{}, ErrNoSubscriptionID |
| paddy@0 | 89 } |
| paddy@0 | 90 m.subscriptionLock.RLock() |
| paddy@0 | 91 defer m.subscriptionLock.RUnlock() |
| paddy@0 | 92 |
| paddy@0 | 93 result := map[string]Subscription{} |
| paddy@0 | 94 |
| paddy@0 | 95 for _, id := range ids { |
| paddy@0 | 96 s, ok := m.subscriptions[id.String()] |
| paddy@0 | 97 if !ok { |
| paddy@0 | 98 continue |
| paddy@0 | 99 } |
| paddy@0 | 100 result[s.ID.String()] = s |
| paddy@0 | 101 } |
| paddy@0 | 102 return result, nil |
| paddy@0 | 103 } |
| paddy@0 | 104 |
| paddy@0 | 105 func (m *Memstore) getSubscriptionByUser(id uuid.ID) (Subscription, error) { |
| paddy@0 | 106 m.subscriptionLock.RLock() |
| paddy@0 | 107 defer m.subscriptionLock.RUnlock() |
| paddy@0 | 108 |
| paddy@0 | 109 for _, subscription := range m.subscriptions { |
| paddy@0 | 110 if subscription.UserID.Equal(id) { |
| paddy@0 | 111 return subscription, nil |
| paddy@0 | 112 } |
| paddy@0 | 113 } |
| paddy@0 | 114 |
| paddy@0 | 115 return Subscription{}, ErrSubscriptionNotFound |
| paddy@0 | 116 } |