ducky/subscriptions

Paddy 2015-06-11 Child:f1a22fc2321d

0:56a2bef197cd Go to Latest

ducky/subscriptions/subscription.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.

History
paddy@0 1 package subscriptions
paddy@0 2
paddy@0 3 import (
paddy@0 4 "errors"
paddy@0 5 "time"
paddy@0 6
paddy@0 7 "code.secondbit.org/uuid.hg"
paddy@0 8 )
paddy@0 9
paddy@0 10 const (
paddy@0 11 // MonthlyPeriod represents a period of once a month.
paddy@0 12 MonthlyPeriod period = "monthly"
paddy@0 13 )
paddy@0 14
paddy@0 15 var (
paddy@0 16 // ErrSubscriptionAlreadyExists is returned when a Subscription
paddy@0 17 // with an identical ID already exists in the subscriptionStore.
paddy@0 18 ErrSubscriptionAlreadyExists = errors.New("Subscription already exists")
paddy@0 19 // ErrSubscriptionNotFound is returned when a single Subscription
paddy@0 20 // is acted upon or requested, but cannot be found.
paddy@0 21 ErrSubscriptionNotFound = errors.New("Subscription not found")
paddy@0 22 // ErrStripeCustomerAlreadyExists is returned when a Subscription
paddy@0 23 // is created or updates its StripeCustomer property, but that
paddy@0 24 // StripeCustomer is already associated with another Subscription.
paddy@0 25 ErrStripeCustomerAlreadyExists = errors.New("Stripe customer already assigned to another Subscription")
paddy@0 26 // ErrSubscriptionChangeEmpty is returned when a SubscriptionChange
paddy@0 27 // is empty but is passed to subscriptionStore.UpdateSubscription
paddy@0 28 // anyways.
paddy@0 29 ErrSubscriptionChangeEmpty = errors.New("SubscriptionChange is empty")
paddy@0 30 // ErrNoSubscriptionID is returned when one or more Subscription IDs
paddy@0 31 // are required, but none are provided.
paddy@0 32 ErrNoSubscriptionID = errors.New("no Subscription ID provided")
paddy@0 33 )
paddy@0 34
paddy@0 35 type period string
paddy@0 36
paddy@0 37 // Subscription represents the state of a user's payments. It holds
paddy@0 38 // metadata about the last time a user was charged, how much a user
paddy@0 39 // should be charged, how to charge a user and how much to charge
paddy@0 40 // the user.
paddy@0 41 type Subscription struct {
paddy@0 42 ID uuid.ID
paddy@0 43 UserID uuid.ID
paddy@0 44 StripeCustomer string
paddy@0 45 Amount int
paddy@0 46 Period period
paddy@0 47 Created time.Time
paddy@0 48 BeginCharging time.Time
paddy@0 49 LastCharged time.Time
paddy@0 50 LastNotified time.Time
paddy@0 51 InLockout bool
paddy@0 52 }
paddy@0 53
paddy@0 54 // SubscriptionChange represents desired changes to a Subscription
paddy@0 55 // object. A nil value means that property should remain unchanged.
paddy@0 56 type SubscriptionChange struct {
paddy@0 57 StripeCustomer *string
paddy@0 58 Amount *int
paddy@0 59 Period *period
paddy@0 60 BeginCharging *time.Time
paddy@0 61 LastCharged *time.Time
paddy@0 62 LastNotified *time.Time
paddy@0 63 InLockout *bool
paddy@0 64 }
paddy@0 65
paddy@0 66 // IsEmpty returns true if the SubscriptionChange doesn't request
paddy@0 67 // a change to any property of the Subscription.
paddy@0 68 func (change SubscriptionChange) IsEmpty() bool {
paddy@0 69 if change.StripeCustomer != nil {
paddy@0 70 return false
paddy@0 71 }
paddy@0 72 if change.Amount != nil {
paddy@0 73 return false
paddy@0 74 }
paddy@0 75 if change.Period != nil {
paddy@0 76 return false
paddy@0 77 }
paddy@0 78 if change.BeginCharging != nil {
paddy@0 79 return false
paddy@0 80 }
paddy@0 81 if change.LastCharged != nil {
paddy@0 82 return false
paddy@0 83 }
paddy@0 84 if change.LastNotified != nil {
paddy@0 85 return false
paddy@0 86 }
paddy@0 87 if change.InLockout != nil {
paddy@0 88 return false
paddy@0 89 }
paddy@0 90 return true
paddy@0 91 }
paddy@0 92
paddy@0 93 // ApplyChange updates a Subscription based on the changes requested
paddy@0 94 // by a SubscriptionChange.
paddy@0 95 func (s *Subscription) ApplyChange(change SubscriptionChange) {
paddy@0 96 if change.StripeCustomer != nil {
paddy@0 97 s.StripeCustomer = *change.StripeCustomer
paddy@0 98 }
paddy@0 99 if change.Amount != nil {
paddy@0 100 s.Amount = *change.Amount
paddy@0 101 }
paddy@0 102 if change.Period != nil {
paddy@0 103 s.Period = *change.Period
paddy@0 104 }
paddy@0 105 if change.BeginCharging != nil {
paddy@0 106 s.BeginCharging = *change.BeginCharging
paddy@0 107 }
paddy@0 108 if change.LastCharged != nil {
paddy@0 109 s.LastCharged = *change.LastCharged
paddy@0 110 }
paddy@0 111 if change.LastNotified != nil {
paddy@0 112 s.LastNotified = *change.LastNotified
paddy@0 113 }
paddy@0 114 if change.InLockout != nil {
paddy@0 115 s.InLockout = *change.InLockout
paddy@0 116 }
paddy@0 117 }
paddy@0 118
paddy@0 119 // ByLastChargeDate allows us to sort a []Subscription by the LastCharged
paddy@0 120 // property, with the lowest LastCharged date first.
paddy@0 121 type ByLastChargeDate []Subscription
paddy@0 122
paddy@0 123 // Len returns the length the SubscriptionsByLastChargeDate. It fulfills
paddy@0 124 // the sort.Interface interface.
paddy@0 125 func (s ByLastChargeDate) Len() int {
paddy@0 126 return len(s)
paddy@0 127 }
paddy@0 128
paddy@0 129 // Swap puts the item in position i in position j, and the item in position
paddy@0 130 // j in position i. It fulfills the sort.Interface interface.
paddy@0 131 func (s ByLastChargeDate) Swap(i, j int) {
paddy@0 132 s[i], s[j] = s[j], s[i]
paddy@0 133 }
paddy@0 134
paddy@0 135 // Less returns true if the item in position i should be sorted before the
paddy@0 136 // item in position j.
paddy@0 137 func (s ByLastChargeDate) Less(i, j int) bool {
paddy@0 138 return s[i].LastCharged.Before(s[j].LastCharged)
paddy@0 139 }
paddy@0 140
paddy@0 141 type subscriptionStore interface {
paddy@0 142 reset() error
paddy@0 143 createSubscription(sub Subscription) error
paddy@0 144 updateSubscription(id uuid.ID, change SubscriptionChange) error
paddy@0 145 deleteSubscription(id uuid.ID) error
paddy@0 146 listSubscriptionsLastChargedBefore(time.Time) ([]Subscription, error)
paddy@0 147 getSubscriptions(ids []uuid.ID) (map[string]Subscription, error)
paddy@0 148 getSubscriptionByUser(id uuid.ID) (Subscription, error)
paddy@0 149 }