package etsy import ( "encoding/json" "fmt" "net/http" "strconv" "time" "ruben/inventory2/internal/domains/platforms/etsy" "ruben/inventory2/internal/domains/raw_events" ) 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", h.test) mux.HandleFunc("GET "+h.cfg.OAuthRedirectURIWithAcctIDParam, h.redirectURI) 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) }