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
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/api/subscription_handlers.go	Mon Jun 22 18:50:02 2015 -0400
     1.3 @@ -0,0 +1,122 @@
     1.4 +package api
     1.5 +
     1.6 +import (
     1.7 +	"net/http"
     1.8 +
     1.9 +	"code.secondbit.org/api.hg"
    1.10 +	"code.secondbit.org/ducky/subscriptions.hg"
    1.11 +	"code.secondbit.org/trout.hg"
    1.12 +	"code.secondbit.org/uuid.hg"
    1.13 +
    1.14 +	"golang.org/x/net/context"
    1.15 +)
    1.16 +
    1.17 +const (
    1.18 +	SubscriptionScope      = "subscriptions"
    1.19 +	SubscriptionAdminScope = "subscriptions_admin"
    1.20 +)
    1.21 +
    1.22 +func HandleSubscriptions(router *trout.Router, c context.Context) {
    1.23 +	router.Endpoint("/subscriptions").Methods("POST", "OPTIONS").Handler(
    1.24 +		api.CORSMiddleware(api.NegotiateMiddleware(api.ContextWrapper(c, CreateSubscriptionHandler))))
    1.25 +	router.Endpoint("/subscriptions/{id}").Methods("GET", "OPTIONS").Handler(
    1.26 +		api.CORSMiddleware(api.NegotiateMiddleware(api.ContextWrapper(c, GetSubscriptionHandler))))
    1.27 +}
    1.28 +
    1.29 +func CreateSubscriptionHandler(w http.ResponseWriter, r *http.Request, c context.Context) {
    1.30 +	store, err := getSubscriptionStore(c)
    1.31 +	if err != nil {
    1.32 +		api.Encode(w, r, http.StatusInternalServerError, Response{Errors: api.ActOfGodError})
    1.33 +		return
    1.34 +	}
    1.35 +	stripe, err := getStripeClient(c)
    1.36 +	if err != nil {
    1.37 +		api.Encode(w, r, http.StatusInternalServerError, Response{Errors: api.ActOfGodError})
    1.38 +		return
    1.39 +	}
    1.40 +	if !api.CheckScopes(r, SubscriptionScope) {
    1.41 +		api.Encode(w, r, http.StatusUnauthorized, Response{Errors: []api.RequestError{{Slug: api.RequestErrAccessDenied}}})
    1.42 +		return
    1.43 +	}
    1.44 +	userID, err := api.AuthUser(r)
    1.45 +	if err != nil {
    1.46 +		api.Encode(w, r, http.StatusUnauthorized, Response{Errors: []api.RequestError{{Slug: api.RequestErrAccessDenied}}})
    1.47 +		return
    1.48 +	}
    1.49 +	var req subscriptions.SubscriptionRequest
    1.50 +	err = api.Decode(r, &req)
    1.51 +	if err != nil {
    1.52 +		api.Encode(w, r, http.StatusBadRequest, Response{Errors: api.InvalidFormatError})
    1.53 +		return
    1.54 +	}
    1.55 +	errs := req.Validate(userID, api.CheckScopes(r, SubscriptionAdminScope))
    1.56 +	if len(errs) != 0 {
    1.57 +		api.Encode(w, r, http.StatusBadRequest, Response{Errors: errs})
    1.58 +		return
    1.59 +	}
    1.60 +	// TODO: need some way of validating the email they sent actually belongs to them
    1.61 +	sub, err := subscriptions.New(req, stripe, store)
    1.62 +	if err != nil {
    1.63 +		var rErr api.RequestError
    1.64 +		var code int
    1.65 +		switch err {
    1.66 +		case subscriptions.ErrSubscriptionAlreadyExists:
    1.67 +			rErr = api.RequestError{Slug: api.RequestErrConflict, Field: "/user_id"}
    1.68 +			code = http.StatusBadRequest
    1.69 +		case subscriptions.ErrStripeSubscriptionAlreadyExists:
    1.70 +			rErr = api.RequestError{Slug: api.RequestErrConflict, Field: "/stripe_token"}
    1.71 +			code = http.StatusBadRequest
    1.72 +		default:
    1.73 +			rErr = api.RequestError{Slug: api.RequestErrActOfGod}
    1.74 +			code = http.StatusInternalServerError
    1.75 +		}
    1.76 +		api.Encode(w, r, code, Response{Errors: []api.RequestError{rErr}})
    1.77 +		return
    1.78 +	}
    1.79 +	resp := Response{Subscriptions: []subscriptions.Subscription{sub}}
    1.80 +	api.Encode(w, r, http.StatusCreated, resp)
    1.81 +}
    1.82 +
    1.83 +func GetSubscriptionHandler(w http.ResponseWriter, r *http.Request, c context.Context) {
    1.84 +	store, err := getSubscriptionStore(c)
    1.85 +	if err != nil {
    1.86 +		api.Encode(w, r, http.StatusInternalServerError, Response{Errors: api.ActOfGodError})
    1.87 +		return
    1.88 +	}
    1.89 +	vars := trout.RequestVars(r)
    1.90 +	rawID := vars.Get("id")
    1.91 +	if rawID == "" {
    1.92 +		api.Encode(w, r, http.StatusBadRequest, Response{Errors: []api.RequestError{{Slug: api.RequestErrMissing, Param: "id"}}})
    1.93 +		return
    1.94 +	}
    1.95 +	id, err := uuid.Parse(rawID)
    1.96 +	if err != nil {
    1.97 +		api.Encode(w, r, http.StatusBadRequest, Response{Errors: []api.RequestError{{Slug: api.RequestErrInvalidFormat, Param: "id"}}})
    1.98 +		return
    1.99 +	}
   1.100 +	if !api.CheckScopes(r, SubscriptionScope) {
   1.101 +		api.Encode(w, r, http.StatusUnauthorized, Response{Errors: []api.RequestError{{Slug: api.RequestErrAccessDenied}}})
   1.102 +		return
   1.103 +	}
   1.104 +	userID, err := api.AuthUser(r)
   1.105 +	if err != nil {
   1.106 +		api.Encode(w, r, http.StatusUnauthorized, Response{Errors: []api.RequestError{{Slug: api.RequestErrAccessDenied}}})
   1.107 +		return
   1.108 +	}
   1.109 +	if !id.Equal(userID) && !api.CheckScopes(r, SubscriptionAdminScope) {
   1.110 +		api.Encode(w, r, http.StatusUnauthorized, Response{Errors: []api.RequestError{{Slug: api.RequestErrAccessDenied}}})
   1.111 +		return
   1.112 +	}
   1.113 +	subs, err := store.GetSubscriptions([]uuid.ID{id})
   1.114 +	if err != nil {
   1.115 +		api.Encode(w, r, http.StatusInternalServerError, Response{Errors: api.ActOfGodError})
   1.116 +		return
   1.117 +	}
   1.118 +	sub, ok := subs[id.String()]
   1.119 +	if !ok {
   1.120 +		api.Encode(w, r, http.StatusNotFound, Response{Errors: []api.RequestError{{Slug: api.RequestErrNotFound, Param: "id"}}})
   1.121 +		return
   1.122 +	}
   1.123 +	resp := Response{Subscriptions: []subscriptions.Subscription{sub}}
   1.124 +	api.Encode(w, r, http.StatusOK, resp)
   1.125 +}