Implement PostgreSQL support, drop subscription IDs.
Create a Postgres object that wraps database/sql, so we can attach methods to it
and fulfill interfaces.
Create a postgres_init.sql script that will create the subscriptions table in a
PostgreSQL database.
Make our period type fulfill the driver.Valuer and driver.Scanner types, so it
can be stored in and retrieved from SQL databases.
Create a SubscriptionStats type, and add a method to our subscriptionStore
interface that will allow us to retrieve current stats about the Subscriptions
it is storing.
Deprecated the ID property of our Subscription type, and use the
Subscription.UserID property instead as our primary key. Subscriptions should be
unique per user and we generally will want to access Subscriptions in the
context of the User they belong to, so the UserID is a better primary key. This
also means we removed the getSubscriptionByUserID method (and implementations)
from our subscriptionStore, as getSubscriptions now fills that role.
Implement our getSubscriptionStats method in the memstore.
Implement the subscriptionStore interface on our new Postgres type.
Run the subscription store tests on our Postgres type, as well, if the
PG_TEST_DB environment variable is set.
Round all our timestamps in our tests to the nearest millisecond, as Postgres
silently truncates all timestamps to the nearest millisecond, and it was causing
false test failures.
Remove the tests for our getSubscriptionStoreByUser method, as that was removed.
7 "code.secondbit.org/uuid.hg"
10 func stripeCustomerInMemstore(stripeCustomer string, m *Memstore) bool {
11 for _, sub := range m.subscriptions {
12 if sub.StripeCustomer == stripeCustomer {
19 func (m *Memstore) createSubscription(sub Subscription) error {
20 m.subscriptionLock.Lock()
21 defer m.subscriptionLock.Unlock()
23 if _, ok := m.subscriptions[sub.UserID.String()]; ok {
24 return ErrSubscriptionAlreadyExists
26 if stripeCustomerInMemstore(sub.StripeCustomer, m) {
27 return ErrStripeCustomerAlreadyExists
29 m.subscriptions[sub.UserID.String()] = sub
33 func (m *Memstore) updateSubscription(id uuid.ID, change SubscriptionChange) error {
35 return ErrSubscriptionChangeEmpty
38 m.subscriptionLock.Lock()
39 defer m.subscriptionLock.Unlock()
41 s, ok := m.subscriptions[id.String()]
43 return ErrSubscriptionNotFound
45 if change.StripeCustomer != nil {
46 if stripeCustomerInMemstore(*change.StripeCustomer, m) {
47 return ErrStripeCustomerAlreadyExists
51 m.subscriptions[id.String()] = s
55 func (m *Memstore) deleteSubscription(id uuid.ID) error {
56 m.subscriptionLock.Lock()
57 defer m.subscriptionLock.Unlock()
59 _, ok := m.subscriptions[id.String()]
61 return ErrSubscriptionNotFound
63 delete(m.subscriptions, id.String())
67 func (m *Memstore) listSubscriptionsLastChargedBefore(cutoff time.Time) ([]Subscription, error) {
68 m.subscriptionLock.RLock()
69 defer m.subscriptionLock.RUnlock()
71 var result []Subscription
72 for _, s := range m.subscriptions {
73 if cutoff.Before(s.LastCharged) {
76 result = append(result, s)
79 sorted := ByLastChargeDate(result)
81 result = []Subscription(sorted)
86 func (m *Memstore) getSubscriptions(ids []uuid.ID) (map[string]Subscription, error) {
88 return map[string]Subscription{}, ErrNoSubscriptionID
90 m.subscriptionLock.RLock()
91 defer m.subscriptionLock.RUnlock()
93 result := map[string]Subscription{}
95 for _, id := range ids {
96 s, ok := m.subscriptions[id.String()]
100 result[s.UserID.String()] = s
105 func (m *Memstore) getSubscriptionStats() (SubscriptionStats, error) {
106 m.subscriptionLock.RLock()
107 defer m.subscriptionLock.RUnlock()
109 stats := SubscriptionStats{}
111 for _, subscription := range m.subscriptions {
113 stats.TotalAmount += int64(subscription.Amount)
116 if stats.Number > 0 {
117 stats.MeanAmount = float64(stats.TotalAmount) / float64(stats.Number)