Skip to content
This repository was archived by the owner on Mar 28, 2023. It is now read-only.

Commit 007a383

Browse files
authored
Merge pull request #1980 from OpenBazaar/1976-normalize-legacy-listings
(#1976) Normalize legacy listings via GET /ob/listings/...
2 parents 9b96160 + 84490a3 commit 007a383

File tree

7 files changed

+310
-17
lines changed

7 files changed

+310
-17
lines changed

api/jsonapi.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1496,6 +1496,11 @@ func (i *jsonAPIHandler) GETListing(w http.ResponseWriter, r *http.Request) {
14961496
log.Warningf("updating coupons for listing (%s): %s", rsl.GetSlug(), err.Error())
14971497
}
14981498

1499+
if err := rsl.Normalize(); err != nil {
1500+
ErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("normalizing listing: %s", err.Error()))
1501+
return
1502+
}
1503+
14991504
out, err := rsl.MarshalJSON()
15001505
if err != nil {
15011506
ErrorResponse(w, http.StatusInternalServerError, err.Error())
@@ -1537,7 +1542,14 @@ func (i *jsonAPIHandler) GETListing(w http.ResponseWriter, r *http.Request) {
15371542
}
15381543
sl.Hash = hash
15391544

1540-
out, err := repo.NewSignedListingFromProtobuf(sl).MarshalJSON()
1545+
rsl := repo.NewSignedListingFromProtobuf(sl)
1546+
1547+
if err := rsl.Normalize(); err != nil {
1548+
ErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("normalizing listing: %s", err.Error()))
1549+
return
1550+
}
1551+
1552+
out, err := rsl.MarshalJSON()
15411553
if err != nil {
15421554
ErrorResponse(w, http.StatusInternalServerError, err.Error())
15431555
return

core/order.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,21 @@ func (n *OpenBazaarNode) GetOrder(orderID string) (*pb.OrderRespApi, error) {
6161
}
6262
isSale = true
6363
}
64+
65+
for i, l := range contract.VendorListings {
66+
repoListing, err := repo.NewListingFromProtobuf(l)
67+
if err != nil {
68+
log.Errorf("failed getting contract listing: %s", err.Error())
69+
return nil, err
70+
}
71+
normalizedListing, err := repoListing.Normalize()
72+
if err != nil {
73+
log.Errorf("failed converting contract listing to v5 schema: %s", err.Error())
74+
return nil, err
75+
}
76+
contract.VendorListings[i] = normalizedListing.GetProtobuf()
77+
}
78+
6479
resp := new(pb.OrderRespApi)
6580
resp.Contract = contract
6681
resp.Funded = funded

