Skip to content

Commit

Permalink
Merge pull request #817 from mesg-foundation/feature/command-marketplace
Browse files Browse the repository at this point in the history
Add command marketplace
  • Loading branch information
antho1404 authored Mar 24, 2019
2 parents c43574b + bf4f214 commit f07a946
Show file tree
Hide file tree
Showing 18 changed files with 969 additions and 180 deletions.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,3 @@ node_modules
# do not store vendor dir
vendor
tmp-systemservices
node_modules
13 changes: 12 additions & 1 deletion commands/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ type ServiceExecutor interface {
ServiceDeleteAll(deleteData bool) error
ServiceDelete(deleteData bool, ids ...string) error
ServiceDeploy(path string, env map[string]string, statuses chan provider.DeployStatus) (sid string, hash string, validationError, err error)
ServicePublishDefinitionFile(path string) (string, error)
ServiceListenEvents(id, eventFilter string) (chan *coreapi.EventData, chan error, error)
ServiceListenResults(id, taskFilter, outputFilter string, tagFilters []string) (chan *coreapi.ResultData, chan error, error)
ServiceLogs(id string, dependencies ...string) (logs []*provider.Log, closer func(), errC chan error, err error)
Expand All @@ -39,6 +38,17 @@ type ServiceExecutor interface {
ServiceInitDownloadTemplate(t *servicetemplate.Template, dst string) error
}

// MarketplaceExecutor is an interface that handles marketplace commands.
type MarketplaceExecutor interface {
CreateManifest(path string, hash string) (provider.MarketplaceManifestData, error)
UploadServiceFiles(path string, manifest provider.MarketplaceManifestData) (protocol string, source string, err error)
PublishServiceVersion(sid, manifest, manifestProtocol, from string) (provider.Transaction, error)
CreateServiceOffer(sid string, price string, duration string, from string) (provider.Transaction, error)
Purchase(sid, offerIndex, from string) ([]provider.Transaction, error)
SendSignedTransaction(signedTransaction string) (provider.TransactionReceipt, error)
GetService(sid string) (provider.MarketplaceService, error)
}

// WalletExecutor is an interface that handles wallet commands.
type WalletExecutor interface {
List() ([]string, error)
Expand All @@ -54,6 +64,7 @@ type WalletExecutor interface {
type Executor interface {
RootExecutor
ServiceExecutor
MarketplaceExecutor
WalletExecutor
}

Expand Down
106 changes: 106 additions & 0 deletions commands/marketplace_create_offer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package commands

import (
"fmt"
"strings"

"github.com/mesg-foundation/core/commands/provider"
"github.com/mesg-foundation/core/utils/pretty"
"github.com/spf13/cobra"
survey "gopkg.in/AlecAivazis/survey.v1"
)

type marketplaceCreateOfferCmd struct {
baseMarketplaceCmd

service provider.MarketplaceService
price string // in MESG token
duration string

e Executor
}

func newMarketplaceCreateOfferCmd(e Executor) *marketplaceCreateOfferCmd {
c := &marketplaceCreateOfferCmd{e: e}
c.cmd = newCommand(&cobra.Command{
Use: "create-offer",
Short: "Create a new offer on a service on the MESG Marketplace",
Example: `mesg-core marketplace create-offer SID
mesg-core marketplace create-offer SID --price 10 --duration 3600`,
PreRunE: c.preRunE,
RunE: c.runE,
Args: cobra.MinimumNArgs(1),
})
c.setupFlags()
c.cmd.Flags().StringVarP(&c.price, "price", "", c.price, "Price (in MESG token) of the offer to create")
c.cmd.Flags().StringVarP(&c.duration, "duration", "", c.duration, "Duration (in second) of the offer to create")
return c
}

func (c *marketplaceCreateOfferCmd) preRunE(cmd *cobra.Command, args []string) error {
var (
err error
)

if err := c.askAccountAndPassphrase(); err != nil {
return err
}

if c.price == "" {
if err := askInput("Enter the price (in MESG Token) of the offer", &c.price); err != nil {
return err
}
}
// if c.price < 0 {
// return fmt.Errorf("Price cannot be negative")
// }
if c.duration == "" {
if err := askInput("Enter the duration (in second) of the offer", &c.duration); err != nil {
return err
}
}

pretty.Progress("Getting service data...", func() {
c.service, err = c.e.GetService(args[0])
})
if err != nil {
return err
}

if !strings.EqualFold(c.service.Owner, c.account) {
return fmt.Errorf("the service's owner %q is different than the specified account", c.service.Owner)
}

var confirmed bool
if err := survey.AskOne(&survey.Confirm{
Message: fmt.Sprintf("Are you sure to create offer on service %q with price %q and duration %q?", args[0], c.price, c.duration),
}, &confirmed, nil); err != nil {
return err
}
if !confirmed {
return fmt.Errorf("cancelled")
}

return nil
}

func (c *marketplaceCreateOfferCmd) runE(cmd *cobra.Command, args []string) error {
var (
tx provider.Transaction
err error
)
pretty.Progress("Creating offer on the marketplace...", func() {
tx, err = c.e.CreateServiceOffer(args[0], c.price, c.duration, c.account)
if err != nil {
return
}
_, err = c.signAndSendTransaction(c.e, tx)
})
if err != nil {
return err
}
fmt.Printf("%s Offer created with success\n", pretty.SuccessSign)
fmt.Printf("%s See it on the marketplace: https://marketplace.mesg.com/services/%s\n", pretty.SuccessSign, c.service.Sid)

return nil
}
118 changes: 118 additions & 0 deletions commands/marketplace_publish.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package commands

import (
"fmt"
"strings"

"github.com/mesg-foundation/core/commands/provider"
"github.com/mesg-foundation/core/utils/pretty"
"github.com/spf13/cobra"
survey "gopkg.in/AlecAivazis/survey.v1"
)

type marketplacePublishCmd struct {
baseMarketplaceCmd

path string
manifest provider.MarketplaceManifestData
service provider.MarketplaceService

e Executor
}

func newMarketplacePublishCmd(e Executor) *marketplacePublishCmd {
c := &marketplacePublishCmd{e: e}
c.cmd = newCommand(&cobra.Command{
Use: "publish",
Short: "Publish a service on the MESG Marketplace",
Example: `mesg-core marketplace publish PATH_TO_SERVICE`,
PreRunE: c.preRunE,
RunE: c.runE,
Args: cobra.MinimumNArgs(1),
})
c.setupFlags()
return c
}

func (c *marketplacePublishCmd) preRunE(cmd *cobra.Command, args []string) error {
var (
err error
question = "a new service"
)

if err := c.askAccountAndPassphrase(); err != nil {
return err
}

c.path = getFirstOrCurrentPath(args)

sid, hash, err := deployService(c.e, c.path, map[string]string{})
if err != nil {
return err
}
fmt.Printf("%s Service deployed with sid %s and hash %s\n", pretty.SuccessSign, pretty.Success(sid), pretty.Success(hash))

c.manifest, err = c.e.CreateManifest(c.path, hash)
if err != nil {
return err
}

pretty.Progress("Getting service data...", func() {
c.service, err = c.e.GetService(c.manifest.Service.Definition.Sid)
})
outputError, isMarketplaceError := err.(provider.MarketplaceErrorOutput)
if err != nil && !isMarketplaceError {
return err
}
if outputError.Code != "notFound" {
question = "a new version of service"
if !strings.EqualFold(c.service.Owner, c.account) {
return fmt.Errorf("the service's owner %q is different than the specified account", c.service.Owner)
}
}

var confirmed bool
if err := survey.AskOne(&survey.Confirm{
Message: fmt.Sprintf("Are you sure to publish %s %q from path %q using account %q?", question, c.manifest.Service.Definition.Sid, c.path, c.account),
}, &confirmed, nil); err != nil {
return err
}
if !confirmed {
return fmt.Errorf("cancelled")
}

return nil
}

func (c *marketplacePublishCmd) runE(cmd *cobra.Command, args []string) error {
var (
tx provider.Transaction
err error
manifestProtocol string
manifestSource string
)

pretty.Progress("Uploading service source code on the marketplace...", func() {
// TODO: add a progress for the upload
manifestProtocol, manifestSource, err = c.e.UploadServiceFiles(c.path, c.manifest)
})
if err != nil {
return err
}
pretty.Progress("Publishing service on the marketplace...", func() {
tx, err = c.e.PublishServiceVersion(c.manifest.Service.Definition.Sid, manifestSource, manifestProtocol, c.account)
if err != nil {
return
}
_, err = c.signAndSendTransaction(c.e, tx)
})
if err != nil {
return err
}
fmt.Printf("%s Service published with success\n", pretty.SuccessSign)
fmt.Printf("%s See it on the marketplace: https://marketplace.mesg.com/services/%s\n", pretty.SuccessSign, c.manifest.Service.Definition.Sid)

fmt.Printf("%s To create a service offer, execute the command:\n\tmesg-core marketplace create-offer %s\n", pretty.SuccessSign, c.manifest.Service.Definition.Sid)

return nil
}
109 changes: 109 additions & 0 deletions commands/marketplace_purchase.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package commands

import (
"fmt"
"strconv"

"github.com/mesg-foundation/core/commands/provider"
"github.com/mesg-foundation/core/utils/pretty"
"github.com/spf13/cobra"
survey "gopkg.in/AlecAivazis/survey.v1"
)

type marketplacePurchaseCmd struct {
baseMarketplaceCmd

offerIndex string

e Executor
}

func newMarketplacePurchaseCmd(e Executor) *marketplacePurchaseCmd {
c := &marketplacePurchaseCmd{e: e}
c.cmd = newCommand(&cobra.Command{
Use: "purchase",
Short: "Purchase a service on the MESG Marketplace",
Example: `mesg-core marketplace purchase SID
mesg-core marketplace purchase SID --offer-index 1`,
PreRunE: c.preRunE,
RunE: c.runE,
Args: cobra.ExactArgs(1),
})
c.setupFlags()
c.cmd.Flags().StringVarP(&c.offerIndex, "offer-index", "o", c.offerIndex, "Offer index to purchase")
return c
}

func (c *marketplacePurchaseCmd) preRunE(cmd *cobra.Command, args []string) error {
var (
service provider.MarketplaceService
err error
)

if err := c.askAccountAndPassphrase(); err != nil {
return err
}

if c.offerIndex == "" {
// TODO: should display the list of offers and ask to select one
if err := askInput("Enter the offer index to purchase", &c.offerIndex); err != nil {
return err
}
}

pretty.Progress("Getting service data...", func() {
service, err = c.e.GetService(args[0])
})
if err != nil {
return err
}

offerIndex, err := strconv.Atoi(c.offerIndex)
if err != nil {
return err
}
if offerIndex >= len(service.Offers) {
return fmt.Errorf("offer index %d doesn't exist", offerIndex)
}
offer := service.Offers[offerIndex]
if !offer.Active {
return fmt.Errorf("cannot purchase this offer because it is not active")
}

var confirmed bool
if err := survey.AskOne(&survey.Confirm{
Message: fmt.Sprintf("Are you sure to purchase service %q for price %q and duration %q?", args[0], offer.Price, offer.Duration),
}, &confirmed, nil); err != nil {
return err
}
if !confirmed {
return fmt.Errorf("cancelled")
}

return nil
}

func (c *marketplacePurchaseCmd) runE(cmd *cobra.Command, args []string) error {
var (
transactions []provider.Transaction
err error
)
pretty.Progress("Purchasing offer on the marketplace...", func() {
transactions, err = c.e.Purchase(args[0], c.offerIndex, c.account)
if err != nil {
return
}
for _, tx := range transactions {
_, err = c.signAndSendTransaction(c.e, tx)
if err != nil {
return
}
}
})
if err != nil {
return err
}
fmt.Printf("%s Offer purchased with success\n", pretty.SuccessSign)

return nil
}
Loading

0 comments on commit f07a946

Please sign in to comment.