ducky/subscriptions

Paddy 2015-07-13 Parent:c4cfceb2f2fb Child:0ae1ff0ee306

7:9e138933e4ce Go to Latest

ducky/subscriptions/api/subscription_handlers.go

Create a client for working with subscriptions. We mostly copied our code.secondbit.org/auth.hg/client package to create a simple client library for communicating with our Subscriptions API. Right now, the client only has support for creating a subscription. It remains untested, but it builds.

History
1 package api
3 import (
4 "net/http"
6 "code.secondbit.org/api.hg"
7 "code.secondbit.org/auth.hg"
8 "code.secondbit.org/trout.hg"
9 "code.secondbit.org/uuid.hg"
11 "code.secondbit.org/ducky/subscriptions.hg"
13 "golang.org/x/net/context"
14 )
16 var (
17 ScopeSubscription = auth.Scope{ID: "subscriptions", Name: "Manage Subscriptions", Description: "Read and update your subscription information."}
18 ScopeSubscriptionAdmin = auth.Scope{ID: "subscriptions_admin", Name: "Administer Subscriptions", Description: "Read and update subscription information, bypassing ACL."}
19 )
21 func HandleSubscriptions(router *trout.Router, c context.Context) {
22 router.Endpoint("/subscriptions").Methods("POST", "OPTIONS").Handler(
23 api.CORSMiddleware(api.NegotiateMiddleware(api.ContextWrapper(c, CreateSubscriptionHandler))))
24 router.Endpoint("/subscriptions/{id}").Methods("GET", "OPTIONS").Handler(
25 api.CORSMiddleware(api.NegotiateMiddleware(api.ContextWrapper(c, GetSubscriptionHandler))))
26 router.Endpoint("/subscriptions/{id}").Methods("PATCH", "OPTIONS").Handler(
27 api.CORSMiddleware(api.NegotiateMiddleware(api.ContextWrapper(c, PatchSubscriptionHandler))))
28 }
30 func CreateSubscriptionHandler(w http.ResponseWriter, r *http.Request, c context.Context) {
31 store, err := getSubscriptionStore(c)
32 if err != nil {
33 api.Encode(w, r, http.StatusInternalServerError, Response{Errors: api.ActOfGodError})
34 return
35 }
36 stripe, err := getStripeClient(c)
37 if err != nil {
38 api.Encode(w, r, http.StatusInternalServerError, Response{Errors: api.ActOfGodError})
39 return
40 }
41 if !api.CheckScopes(r, ScopeSubscriptionAdmin.ID) {
42 api.Encode(w, r, http.StatusUnauthorized, Response{Errors: []api.RequestError{{Slug: api.RequestErrAccessDenied}}})
43 return
44 }
45 var req subscriptions.SubscriptionChange
46 err = api.Decode(r, &req)
47 if err != nil {
48 api.Encode(w, r, http.StatusBadRequest, Response{Errors: api.InvalidFormatError})
49 return
50 }
51 // BUG(paddy): Need to validate the request when creating a subscription
52 sub, err := subscriptions.New(req, stripe, store)
53 if err != nil {
54 var rErr api.RequestError
55 var code int
56 switch err {
57 case subscriptions.ErrSubscriptionAlreadyExists:
58 rErr = api.RequestError{Slug: api.RequestErrConflict, Field: "/user_id"}
59 code = http.StatusBadRequest
60 case subscriptions.ErrStripeSubscriptionAlreadyExists:
61 rErr = api.RequestError{Slug: api.RequestErrConflict, Field: "/stripe_token"}
62 code = http.StatusBadRequest
63 default:
64 rErr = api.RequestError{Slug: api.RequestErrActOfGod}
65 code = http.StatusInternalServerError
66 }
67 api.Encode(w, r, code, Response{Errors: []api.RequestError{rErr}})
68 return
69 }
70 resp := Response{Subscriptions: []subscriptions.Subscription{sub}}
71 api.Encode(w, r, http.StatusCreated, resp)
72 }
74 func GetSubscriptionHandler(w http.ResponseWriter, r *http.Request, c context.Context) {
75 store, err := getSubscriptionStore(c)
76 if err != nil {
77 api.Encode(w, r, http.StatusInternalServerError, Response{Errors: api.ActOfGodError})
78 return
79 }
80 vars := trout.RequestVars(r)
81 rawID := vars.Get("id")
82 if rawID == "" {
83 api.Encode(w, r, http.StatusBadRequest, Response{Errors: []api.RequestError{{Slug: api.RequestErrMissing, Param: "id"}}})
84 return
85 }
86 id, err := uuid.Parse(rawID)
87 if err != nil {
88 api.Encode(w, r, http.StatusBadRequest, Response{Errors: []api.RequestError{{Slug: api.RequestErrInvalidFormat, Param: "id"}}})
89 return
90 }
91 if !api.CheckScopes(r, ScopeSubscription.ID) {
92 api.Encode(w, r, http.StatusUnauthorized, Response{Errors: []api.RequestError{{Slug: api.RequestErrAccessDenied}}})
93 return
94 }
95 userID, err := api.AuthUser(r)
96 if err != nil {
97 api.Encode(w, r, http.StatusUnauthorized, Response{Errors: []api.RequestError{{Slug: api.RequestErrAccessDenied}}})
98 return
99 }
100 if !id.Equal(userID) && !api.CheckScopes(r, ScopeSubscriptionAdmin.ID) {
101 api.Encode(w, r, http.StatusUnauthorized, Response{Errors: []api.RequestError{{Slug: api.RequestErrAccessDenied}}})
102 return
103 }
104 subs, err := store.GetSubscriptions([]uuid.ID{id})
105 if err != nil {
106 api.Encode(w, r, http.StatusInternalServerError, Response{Errors: api.ActOfGodError})
107 return
108 }
109 sub, ok := subs[id.String()]
110 if !ok {
111 api.Encode(w, r, http.StatusNotFound, Response{Errors: []api.RequestError{{Slug: api.RequestErrNotFound, Param: "id"}}})
112 return
113 }
114 resp := Response{Subscriptions: []subscriptions.Subscription{sub}}
115 api.Encode(w, r, http.StatusOK, resp)
116 }
118 func PatchSubscriptionHandler(w http.ResponseWriter, r *http.Request, c context.Context) {
119 store, err := getSubscriptionStore(c)
120 if err != nil {
121 api.Encode(w, r, http.StatusInternalServerError, Response{Errors: api.ActOfGodError})
122 return
123 }
124 stripe, err := getStripeClient(c)
125 if err != nil {
126 api.Encode(w, r, http.StatusInternalServerError, Response{Errors: api.ActOfGodError})
127 return
128 }
129 if !api.CheckScopes(r, ScopeSubscription.ID) {
130 api.Encode(w, r, http.StatusUnauthorized, Response{Errors: []api.RequestError{{Slug: api.RequestErrAccessDenied}}})
131 return
132 }
133 userID, err := api.AuthUser(r)
134 if err != nil {
135 api.Encode(w, r, http.StatusUnauthorized, Response{Errors: []api.RequestError{{Slug: api.RequestErrAccessDenied}}})
136 return
137 }
138 vars := trout.RequestVars(r)
139 rawID := vars.Get("id")
140 if rawID == "" {
141 api.Encode(w, r, http.StatusBadRequest, Response{Errors: []api.RequestError{{Slug: api.RequestErrMissing, Param: "id"}}})
142 return
143 }
144 id, err := uuid.Parse(rawID)
145 if err != nil {
146 api.Encode(w, r, http.StatusBadRequest, Response{Errors: []api.RequestError{{Slug: api.RequestErrInvalidFormat, Param: "id"}}})
147 return
148 }
150 var req subscriptions.SubscriptionChange
151 err = api.Decode(r, &req)
152 if err != nil {
153 api.Encode(w, r, http.StatusBadRequest, Response{Errors: api.InvalidFormatError})
154 return
155 }
156 // BUG(paddy): Need to validate the request when updating a subscription
158 // only admin users can update the system-controlled properties
159 changedSysProps := subscriptions.ChangingSystemProperties(req)
160 if len(changedSysProps) > 0 && !api.CheckScopes(r, ScopeSubscriptionAdmin.ID) {
161 errs := make([]api.RequestError, len(changedSysProps))
162 for pos, prop := range changedSysProps {
163 errs[pos] = api.RequestError{Slug: api.RequestErrAccessDenied, Field: prop}
164 }
165 api.Encode(w, r, http.StatusBadRequest, Response{Errors: errs})
166 return
167 }
169 subs, err := store.GetSubscriptions([]uuid.ID{userID})
170 if err != nil {
171 api.Encode(w, r, http.StatusInternalServerError, Response{Errors: api.ActOfGodError})
172 return
173 }
174 sub, ok := subs[userID.String()]
175 if !ok {
176 api.Encode(w, r, http.StatusNotFound, Response{Errors: []api.RequestError{{Slug: api.RequestErrNotFound, Param: "id"}}})
177 return
178 }
179 stripeSub, err := subscriptions.UpdateStripeSubscription(sub.StripeSubscription, req.Plan, req.StripeSource, stripe)
180 if err != nil {
181 api.Encode(w, r, http.StatusInternalServerError, Response{Errors: api.ActOfGodError})
182 return
183 }
184 change := subscriptions.StripeSubscriptionChange(sub, *stripeSub)
185 if change.IsEmpty() {
186 resp := Response{Subscriptions: []subscriptions.Subscription{sub}}
187 api.Encode(w, r, http.StatusOK, resp)
188 return
189 }
190 err = store.UpdateSubscription(id, change)
191 if err != nil {
192 api.Encode(w, r, http.StatusInternalServerError, Response{Errors: api.ActOfGodError})
193 return
194 }
195 sub.ApplyChange(change)
196 resp := Response{Subscriptions: []subscriptions.Subscription{sub}}
197 api.Encode(w, r, http.StatusOK, resp)
198 }