repo/db/sales.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -315,9 +315,11 @@ func (s *SalesDB) GetByOrderId(orderId string) (*pb.RicardianContract, pb.OrderS
315315
return nil, pb.OrderState(0), false, nil, false, nil, fmt.Errorf("validating payment coin: %s", err.Error())
316316
}
317317
var records []*wallet.TransactionRecord
318-
err = json.Unmarshal(serializedTransactions, &records)
319-
if err != nil {
320-
log.Error(err)
318+
if len(serializedTransactions) > 0 {
319+
err = json.Unmarshal(serializedTransactions, &records)
320+
if err != nil {
321+
return nil, pb.OrderState(0), false, nil, false, nil, fmt.Errorf("unmarshal purchase transactions: %s", err.Error())
322+
}
321323
}
322324
cc := def.CurrencyCode()
323325
return rc, pb.OrderState(stateInt), funded, records, read, &cc, nil

repo/listing.go

Lines changed: 105 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -138,14 +138,16 @@ func (*Listing) ProtoMessage() {}
138138

139139
// NewListingFromProtobuf - return Listing from pb.Listing
140140
func NewListingFromProtobuf(l *pb.Listing) (*Listing, error) {
141-
if l.Metadata.Version == 0 {
142-
l.Metadata.Version = ListingVersion
141+
clonedListing := proto.Clone(l).(*pb.Listing)
142+
143+
if clonedListing.Metadata.Version == 0 {
144+
clonedListing.Metadata.Version = ListingVersion
143145
}
144-
if l.Metadata.EscrowTimeoutHours == 0 {
145-
l.Metadata.EscrowTimeoutHours = DefaultEscrowTimeout
146+
if clonedListing.Metadata.EscrowTimeoutHours == 0 {
147+
clonedListing.Metadata.EscrowTimeoutHours = DefaultEscrowTimeout
146148
}
147149
return &Listing{
148-
listingProto: l,
150+
listingProto: clonedListing,
149151
}, nil
150152
}
151153

@@ -376,6 +378,59 @@ func ExtractIDFromListing(data []byte) (*pb.ID, error) {
376378
return vendorPlay, nil
377379
}
378380

381+
// Normalize converts legacy schema listing data from other users on the network
382+
// to fit the latest schema for consumption via the API for local use. NOTE: Legacy
383+
// nodes do not understand the latest schema. As such, normalized listings must not
384+
// be used as part of the RicardianContract and must be serialized and used as they
385+
// were provided by the originating node.
386+
func (l *Listing) Normalize() (*Listing, error) {
387+
if l == nil {
388+
return nil, errors.New("nil listing cannot be normalized")
389+
}
390+
391+
if l.GetVersion() == ListingVersion {
392+
return l, nil
393+
}
394+
395+
nl, err := NewListingFromProtobuf(l.listingProto)
396+
if err != nil {
397+
return nil, fmt.Errorf("creating listing clone: %s", err.Error())
398+
}
399+
400+
nlp := nl.GetProtobuf()
401+
nlp.Metadata.Version = ListingVersion
402+
403+
if p, err := l.GetPrice(); err != nil {
404+
return nil, fmt.Errorf("get price: %s", err.Error())
405+
} else {
406+
nlp.Item.BigPrice = p.Amount.String()
407+
nlp.Item.PriceCurrency = &pb.CurrencyDefinition{
408+
Code: p.Currency.Code.String(),
409+
Divisibility: uint32(p.Currency.Divisibility),
410+
}
411+
}
412+
413+
if ss, err := l.GetSkus(); err != nil {
414+
return nil, fmt.Errorf("get skus: %s", err.Error())
415+
} else {
416+
nlp.Item.Skus = ss
417+
}
418+
419+
if sos, err := l.GetShippingOptions(); err != nil {
420+
return nil, fmt.Errorf("get shipping options: %s", err.Error())
421+
} else {
422+
nlp.ShippingOptions = sos
423+
}
424+
425+
if cs, err := l.GetCoupons(); err != nil {
426+
return nil, fmt.Errorf("get coupons: %s", err.Error())
427+
} else {
428+
nlp.Coupons = cs.GetProtobuf()
429+
}
430+
431+
return nl, nil
432+
}
433+
379434
// GetProtobuf returns the current state of pb.Listing managed by Listing
380435
func (l *Listing) GetProtobuf() *pb.Listing {
381436
return l.listingProto
@@ -695,18 +750,18 @@ func (l *Listing) GetImages() []*ListingImage {
695750

696751
// GetSkus returns the listing SKUs
697752
func (l *Listing) GetSkus() ([]*pb.Listing_Item_Sku, error) {
753+
var ss = make([]*pb.Listing_Item_Sku, len(l.listingProto.Item.Skus))
754+
for i, s := range l.listingProto.Item.Skus {
755+
ss[i] = proto.Clone(s).(*pb.Listing_Item_Sku)
756+
}
698757
switch l.GetVersion() {
699758
case 3, 4:
700-
for _, sku := range l.listingProto.Item.Skus {
701-
surcharge := new(big.Int).SetInt64(sku.Surcharge)
702-
quantity := new(big.Int).SetInt64(sku.Quantity)
703-
sku.BigSurcharge = surcharge.String()
704-
sku.BigQuantity = quantity.String()
705-
sku.Quantity = 0
706-
sku.Surcharge = 0
759+
for _, sku := range ss {
760+
sku.BigSurcharge = big.NewInt(sku.Surcharge).String()
761+
sku.BigQuantity = big.NewInt(sku.Quantity).String()
707762
}
708763
}
709-
return l.listingProto.Item.Skus, nil
764+
return ss, nil
710765
}
711766

712767
//GetLanguage return listing's language
@@ -858,6 +913,24 @@ func (l *Listing) GetCoupons() (ListingCoupons, error) {
858913
// ListingCoupons is a set of listing coupons
859914
type ListingCoupons []*ListingCoupon
860915

916+
// GetProtobuf converts ListingCoupons into its protobuf representation
917+
func (cs ListingCoupons) GetProtobuf() []*pb.Listing_Coupon {
918+
var cspb = make([]*pb.Listing_Coupon, len(cs))
919+
for i, c := range cs {
920+
cspb[i] = &pb.Listing_Coupon{
921+
Title: c.GetTitle(),
922+
PercentDiscount: c.GetPercentOff(),
923+
BigPriceDiscount: c.GetAmountOff().Amount.String(),
924+
}
925+
if hash, err := c.GetRedemptionHash(); err == nil {
926+
cspb[i].Code = &pb.Listing_Coupon_Hash{Hash: hash}
927+
} else if code, err := c.GetRedemptionCode(); err == nil {
928+
cspb[i].Code = &pb.Listing_Coupon_DiscountCode{DiscountCode: code}
929+
}
930+
}
931+
return cspb
932+
}
933+
861934
// ListingCoupon represents an coupon which can be applied to a listing for a discount
862935
type ListingCoupon struct {
863936
listing *Listing
@@ -932,6 +1005,25 @@ func (c *ListingCoupon) updateProtoHash(hash string) error {
9321005
return errors.New("unable to update missing coupon proto")
9331006
}
9341007

1008+
// GetShippingOptions returns all shipping options
1009+
func (l *Listing) GetShippingOptions() ([]*pb.Listing_ShippingOption, error) {
1010+
var so = make([]*pb.Listing_ShippingOption, len(l.listingProto.ShippingOptions))
1011+
for i, s := range l.listingProto.ShippingOptions {
1012+
so[i] = proto.Clone(s).(*pb.Listing_ShippingOption)
1013+
}
1014+
switch l.GetVersion() {
1015+
case 3, 4:
1016+
for _, o := range so {
1017+
for _, s := range o.Services {
1018+
s.BigPrice = big.NewInt(int64(s.Price)).String()
1019+
s.BigAdditionalItemPrice = big.NewInt(int64(s.AdditionalItemPrice)).String()
1020+
}
1021+
}
1022+
}
1023+
return so, nil
1024+
1025+
}
1026+
9351027
// GetShippingRegions returns all region strings for the defined shipping
9361028
// services
9371029
func (l *Listing) GetShippingRegions() ([]string, []string) {

repo/listing_test.go

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ package repo_test
22

33
import (
44
"bytes"
5+
"fmt"
56
"math/big"
67
"testing"
78

9+
"github.com/OpenBazaar/openbazaar-go/pb"
810
"github.com/OpenBazaar/openbazaar-go/repo"
911
"github.com/OpenBazaar/openbazaar-go/test/factory"
1012
)
@@ -209,5 +211,104 @@ func TestListingFromProtobuf(t *testing.T) {
209211
if !bytes.Equal(subject.VendorID.BitcoinSig, actual.GetVendorID().BitcoinSignature()) {
210212
t.Errorf("expected refund policy to be (%s), but was (%s)", subject.VendorID.BitcoinSig, actual.GetVendorID().BitcoinSignature())
211213
}
214+
}
215+
216+
func TestV4PhysicalGoodDataNormalizesToLatestSchema(t *testing.T) {
217+
var (
218+
expectedPrice uint64 = 100000
219+
expectedPriceCurrency = "EUR"
220+
expectedSkuSurcharge int64 = 200
221+
expectedSkuQuantity int64 = 12
222+
expectedShippingPrice uint64 = 30
223+
expectedCouponDiscount uint64 = 50
224+
v4Proto = &pb.Listing{
225+
Metadata: &pb.Listing_Metadata{
226+
Version: 4,
227+
ContractType: pb.Listing_Metadata_PHYSICAL_GOOD,
228+
PricingCurrency: expectedPriceCurrency,
229+
},
230+
Item: &pb.Listing_Item{
231+
Price: expectedPrice,
232+
Skus: []*pb.Listing_Item_Sku{
233+
{
234+
Surcharge: expectedSkuSurcharge,
235+
Quantity: expectedSkuQuantity,
236+
},
237+
},
238+
},
239+
ShippingOptions: []*pb.Listing_ShippingOption{
240+
{
241+
Services: []*pb.Listing_ShippingOption_Service{
242+
{
243+
Price: expectedShippingPrice,
244+
AdditionalItemPrice: expectedShippingPrice + 1,
245+
},
246+
},
247+
},
248+
},
249+
Coupons: []*pb.Listing_Coupon{
250+
{
251+
PriceDiscount: expectedCouponDiscount,
252+
},
253+
},
254+
}
255+
)
256+
257+
l, err := repo.NewListingFromProtobuf(v4Proto)
258+
if err != nil {
259+
t.Fatal(err)
260+
}
261+
262+
nl, err := l.Normalize()
263+
if err != nil {
264+
t.Fatal(err)
265+
}
266+
267+
nlp := nl.GetProtobuf()
268+
if v := nlp.Metadata.GetVersion(); v != repo.ListingVersion {
269+
t.Errorf("expected version to be (%d), but was (%d)", repo.ListingVersion, v)
270+
}
271+
272+
if p := nlp.Item.BigPrice; p != fmt.Sprintf("%d", expectedPrice) {
273+
t.Errorf("expected price to be (%d), but was (%s)", expectedPrice, p)
274+
}
212275

276+
if pc := nlp.Item.PriceCurrency; pc == nil {
277+
t.Error("expected to have pricing currency set, but was nil")
278+
} else {
279+
if pcc := pc.Code; pcc != expectedPriceCurrency {
280+
t.Errorf("expected price currency to be (%s), but was (%s)", expectedPriceCurrency, pcc)
281+
}
282+
}
283+
284+
if s := nlp.Item.Skus[0]; s == nil {
285+
t.Error("expected sku to be present, but was nil")
286+
} else {
287+
if ss := s.BigSurcharge; ss != fmt.Sprintf("%d", expectedSkuSurcharge) {
288+
t.Errorf("expected surcharge to be (%d), but was (%s)", expectedSkuSurcharge, ss)
289+
}
290+
if sq := s.BigQuantity; sq != fmt.Sprintf("%d", expectedSkuQuantity) {
291+
t.Errorf("expected quantity to be (%d), but was (%s)", expectedSkuQuantity, sq)
292+
}
293+
}
294+
295+
if s := nlp.ShippingOptions[0]; s == nil {
296+
t.Error("expected shipping options to be present, but was nil")
297+
} else {
298+
if ss := s.Services[0]; ss == nil {
299+
t.Error("expected shipping option services to be present, but was nil")
300+
} else {
301+
if ssp := ss.BigPrice; ssp != fmt.Sprintf("%d", expectedShippingPrice) {
302+
t.Errorf("expected shipping option price to be (%d), but was (%s)", expectedShippingPrice, ssp)
303+
}
304+
}
305+
}
306+
307+
if c := nlp.Coupons[0]; c == nil {
308+
t.Error("expected coupon to be present, but was nil")
309+
} else {
310+
if cd := c.BigPriceDiscount; cd != fmt.Sprintf("%d", expectedCouponDiscount) {
311+
t.Errorf("expected coupon discount to be (%d), but was (%s)", expectedCouponDiscount, cd)
312+
}
313+
}
213314
}

repo/signed_listing.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,19 @@ func (l *SignedListing) Reset() { *l = SignedListing{} }
4343
func (l *SignedListing) String() string { return proto.CompactTextString(l) }
4444
func (*SignedListing) ProtoMessage() {}
4545

46+
// Normalize is a helper method which will mutate the listing protobuf
47+
// in-place but maintain the original signature for external verification
48+
// purposes.
49+
func (l *SignedListing) Normalize() error {
50+
nl, err := l.GetListing().Normalize()
51+
if err != nil {
52+
return err
53+
}
54+
55+
l.signedListingProto.Listing = nl.listingProto
56+
return nil
57+
}
58+
4659
// GetListing returns the underlying repo.Listing object
4760
func (l SignedListing) GetListing() *Listing {
4861
return &Listing{

0 commit comments

Comments
 (0)