Skip to content

Commit

Permalink
Merge pull request #184 from netlify/feature/refresh-downloads-on-demand
Browse files Browse the repository at this point in the history
Refresh downloads on demand
  • Loading branch information
mraerino authored Sep 10, 2019
2 parents af8d649 + ef207c0 commit 1352182
Show file tree
Hide file tree
Showing 7 changed files with 308 additions and 79 deletions.
5 changes: 4 additions & 1 deletion api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,10 @@ func (a *API) orderRoutes(r *router) {
r.With(addGetBody).Post("/", a.PaymentCreate)
})

r.Get("/downloads", a.DownloadList)
r.Route("/downloads", func(r *router) {
r.Get("/", a.DownloadList)
r.Post("/refresh", a.DownloadRefresh)
})
r.Get("/receipt", a.ReceiptView)
r.Post("/receipt", a.ResendOrderReceipt)
})
Expand Down
43 changes: 42 additions & 1 deletion api/download.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,6 @@ func (a *API) DownloadList(w http.ResponseWriter, r *http.Request) error {
ctx := r.Context()
orderID := gcontext.GetOrderID(ctx)
log := getLogEntry(r)
claims := gcontext.GetClaims(ctx)

order := &models.Order{}
if orderID != "" {
Expand Down Expand Up @@ -114,6 +113,7 @@ func (a *API) DownloadList(w http.ResponseWriter, r *http.Request) error {
if order != nil {
query = query.Where(orderTable+".id = ?", order.ID)
} else {
claims := gcontext.GetClaims(ctx)
query = query.Where(orderTable+".user_id = ?", claims.Subject)
}

Expand All @@ -130,3 +130,44 @@ func (a *API) DownloadList(w http.ResponseWriter, r *http.Request) error {
log.WithField("download_count", len(downloads)).Debugf("Successfully retrieved %d downloads", len(downloads))
return sendJSON(w, http.StatusOK, downloads)
}

// DownloadRefresh makes sure downloads are up to date
func (a *API) DownloadRefresh(w http.ResponseWriter, r *http.Request) error {
ctx := r.Context()
orderID := gcontext.GetOrderID(ctx)
config := gcontext.GetConfig(ctx)
log := getLogEntry(r)

order := &models.Order{}
if orderID == "" {
return badRequestError("Order id missing")
}

query := a.db.Where("id = ?", orderID).
Preload("LineItems").
Preload("Downloads")
if result := query.First(order); result.Error != nil {
if result.RecordNotFound() {
return notFoundError("Download order not found")
}
return internalServerError("Error during database query").WithInternalError(result.Error)
}

if !hasOrderAccess(ctx, order) {
return unauthorizedError("You don't have permission to access this order")
}

if order.PaymentState != models.PaidState {
return unauthorizedError("This order has not been completed yet")
}

if err := order.UpdateDownloads(config, log); err != nil {
return internalServerError("Error during updating downloads").WithInternalError(err)
}

if result := a.db.Save(order); result.Error != nil {
return internalServerError("Error during saving order").WithInternalError(result.Error)
}

return sendJSON(w, http.StatusOK, map[string]string{})
}
71 changes: 71 additions & 0 deletions api/download_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package api

import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"

"github.com/netlify/gocommerce/models"
Expand All @@ -19,3 +23,70 @@ func TestDownloadList(t *testing.T) {
assert.Len(t, downloads, 1)
})
}

func currentDownloads(test *RouteTest) []models.Download {
recorder := test.TestEndpoint(http.MethodGet, "/downloads", nil, test.Data.testUserToken)

downloads := []models.Download{}
extractPayload(test.T, http.StatusOK, recorder, &downloads)
return downloads
}

type DownloadMeta struct {
Title string `json:"title"`
URL string `json:"url"`
}

func startTestSiteWithDownloads(t *testing.T, downloads []*DownloadMeta) *httptest.Server {
downloadsList, err := json.Marshal(downloads)
assert.NoError(t, err)
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/i/believe/i/can/fly":
fmt.Fprintf(w, productMetaFrame(`
{"sku": "123-i-can-fly-456", "downloads": %s}`),
string(downloadsList),
)
}
}))
}

func TestDownloadRefresh(t *testing.T) {
test := NewRouteTest(t)
downloadsBefore := currentDownloads(test)

testSite := startTestSiteWithDownloads(t, []*DownloadMeta{
&DownloadMeta{
Title: "Updated Download",
URL: "/my/special/new/url",
},
})
defer testSite.Close()
test.Config.SiteURL = testSite.URL

url := fmt.Sprintf("/orders/%s/downloads/refresh", test.Data.firstOrder.ID)
recorder := test.TestEndpoint(http.MethodPost, url, nil, test.Data.testUserToken)
body, err := ioutil.ReadAll(recorder.Body)
assert.NoError(t, err)
assert.Equal(t, http.StatusOK, recorder.Code, "Failure: %s", string(body))

downloadsAfter := currentDownloads(test)

assert.Equal(t, len(downloadsBefore)+1, len(downloadsAfter))
exists := false
for _, download := range downloadsAfter {
found := false
for _, prev := range downloadsBefore {
if download.ID == prev.ID {
found = true
break
}
}
if !found {
assert.Equal(t, "/my/special/new/url", download.URL)
assert.Equal(t, "123-i-can-fly-456", download.Sku)
exists = true
}
}
assert.True(t, exists)
}
59 changes: 10 additions & 49 deletions api/order.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"net/http"
"sync"

