Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add crowdfund/claim command #252

Merged
merged 1 commit into from
Jan 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions internal/engine/command/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@ type CommandResult struct {
Successful bool
}

func (cmd *Command) RenderInternalFailure() CommandResult {
return cmd.RenderFailedTemplate("An internal error happened. Please try later")
}

func (cmd *Command) RenderFailedTemplateF(reason string, a ...any) CommandResult {
return cmd.RenderFailedTemplate(fmt.Sprintf(reason, a...))
}
Expand Down
59 changes: 55 additions & 4 deletions internal/engine/command/crowdfund/claim.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,63 @@ package crowdfund
import (
"github.com/pagu-project/pagu/internal/engine/command"
"github.com/pagu-project/pagu/internal/entity"
"github.com/pagu-project/pagu/pkg/log"
)

func (*CrowdfundCmd) claimHandler(
_ *entity.User,
func (c *CrowdfundCmd) claimHandler(
user *entity.User,
cmd *command.Command,
_ map[string]string,
args map[string]string,
) command.CommandResult {
return cmd.SuccessfulResult("TODO")
purchases, err := c.db.GetCrowdfundPurchases(user.ID)
if err != nil {
return cmd.RenderErrorTemplate(err)
}

txID := ""
for _, purchase := range purchases {
if purchase.IsClaimed() {
continue
}

isPaid, err := c.nowPayments.IsPaid(purchase.InvoiceID)
if err != nil {
log.Error("nowPayments failed", "error", err, "purchase", purchase)

return cmd.RenderInternalFailure()
}

if isPaid {
address := args[argNameClaimAddress]

txID, err = c.wallet.TransferTransaction(address, "Crowdfund campaign", purchase.PACAmount)
if err != nil {
log.Error("wallet failed", "error", err, "address", address)

return cmd.RenderErrorTemplate(err)
}

purchase.TxHash = txID
purchase.Recipient = address
err = c.db.UpdateCrowdfundPurchase(purchase)
if err != nil {
log.Error("nowPayments failed", "error", err, "purchase", purchase)

return cmd.RenderInternalFailure()
}

// Ensure we log it always
log.Warn("payment successful", "txID", txID, "address", address, "amount", purchase.PACAmount)

break
}
}

if txID == "" {
return cmd.RenderFailedTemplate("No unpaid purchase for this user")
}

txLink := c.wallet.LinkToExplorer(txID)

return cmd.RenderResultTemplate("txLink", txLink)
}
22 changes: 17 additions & 5 deletions internal/engine/command/crowdfund/crowdfund.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions internal/engine/command/crowdfund/crowdfund.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func NewCrowdfundCmd(ctx context.Context,
}

func (c *CrowdfundCmd) activeCampaign() *entity.CrowdfundCampaign {
return c.db.GetActiveCrowdfundCampaign()
return c.db.GetCrowdfundActiveCampaign()
}

func (c *CrowdfundCmd) GetCommand() *command.Command {
Expand Down Expand Up @@ -75,7 +75,7 @@ func (c *CrowdfundCmd) GetCommand() *command.Command {

purchaseChoices = append(purchaseChoices, choice)
}
c.subCmdCreate.Args[0].Choices = purchaseChoices
c.subCmdPurchase.Args[0].Choices = purchaseChoices
}

return cmd
Expand Down
12 changes: 11 additions & 1 deletion internal/engine/command/crowdfund/crowdfund.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ sub_commands:

Packages:
{{range .campaign.Packages}}
- {{.Name}}: {{.USDAmount}} USDT to {{.PACAmount}} PAC
- {{.Name}}: {{.USDAmount}} USDT to {{.PACAmount }}
{{- end}}
- name: purchase
help: Make a purchase in a crowdfunding campaign
Expand All @@ -49,3 +49,13 @@ sub_commands:
Thanks
- name: claim
help: Claim packages from a crowdfunding campaign
args:
- name: address
desc: Set your Pactus address
input_box: Text
optional: false
result_template: |
Thank you for supporting the Pactus blockchain!

You can track your transaction here: {{.txLink}}
If you have any questions or need assistance, feel free to reach out to our community.
6 changes: 3 additions & 3 deletions internal/engine/command/crowdfund/crowdfund_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,17 +71,17 @@ func (td *testData) createTestCampaign(t *testing.T, opts ...CampaignOption) *en
{
Name: td.RandString(16),
USDAmount: td.RandInt(1000),
PACAmount: td.RandInt(1000),
PACAmount: td.RandAmount(),
},
{
Name: td.RandString(16),
USDAmount: td.RandInt(1000),
PACAmount: td.RandInt(1000),
PACAmount: td.RandAmount(),
},
{
Name: td.RandString(16),
USDAmount: td.RandInt(1000),
PACAmount: td.RandInt(1000),
PACAmount: td.RandAmount(),
},
},
}
Expand Down
14 changes: 11 additions & 3 deletions internal/engine/command/crowdfund/purchase.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

"github.com/pagu-project/pagu/internal/engine/command"
"github.com/pagu-project/pagu/internal/entity"
"github.com/pagu-project/pagu/pkg/log"
)

