diff --git a/README.md b/README.md
index fa6b460..d5d338f 100644
--- a/README.md
+++ b/README.md
@@ -6,17 +6,18 @@
- [ ] Etsy (WIP)
- [ ] GET ETSY AUTH (WIP)
- [x] move auth state stuff to database (out of cache)
- - [ ] only generate a sign up link IF they click the link on the accounts page
+ - [x] only generate a sign up link IF they click the link on the accounts page
- [ ] get api key approved
- - [o] NEEDS TESTING - Get new access token using refresh token flow
+ - [?] Get new access token using refresh token flow
- [ ] make a FK between the etsy_store_events table and etsy_users table (store_id columns don't match types)
- [ ] Auth0
- [ ] get off dev api key?
- - [ ] Get new access token using refresh token flow
- - [ ] test
- - [ ] Get new refresh token flow
+ - [x] Get new access token using refresh token flow
- [ ] test
- [ ] Social connections login
+ - [ ] automatically clean up access tokens and state when expired
+ - [ ] access tokens
+ - [ ] state
- [ ] Complete this design document?
- [ ] Complete defining this roadmap checklist
- [ ] Website displaying an audit of store events
diff --git a/internal/site/templates/page_bodies/accounts/{acctID}/index.html.tmpl b/internal/site/templates/page_bodies/accounts/{acctID}/index.html.tmpl
index 2314644..8d48c6c 100644
--- a/internal/site/templates/page_bodies/accounts/{acctID}/index.html.tmpl
+++ b/internal/site/templates/page_bodies/accounts/{acctID}/index.html.tmpl
@@ -12,7 +12,7 @@
{{- else }}
{{/* TODO: create this link dynamically, not EVERYTIME THE PAGE IS LOADED */}}
-
+
Link Your Etsy Store!
diff --git a/internal/webhooks/etsy/webhooks.go b/internal/webhooks/etsy/webhooks.go
index 38f1303..b35cfdb 100644
--- a/internal/webhooks/etsy/webhooks.go
+++ b/internal/webhooks/etsy/webhooks.go
@@ -11,99 +11,139 @@ import (
"ruben/inventory2/internal/domains/raw_events"
)
-type Config struct {
- OAuthRedirectURIWithAcctIDParam string
-}
+type (
+ Webhooks struct {
+ cfg Config
+ db *raw_events.Store
+ etsy *etsy.Platform
+ }
+
+ Config struct {
+ OAuthRedirectURIWithAcctIDParam string
+ }
+)
func NewWebhookHandler(
db *raw_events.Store,
platform *etsy.Platform,
cfg Config,
) http.Handler {
+ h := Webhooks{
+ cfg: cfg,
+ db: db,
+ etsy: platform,
+ }
+
mux := http.NewServeMux()
- mux.HandleFunc("POST /test", func(w http.ResponseWriter, r *http.Request) {
- var body json.RawMessage
- if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
- http.Error(w, "Failed to decode body as json: "+err.Error(), 500)
- return
- }
+ mux.HandleFunc("POST /test", h.test)
- ts := time.Now().UTC()
+ mux.HandleFunc("GET "+h.cfg.OAuthRedirectURIWithAcctIDParam, h.redirectURI)
- storeID := "test-store-id"
- var payloadObject struct {
- StoreID string
- }
- if err := json.Unmarshal(body, &payloadObject); err == nil && payloadObject.StoreID != "" {
- storeID = payloadObject.StoreID
- }
-
- err := db.Save(r.Context(), &raw_events.Event{
- Platform: "etsy",
- StoreID: storeID,
- EventID: fmt.Sprint(ts.Unix()),
- EventTimestamp: ts,
- Payload: body,
- })
- if err != nil {
- http.Error(w, "Error occurred saving the body as the event payload: "+err.Error(), 500)
- return
- }
-
- w.WriteHeader(201)
- })
-
- mux.HandleFunc("GET "+cfg.OAuthRedirectURIWithAcctIDParam, func(w http.ResponseWriter, r *http.Request) {
- // get account id for the request
-
- acctID, err := strconv.ParseInt(r.PathValue("acctID"), 10, 64)
- if err != nil || acctID <= 0 {
- w.WriteHeader(http.StatusNotFound)
- return
- }
-
- ctx := r.Context()
-
- q := r.URL.Query()
- state := q.Get("state")
-
- // handle failed, potentially non-consenting, request
-
- if errCode := q.Get("error"); errCode != "" {
- errDesc := q.Get("error_description")
- errURI := q.Get("error_uri")
-
- fmt.Printf(
- "error in obtaining an OAuth Token: error=%s, error_desc=%s, error_uri=%s, account_id=%d\n",
- errCode,
- errDesc,
- errURI,
- acctID,
- )
-
- platform.InvalidateState(ctx, state)
-
- return
- }
-
- // validate the state to prevent CSRF attacks
-
- ok, err := platform.HandleNewAuthCode(ctx, acctID, state, q.Get("code"))
- if err != nil {
- w.WriteHeader(http.StatusForbidden)
- fmt.Println("failed to handle new auth code:", err)
- return
- }
- if !ok {
- w.WriteHeader(http.StatusForbidden)
- return
- }
-
- // TODO: response with a redirect to the user's account page (SUCCESS - new sign up or login)!
-
- http.Redirect(w, r, fmt.Sprintf("/accounts/%d", acctID), http.StatusSeeOther)
- })
+ mux.HandleFunc("GET /{acctID}/new-account-link", h.newAccountLink)
return mux
}
+
+// POST /test
+func (h Webhooks) test(w http.ResponseWriter, r *http.Request) {
+ var body json.RawMessage
+ if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
+ http.Error(w, "Failed to decode body as json: "+err.Error(), 500)
+ return
+ }
+
+ ts := time.Now().UTC()
+
+ storeID := "test-store-id"
+ var payloadObject struct {
+ StoreID string
+ }
+ if err := json.Unmarshal(body, &payloadObject); err == nil && payloadObject.StoreID != "" {
+ storeID = payloadObject.StoreID
+ }
+
+ err := h.db.Save(r.Context(), &raw_events.Event{
+ Platform: "etsy",
+ StoreID: storeID,
+ EventID: fmt.Sprint(ts.Unix()),
+ EventTimestamp: ts,
+ Payload: body,
+ })
+ if err != nil {
+ http.Error(w, "Error occurred saving the body as the event payload: "+err.Error(), 500)
+ return
+ }
+
+ w.WriteHeader(201)
+}
+
+// GET h.cfg.OAuthRedirectURIWithAcctIDParam
+func (h Webhooks) redirectURI(w http.ResponseWriter, r *http.Request) {
+ // get account id for the request
+
+ acctID, err := strconv.ParseInt(r.PathValue("acctID"), 10, 64)
+ if err != nil || acctID <= 0 {
+ w.WriteHeader(http.StatusNotFound)
+ return
+ }
+
+ ctx := r.Context()
+
+ q := r.URL.Query()
+ state := q.Get("state")
+
+ // handle failed, potentially non-consenting, request
+
+ if errCode := q.Get("error"); errCode != "" {
+ errDesc := q.Get("error_description")
+ errURI := q.Get("error_uri")
+
+ fmt.Printf(
+ "error in obtaining an OAuth Token: error=%s, error_desc=%s, error_uri=%s, account_id=%d\n",
+ errCode,
+ errDesc,
+ errURI,
+ acctID,
+ )
+
+ h.etsy.InvalidateState(ctx, state)
+
+ return
+ }
+
+ // validate the state to prevent CSRF attacks
+
+ ok, err := h.etsy.HandleNewAuthCode(ctx, acctID, state, q.Get("code"))
+ if err != nil {
+ w.WriteHeader(http.StatusForbidden)
+ fmt.Println("failed to handle new auth code:", err)
+ return
+ }
+ if !ok {
+ w.WriteHeader(http.StatusForbidden)
+ return
+ }
+
+ // redirect to the user's account page
+
+ http.Redirect(w, r, fmt.Sprintf("/accounts/%d", acctID), http.StatusSeeOther)
+}
+
+// GET /{acctID}/new-account-link
+func (h Webhooks) newAccountLink(w http.ResponseWriter, r *http.Request) {
+ acctIDStr := r.PathValue("acctID")
+ acctID, err := strconv.ParseInt(acctIDStr, 10, 64)
+ if err != nil {
+ http.Error(w, fmt.Sprintf("account %s not found", acctIDStr), http.StatusNotFound)
+ return
+ }
+
+ u, err := h.etsy.GenerateConnectionURLForNewAccount(r.Context(), acctID)
+ if err != nil {
+ http.Error(w, fmt.Sprintf("failed to generate url for account %d: %v", acctID, err), http.StatusInternalServerError)
+ return
+ }
+
+ http.Redirect(w, r, u.String(), http.StatusTemporaryRedirect)
+}