Skip to content

Commit ae2c2c1

Browse files
akbariandevmj
andauthored
feat: read csv files using csv tags (#152)
Co-authored-by: mj <akbarian.dev@gmail.ccom>
1 parent b05ecfb commit ae2c2c1

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+6490
-26
lines changed

go.mod

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ require (
66
github.com/PaulSonOfLars/gotgbot/v2 v2.0.0-rc.27
77
github.com/bwmarrin/discordgo v0.28.1
88
github.com/go-mail/mail/v2 v2.3.0
9+
github.com/h2non/gock v1.2.0
10+
github.com/jszwec/csvutil v1.10.0
911
github.com/labstack/echo/v4 v4.12.0
1012
github.com/pactus-project/pactus v1.3.0
1113
github.com/spf13/cobra v1.8.1
@@ -16,6 +18,8 @@ require (
1618
gorm.io/gorm v1.25.10
1719
)
1820

21+
require github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect
22+
1923
require (
2024
filippo.io/edwards25519 v1.1.0 // indirect
2125
github.com/NathanBaulch/protoc-gen-cobra v1.2.1 // indirect

go.sum

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDa
7676
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8=
7777
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0=
7878
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k=
79+
github.com/h2non/gock v1.2.0 h1:K6ol8rfrRkUOefooBC8elXoaNGYkpp7y2qcxGG6BzUE=
80+
github.com/h2non/gock v1.2.0/go.mod h1:tNhoxHYW2W42cYkYb1WqzdbYIieALC99kpYr7rH/BQk=
81+
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
82+
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
7983
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
8084
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
8185
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
@@ -120,6 +124,8 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD
120124
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
121125
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
122126
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
127+
github.com/jszwec/csvutil v1.10.0 h1:upMDUxhQKqZ5ZDCs/wy+8Kib8rZR8I8lOR34yJkdqhI=
128+
github.com/jszwec/csvutil v1.10.0/go.mod h1:/E4ONrmGkwmWsk9ae9jpXnv9QT8pLHEPcCirMFhxG9I=
123129
github.com/kilic/bls12-381 v0.1.0 h1:encrdjqKMEvabVQ7qYOKu1OvhqpK4s47wDYtNiPtlp4=
124130
github.com/kilic/bls12-381 v0.1.0/go.mod h1:vDTTHJONJ6G+P2R74EhnyotQDTliQDnFEwhdmfzw1ig=
125131
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
@@ -212,6 +218,8 @@ github.com/multiformats/go-multistream v0.5.0 h1:5htLSLl7lvJk3xx3qT/8Zm9J4K8vEOf
212218
github.com/multiformats/go-multistream v0.5.0/go.mod h1:n6tMZiwiP2wUsR8DgfDWw1dydlEqV3l6N3/GBsX6ILA=
213219
github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8=
214220
github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU=
221+
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4=
222+
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
215223
github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA=
216224
github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To=
217225
github.com/opencontainers/runtime-spec v1.2.0 h1:z97+pHb3uELt/yiAWD691HNHQIF07bE7dzrbT927iTk=

internal/engine/command/voucher/create.go

Lines changed: 46 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@ import (
66
"encoding/json"
77
"errors"
88
"fmt"
9+
"io"
910
"net/http"
1011
"strconv"
11-
"strings"
1212

13+
"github.com/jszwec/csvutil"
1314
"github.com/pactus-project/pactus/util/logger"
1415
"github.com/pagu-project/Pagu/internal/engine/command"
1516
"github.com/pagu-project/Pagu/internal/entity"
@@ -19,6 +20,14 @@ import (
1920
"gorm.io/datatypes"
2021
)
2122

23+
type BulkRecorder struct {
24+
Recipient string `csv:"Recipient"`
25+
Email string `csv:"Email"`
26+
Amount float64 `csv:"Amount"`
27+
ValidatedInMonth int `csv:"Validated"`
28+
Description string `csv:"Description"`
29+
}
30+
2231
func (v *Voucher) createOneHandler(
2332
caller *entity.User,
2433
cmd *command.Command,
@@ -87,14 +96,27 @@ func (v *Voucher) createBulkHandler(
8796
_ = resp.Body.Close()
8897
}()
8998

90-
r := csv.NewReader(resp.Body)
91-
records, err := r.ReadAll()
99+
csvReader := csv.NewReader(resp.Body)
100+
dec, err := csvutil.NewDecoder(csvReader)
92101
if err != nil {
93102
logger.Error(err.Error())
94-
return cmd.ErrorResult(errors.New("failed to read attachment content"))
103+
return cmd.ErrorResult(errors.New("failed to read csv content"))
104+
}
105+
106+
var records []BulkRecorder
107+
for {
108+
r := BulkRecorder{}
109+
if err = dec.Decode(&r); errors.Is(err, io.EOF) {
110+
break
111+
} else if err != nil {
112+
logger.Error(err.Error())
113+
return cmd.ErrorResult(errors.New("failed to parse csv content"))
114+
}
115+
116+
records = append(records, r)
95117
}
96118

97-
if len(records) < 2 {
119+
if len(records) == 0 {
98120
err = fmt.Errorf("no record founded. please add at least one record to csv file")
99121
return cmd.ErrorResult(err)
100122
}
@@ -112,7 +134,7 @@ func (v *Voucher) createBulkHandler(
112134
}
113135

114136
if notify == "TRUE" {
115-
if v.createNotification(vch.Email, vch.Code) != nil {
137+
if v.createNotification(vch.Email, vch.Code, vch.Recipient, vch.Amount.ToPAC()) != nil {
116138
return cmd.ErrorResult(err)
117139
}
118140
}
@@ -121,32 +143,33 @@ func (v *Voucher) createBulkHandler(
121143
return cmd.SuccessfulResult("Vouchers created successfully!")
122144
}
123145

124-
func (v *Voucher) createBulkVoucher(records [][]string, callerID uint) ([]*entity.Voucher, error) {
146+
func (v *Voucher) createBulkVoucher(records []BulkRecorder, callerID uint) ([]*entity.Voucher, error) {
125147
vouchers := make([]*entity.Voucher, 0)
126-
for rowIndex := 1; rowIndex < len(records); rowIndex++ {
148+
for index, record := range records {
127149
code := utils.RandomString(8, utils.CapitalAlphanumerical)
128150
for _, err := v.db.GetVoucherByCode(code); err == nil; {
129151
code = utils.RandomString(8, utils.CapitalAlphanumerical)
130152
}
131153

132-
email := records[rowIndex][0] // TODO: validate email address using regex
133-
recipient := records[rowIndex][1]
134-
amt, err := amount.FromString(strings.TrimSpace(records[rowIndex][2]))
154+
email := record.Email // TODO: validate email address using regex
155+
recipient := record.Recipient
156+
desc := record.Description
157+
158+
amt, err := amount.NewAmount(record.Amount)
135159
if err != nil {
136-
return nil, fmt.Errorf("invalid amount at row %d", rowIndex)
160+
return nil, fmt.Errorf("invalid amount at row %d", index+1)
137161
}
138162

139163
maxStake, _ := amount.NewAmount(1000)
140164
if amt > maxStake {
141165
return nil, fmt.Errorf("stake amount is more than 1000")
142166
}
143167

144-
validMonths, err := strconv.Atoi(strings.TrimSpace(records[rowIndex][3]))
145-
if err != nil {
146-
return nil, fmt.Errorf("invalid validate months at row %d", rowIndex)
168+
validMonths := record.ValidatedInMonth
169+
if validMonths < 1 {
170+
return nil, fmt.Errorf("num of validated month of code must be greater than 0 at row %d", index+1)
147171
}
148172

149-
desc := records[rowIndex][4]
150173
vouchers = append(vouchers, &entity.Voucher{
151174
Creator: callerID,
152175
Code: code,
@@ -161,17 +184,21 @@ func (v *Voucher) createBulkVoucher(records [][]string, callerID uint) ([]*entit
161184
return vouchers, nil
162185
}
163186

164-
func (v *Voucher) createNotification(email, code string) error {
165-
notificationData := entity.VoucherNotificationData{Code: code}
187+
func (v *Voucher) createNotification(email, code, recipient string, amt float64) error {
188+
notificationData := entity.VoucherNotificationData{
189+
Code: code,
190+
Recipient: recipient,
191+
Amount: amt,
192+
}
166193
b, err := json.Marshal(notificationData)
167194
if err != nil {
168195
return err
169196
}
170197
voucherCodeJSON := datatypes.JSON(b)
171198
return v.db.AddNotification(&entity.Notification{
172199
Type: notification.NotificationTypeMail,
200+
Status: entity.NotificationStatusPending,
173201
Recipient: email,
174202
Data: voucherCodeJSON,
175-
Status: entity.NotificationStatusPending,
176203
})
177204
}

internal/engine/command/voucher/create_test.go

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"errors"
55
"testing"
66

7+
"github.com/h2non/gock"
78
"github.com/pagu-project/Pagu/internal/engine/command"
89
"github.com/pagu-project/Pagu/internal/entity"
910
"github.com/pagu-project/Pagu/internal/repository"
@@ -106,14 +107,30 @@ func TestCreateOne(t *testing.T) {
106107

107108
func TestCreateBulk(t *testing.T) {
108109
voucher, db, _, _ := setup(t)
109-
t.Run("create and save notification", func(t *testing.T) {
110-
db.EXPECT().GetPendingMailNotification().Return(
111-
&entity.Notification{}, errors.New(""),
112-
).AnyTimes()
110+
t.Run("normal", func(t *testing.T) {
111+
defer gock.Off()
112+
gock.New("http://foo.com").
113+
Get("/bar").
114+
Reply(200).
115+
BodyString("Recipient,Email,Amount,Validated,Description\n" +
116+
"foo.bar,a@gmail.com,1,2,Some Descriptions\n" +
117+
"foo.bar,b@gmail.com,1,2,Some Descriptions")
118+
119+
cmd := &command.Command{}
113120

121+
db.EXPECT().AddVoucher(gomock.Any()).Return(nil).AnyTimes()
114122
db.EXPECT().AddNotification(gomock.Any()).Return(nil).AnyTimes()
123+
db.EXPECT().GetVoucherByCode(gomock.Any()).Return(
124+
entity.Voucher{}, errors.New(""),
125+
).AnyTimes()
126+
127+
args := make(map[string]string)
128+
args["file"] = "http://foo.com/bar"
129+
args["notify"] = "TRUE"
130+
caller := &entity.User{ID: 1}
131+
result := voucher.createBulkHandler(caller, cmd, args)
115132

116-
err := voucher.createNotification("foo@bar", "12345678")
117-
assert.Equal(t, nil, err)
133+
assert.True(t, result.Successful)
134+
assert.Contains(t, result.Message, "Vouchers created successfully!")
118135
})
119136
}

internal/entity/notification.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,7 @@ type Notification struct {
2525
}
2626

2727
type VoucherNotificationData struct {
28-
Code string `json:"code"`
28+
Code string `json:"code"`
29+
Amount float64 `json:"amount"`
30+
Recipient string `json:"recipient"`
2931
}

vendor/github.com/h2non/gock/.editorconfig

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/github.com/h2non/gock/.gitignore

Lines changed: 30 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/github.com/h2non/gock/.travis.yml

Lines changed: 26 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)