ducky/subscriptions

Paddy 2015-06-30 Parent:36e90e828dd0 Child:c4cfceb2f2fb

5:fe8f092cc149 Go to Latest

ducky/subscriptions/api/subscription_handlers.go

Make subscriptions Kubernetes-ready. Update our .hgignore file to include the docker-ready subscripionsd binary (which differs from the local subscriptionsd binary only in that it's statically-compiled for linux). Add a replication controller for subscriptionsd that will spin up the appropriate pods for us and make sure our Stripe and Subscriptionsd secret volumes are attached, so the pods may configure themselves with that private info. Create a Stripe secret volume with a placeholder for the stripe secret key. Create a Subscriptionsd secret volume with the DSN sent to the base64 encoding of an empty string right now (which means subscriptionsd will store data in memory, but whatever.) Create a subscriptionsd service that will route traffic to the subscriptionsd pods created by the replication controller. Create a minimal Dockerfile to get the subscriptionsd binary running on kubernetes. Add a build-docker helper script that will compile a Docker-ready subscriptionsd binary (by compiling it in a Docker container). Copy a Ubuntu ca-certificates.crt file that we'll inject into our Dockerfile so the Stripe SSL can be resolved. Busybox doesn't have any certificates, by default. Create a wrapper script that will be run in the Docker container to read the secrets from our secret volumes and inject them as environment variables, which is what subscriptionsd understands.

History
1 package api
3 import (
4 "net/http"
6 "code.secondbit.org/api.hg"
7 "code.secondbit.org/ducky/subscriptions.hg"
8 "code.secondbit.org/trout.hg"
9 "code.secondbit.org/uuid.hg"
11 "golang.org/x/net/context"
12 )
14 const (
15 SubscriptionScope = "subscriptions"
16 SubscriptionAdminScope = "subscriptions_admin"
17 )
19 func HandleSubscriptions(router *trout.Router, c context.Context) {
20 router.Endpoint("/subscriptions").Methods("POST", "OPTIONS").Handler(
21 api.CORSMiddleware(api.NegotiateMiddleware(api.ContextWrapper(c, CreateSubscriptionHandler))))
22 router.Endpoint("/subscriptions/{id}").Methods("GET", "OPTIONS").Handler(
23 api.CORSMiddleware(api.NegotiateMiddleware(api.ContextWrapper(c, GetSubscriptionHandler))))
24 }
26 func CreateSubscriptionHandler(w http.ResponseWriter, r *http.Request, c context.Context) {
27 store, err := getSubscriptionStore(c)
28 if err != nil {
29 api.Encode(w, r, http.StatusInternalServerError, Response{Errors: api.ActOfGodError})
30 return
31 }
32 stripe, err := getStripeClient(c)
33 if err != nil {
34 api.Encode(w, r, http.StatusInternalServerError, Response{Errors: api.ActOfGodError})
35 return
36 }
37 if !api.CheckScopes(r, SubscriptionScope) {
38 api.Encode(w, r, http.StatusUnauthorized, Response{Errors: []api.RequestError{{Slug: api.RequestErrAccessDenied}}})
39 return
40 }
41 userID, err := api.AuthUser(r)
42 if err != nil {
43 api.Encode(w, r, http.StatusUnauthorized, Response{Errors: []api.RequestError{{Slug: api.RequestErrAccessDenied}}})
44 return
45 }
46 var req subscriptions.SubscriptionRequest
47 err = api.Decode(r, &req)
48 if err != nil {
49 api.Encode(w, r, http.StatusBadRequest, Response{Errors: api.InvalidFormatError})
50 return
51 }
52 errs := req.Validate(userID, api.CheckScopes(r, SubscriptionAdminScope))
53 if len(errs) != 0 {
54 api.Encode(w, r, http.StatusBadRequest, Response{Errors: errs})
55 return
56 }
57 // TODO: need some way of validating the email they sent actually belongs to them
58 sub, err := subscriptions.New(req, stripe, store)
59 if err != nil {
60 var rErr api.RequestError
61 var code int
62 switch err {
63 case subscriptions.ErrSubscriptionAlreadyExists:
64 rErr = api.RequestError{Slug: api.RequestErrConflict, Field: "/user_id"}
65 code = http.StatusBadRequest
66 case subscriptions.ErrStripeSubscriptionAlreadyExists:
67 rErr = api.RequestError{Slug: api.RequestErrConflict, Field: "/stripe_token"}
68 code = http.StatusBadRequest
69 default:
70 rErr = api.RequestError{Slug: api.RequestErrActOfGod}
71 code = http.StatusInternalServerError
72 }
73 api.Encode(w, r, code, Response{Errors: []api.RequestError{rErr}})
74 return
75 }
76 resp := Response{Subscriptions: []subscriptions.Subscription{sub}}
77 api.Encode(w, r, http.StatusCreated, resp)
78 }
80 func GetSubscriptionHandler(w http.ResponseWriter, r *http.Request, c context.Context) {
81 store, err := getSubscriptionStore(c)
82 if err != nil {
83 api.Encode(w, r, http.StatusInternalServerError, Response{Errors: api.ActOfGodError})
84 return
85 }
86 vars := trout.RequestVars(r)
87 rawID := vars.Get("id")
88 if rawID == "" {
89 api.Encode(w, r, http.StatusBadRequest, Response{Errors: []api.RequestError{{Slug: api.RequestErrMissing, Param: "id"}}})
90 return
91 }
92 id, err := uuid.Parse(rawID)
93 if err != nil {
94 api.Encode(w, r, http.StatusBadRequest, Response{Errors: []api.RequestError{{Slug: api.RequestErrInvalidFormat, Param: "id"}}})
95 return
96 }
97 if !api.CheckScopes(r, SubscriptionScope) {
98 api.Encode(w, r, http.StatusUnauthorized, Response{Errors: []api.RequestError{{Slug: api.RequestErrAccessDenied}}})
99 return
100 }
101 userID, err := api.AuthUser(r)
102 if err != nil {
103 api.Encode(w, r, http.StatusUnauthorized, Response{Errors: []api.RequestError{{Slug: api.RequestErrAccessDenied}}})
104 return
105 }
106 if !id.Equal(userID) && !api.CheckScopes(r, SubscriptionAdminScope) {
107 api.Encode(w, r, http.StatusUnauthorized, Response{Errors: []api.RequestError{{Slug: api.RequestErrAccessDenied}}})
108 return
109 }
110 subs, err := store.GetSubscriptions([]uuid.ID{id})
111 if err != nil {
112 api.Encode(w, r, http.StatusInternalServerError, Response{Errors: api.ActOfGodError})
113 return
114 }
115 sub, ok := subs[id.String()]
116 if !ok {
117 api.Encode(w, r, http.StatusNotFound, Response{Errors: []api.RequestError{{Slug: api.RequestErrNotFound, Param: "id"}}})
118 return
119 }
120 resp := Response{Subscriptions: []subscriptions.Subscription{sub}}
121 api.Encode(w, r, http.StatusOK, resp)
122 }