ducky/subscriptions

Paddy 2015-06-22 Child:c4cfceb2f2fb

4:36e90e828dd0 Go to Latest

ducky/subscriptions/api/subscription_handlers.go

Add an API and subscriptionsd . Create a barebones implementation of the API, including only methods to create a Subscription and retrieve the Subscription associated with a user. Also create a subscriptiond service that will bootstrap the service and stores, and get everything stood up.

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