func (c *CrowdfundCmd) purchaseHandler(
Expand All @@ -32,18 +33,25 @@ func (c *CrowdfundCmd) purchaseHandler(
}
err := c.db.AddCrowdfundPurchase(purchase)
if err != nil {
return cmd.RenderErrorTemplate(err)
log.Error("database failed", "error", err, "purchase", purchase)

return cmd.RenderInternalFailure()
}

orderID := fmt.Sprintf("crowdfund/%d", purchase.ID)
invoiceID, err := c.nowPayments.CreateInvoice(pkg.USDAmount, orderID)
if err != nil {
return cmd.RenderErrorTemplate(err)
log.Error("NowPayments failed", "error", err, "orderID", orderID)

return cmd.RenderInternalFailure()
}

purchase.InvoiceID = invoiceID
err = c.db.UpdateCrowdfundPurchase(purchase)
if err != nil {
return cmd.RenderErrorTemplate(err)
log.Error("database failed", "error", err, "purchase", purchase)

return cmd.RenderInternalFailure()
}

return cmd.RenderResultTemplate(
Expand Down
14 changes: 8 additions & 6 deletions internal/entity/crowdfund.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package entity

import "github.com/pagu-project/pagu/pkg/amount"

type Package struct {
Name string `json:"name"`
USDAmount int `json:"usd_amount"`
PACAmount int `json:"pac_amount"`
Name string `json:"name"`
USDAmount int `json:"usd_amount"`
PACAmount amount.Amount `gorm:"type:double" json:"pac_amount"`
}

type CrowdfundCampaign struct {
Expand All @@ -22,9 +24,9 @@ type CrowdfundPurchase struct {
UserID uint
InvoiceID string
USDAmount int
PACAmount int
TxHash string `gorm:"type:char(64);default:null"`
Recipient string `gorm:"type:char(42);default:null"`
PACAmount amount.Amount `gorm:"type:double"`
TxHash string `gorm:"type:char(64);default:null"`
Recipient string `gorm:"type:char(42);default:null"`
}

func (p *CrowdfundPurchase) IsClaimed() bool {
Expand Down
21 changes: 16 additions & 5 deletions internal/repository/crowdfund.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,10 @@ func (db *Database) AddCrowdfundCampaign(campaign *entity.CrowdfundCampaign) err
}
}

campaign.ID = uint(tx.RowsAffected)

return nil
}

func (db *Database) GetActiveCrowdfundCampaign() *entity.CrowdfundCampaign {
func (db *Database) GetCrowdfundActiveCampaign() *entity.CrowdfundCampaign {
var campaign *entity.CrowdfundCampaign
tx := db.gormDB.Model(&entity.CrowdfundCampaign{}).
Where("active = true").
Expand Down Expand Up @@ -51,8 +49,6 @@ func (db *Database) AddCrowdfundPurchase(purchase *entity.CrowdfundPurchase) err
}
}

purchase.ID = uint(tx.RowsAffected)

return nil
}

Expand All @@ -66,3 +62,18 @@ func (db *Database) UpdateCrowdfundPurchase(purchase *entity.CrowdfundPurchase)

return nil
}

func (db *Database) GetCrowdfundPurchases(userID uint) ([]*entity.CrowdfundPurchase, error) {
var purchases []*entity.CrowdfundPurchase
tx := db.gormDB.Model(&entity.CrowdfundPurchase{}).
Where("user_id = ?", userID).
Find(&purchases)

if tx.Error != nil {
return nil, ReadError{
Message: tx.Error.Error(),
}
}

return purchases, nil
}
27 changes: 27 additions & 0 deletions pkg/amount/amount.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package amount

import (
"database/sql/driver"
"encoding/json"
"errors"
"math"
"strconv"
Expand Down Expand Up @@ -168,6 +169,8 @@ func (a *Amount) Scan(src any) error {
}

// UnmarshalYAML implements the yaml.Unmarshaler interface.
// It parses YAML data into an Amount. It first attempts to unmarshal the data as a string,
// and if that fails, as a float64. Returns an error if conversion fails.
func (a *Amount) UnmarshalYAML(unmarshal func(any) error) error {
var s string
if err := unmarshal(&s); err != nil {
Expand All @@ -182,6 +185,30 @@ func (a *Amount) UnmarshalYAML(unmarshal func(any) error) error {
return nil
}

// UnmarshalJSON implements the json.Unmarshaler interface.
// It parses JSON data into an Amount, first trying as a string, then as a float64.
// Returns an error if the data is invalid or cannot be converted.
func (a *Amount) UnmarshalJSON(data []byte) error {
var s string
if err := json.Unmarshal(data, &s); err != nil {
s = string(data)
}

val, err := FromString(s)
if err != nil {
return err
}
*a = val

return nil
}

// MarshalJSON implements the json.Marshaler interface.
// It converts the Amount to a float64 value representing PAC.
func (a Amount) MarshalJSON() ([]byte, error) {
return json.Marshal(a.ToPAC())
}

func (a Amount) ToPactusAmount() amount.Amount {
return amount.Amount(a)
}
Loading
Loading