"github.com/PuerkitoBio/goquery"
"github.com/go-chi/chi"
"github.com/jinzhu/gorm"
"github.com/mattes/vat"
Expand Down Expand Up @@ -641,6 +640,13 @@ func (a *API) createLineItems(ctx context.Context, tx *gorm.DB, order *models.Or
Path: orderItem.Path,
OrderID: order.ID,
}

for _, addon := range orderItem.Addons {
lineItem.AddonItems = append(lineItem.AddonItems, &models.AddonItem{
Sku: addon.Sku,
})
}

order.LineItems = append(order.LineItems, lineItem)
sem <- 1
wg.Add(1)
Expand All @@ -654,7 +660,7 @@ func (a *API) createLineItems(ctx context.Context, tx *gorm.DB, order *models.Or
return
}

if err := a.processLineItem(ctx, order, item, orderItem); err != nil {
if err := a.processLineItem(ctx, order, item); err != nil {
sharedErr.setError(err)
}
}(lineItem, orderItem)
Expand Down Expand Up @@ -735,56 +741,11 @@ func (a *API) processAddress(tx *gorm.DB, order *models.Order, name string, addr
return address, nil
}

func (a *API) processLineItem(ctx context.Context, order *models.Order, item *models.LineItem, orderItem *orderLineItem) error {
func (a *API) processLineItem(ctx context.Context, order *models.Order, item *models.LineItem) error {
config := gcontext.GetConfig(ctx)
jwtClaims := gcontext.GetClaimsAsMap(ctx)
resp, err := a.httpClient.Get(config.SiteURL + item.Path)
if err != nil {
return err
}
defer resp.Body.Close()

doc, err := goquery.NewDocumentFromResponse(resp)
if err != nil {
return err
}

metaTag := doc.Find(".gocommerce-product")
if metaTag.Length() == 0 {
return fmt.Errorf("No script tag with class gocommerce-product tag found for '%v'", item.Title)
}
metaProducts := []*models.LineItemMetadata{}
var parsingErr error
metaTag.EachWithBreak(func(_ int, tag *goquery.Selection) bool {
meta := &models.LineItemMetadata{}
parsingErr = json.Unmarshal([]byte(tag.Text()), meta)
if parsingErr != nil {
return false
}
metaProducts = append(metaProducts, meta)
return true
})
if parsingErr != nil {
return fmt.Errorf("Error parsing product metadata: %v", parsingErr)
}

if len(metaProducts) == 1 && item.Sku == "" {
item.Sku = metaProducts[0].Sku
}

for _, meta := range metaProducts {
if meta.Sku == item.Sku {
for _, addon := range orderItem.Addons {
item.AddonItems = append(item.AddonItems, &models.AddonItem{
Sku: addon.Sku,
})
}

return item.Process(jwtClaims, order, meta)
}
}

return fmt.Errorf("No product Sku from path matched: %v", item.Sku)
return item.Process(config, jwtClaims, order)
}

func orderQuery(db *gorm.DB) *gorm.DB {
Expand Down
47 changes: 23 additions & 24 deletions api/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -418,35 +418,34 @@ func signInstanceRequest(req *http.Request, instanceID string, jwtSecret string)
// TEST SITE
// ------------------------------------------------------------------------------------------------

func productMetaFrame(meta string) string {
return fmt.Sprintf(`<!doctype html>
<html>
<head><title>Test Product</title></head>
<body>
<script class="gocommerce-product">
%s
</script>
</body>
</html>`,
meta)
}

func handleTestProducts(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/simple-product":
fmt.Fprintln(w, `<!doctype html>
<html>
<head><title>Test Product</title></head>
<body>
<script class="gocommerce-product">
{"sku": "product-1", "title": "Product 1", "type": "Book", "prices": [
{"amount": "9.99", "currency": "USD"}
]}
</script>
</body>
</html>`)
fmt.Fprintln(w, productMetaFrame(`
{"sku": "product-1", "title": "Product 1", "type": "Book", "prices": [
{"amount": "9.99", "currency": "USD"}
]}`))
case "/bundle-product":
fmt.Fprintln(w, `<!doctype html>
<html>
<head><title>Test Product</title></head>
<body>
<script class="gocommerce-product">
{"sku": "product-1", "title": "Product 1", "type": "Book", "prices": [
{"amount": "9.99", "currency": "USD", "items": [
{"amount": "7.00", "type": "Book"},
{"amount": "2.99", "type": "E-Book"}
]}
fmt.Fprintln(w, productMetaFrame(`
{"sku": "product-1", "title": "Product 1", "type": "Book", "prices": [
{"amount": "9.99", "currency": "USD", "items": [
{"amount": "7.00", "type": "Book"},
{"amount": "2.99", "type": "E-Book"}
]}
</script>
</body>
</html>`)
]}`))
default:
w.WriteHeader(http.StatusNotFound)
}
Expand Down
Loading

0 comments on commit 1352182

Please sign in to comment.