ducky/subscriptions
2015-06-22
Child:c4cfceb2f2fb
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.
| 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 } |