Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cea9a47de4 | |||
| 6a24cd6546 | |||
| b58718b537 | |||
| c4b4967777 | |||
| 3e4a977bef | |||
| bfa34d5c06 | |||
|
|
f75888a1a9 | ||
|
|
806137de61 | ||
|
|
34686494dd | ||
|
|
507eef148a | ||
|
|
a9d1fbc2cd | ||
|
|
3c593f5fdc | ||
|
|
7d23bca7f1 | ||
|
|
956ae53b59 | ||
|
|
7a1803df3a | ||
|
|
6e961a708c | ||
|
|
dab28a044a | ||
|
|
e2c7e54c16 |
@ -1,6 +1,6 @@
|
||||
FROM golang:1.21-alpine AS builder
|
||||
FROM golang:1.25-alpine AS build
|
||||
|
||||
RUN apk update && apk add --no-cache git
|
||||
RUN apk update && apk add --no-cache git ca-certificates curl
|
||||
|
||||
WORKDIR /go/src/app
|
||||
COPY pkg /go/src/app/pkg
|
||||
@ -16,7 +16,9 @@ RUN go build -ldflags "-X github.com/noqqe/serra/pkg/serra.Version=`git describe
|
||||
# copy
|
||||
FROM scratch
|
||||
WORKDIR /go/src/app
|
||||
COPY --from=builder /go/src/app/serra /go/src/app/serra
|
||||
COPY --from=build /go/src/app/serra /go/src/app/serra
|
||||
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
||||
COPY --from=build /tmp /tmp
|
||||
COPY templates /go/src/app/templates
|
||||
|
||||
# run
|
||||
|
||||
10
go.mod
10
go.mod
@ -1,8 +1,8 @@
|
||||
module github.com/noqqe/serra
|
||||
|
||||
go 1.22.0
|
||||
go 1.25.0
|
||||
|
||||
toolchain go1.22.6
|
||||
toolchain go1.25.4
|
||||
|
||||
require (
|
||||
github.com/charmbracelet/log v0.4.0
|
||||
@ -22,6 +22,7 @@ require (
|
||||
github.com/charmbracelet/x/ansi v0.3.2 // indirect
|
||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||
github.com/dolmen-go/kittyimg v0.0.0-20250610224728-874967bd8ea4 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.6 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
||||
@ -43,6 +44,7 @@ require (
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/montanaflynn/stats v0.7.1 // indirect
|
||||
github.com/muesli/termenv v0.15.2 // indirect
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
@ -57,8 +59,8 @@ require (
|
||||
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // indirect
|
||||
golang.org/x/net v0.30.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/sys v0.26.0 // indirect
|
||||
golang.org/x/term v0.25.0 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
golang.org/x/term v0.29.0 // indirect
|
||||
golang.org/x/text v0.19.0 // indirect
|
||||
google.golang.org/protobuf v1.35.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
|
||||
8
go.sum
8
go.sum
@ -27,6 +27,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dolmen-go/kittyimg v0.0.0-20250610224728-874967bd8ea4 h1:KGRb+vxMx5pGsfDjDSW2Th+b2OEflb0yC3s0daCmiYU=
|
||||
github.com/dolmen-go/kittyimg v0.0.0-20250610224728-874967bd8ea4/go.mod h1:2vk7ATPVcI7uW4Sh6PrSQvtO+Czmq8509xcg/y8Osd0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.6 h1:3+PzJTKLkvgjeTbts6msPJt4DixhT4YtFNf1gtGe3zc=
|
||||
github.com/gabriel-vasile/mimetype v1.4.6/go.mod h1:JX1qVKqZd40hUPpAfiNTe0Sne7hdfKSbOqqmkq8GCXc=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
@ -81,6 +83,8 @@ github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8
|
||||
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
|
||||
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
|
||||
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
||||
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
@ -148,10 +152,14 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
|
||||
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
|
||||
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
|
||||
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
|
||||
@ -141,11 +141,14 @@ func addCards(cards []string, unique bool, count int64) error {
|
||||
continue
|
||||
}
|
||||
|
||||
if askConfirmation(&c) {
|
||||
modifyCardCount(coll, &c, count, foil)
|
||||
}
|
||||
|
||||
} else {
|
||||
// Fetch card from scryfall
|
||||
c, err := fetchCard(setName, collectorNumber)
|
||||
|
||||
outputColor := coloredValue(c.getValue(foil))
|
||||
if err != nil {
|
||||
l.Warn(err)
|
||||
@ -156,11 +159,18 @@ func addCards(cards []string, unique bool, count int64) error {
|
||||
var total int64 = 0
|
||||
if foil {
|
||||
c.SerraCountFoil = count
|
||||
c.SerraCountFoilDeck = 0
|
||||
total = c.SerraCountFoil
|
||||
} else {
|
||||
c.SerraCount = count
|
||||
c.SerraCountDeck = 0
|
||||
total = c.SerraCount
|
||||
}
|
||||
|
||||
if !askConfirmation(c) {
|
||||
continue
|
||||
}
|
||||
|
||||
err = coll.storageAdd(c)
|
||||
if err != nil {
|
||||
l.Warn(err)
|
||||
|
||||
@ -1,10 +1,16 @@
|
||||
package serra
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"image/png"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/dolmen-go/kittyimg"
|
||||
"github.com/nfnt/resize"
|
||||
"github.com/spf13/cobra"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
)
|
||||
@ -23,6 +29,8 @@ func init() {
|
||||
cardCmd.Flags().BoolVarP(&detail, "detail", "d", false, "Show details for cards (url)")
|
||||
cardCmd.Flags().BoolVarP(&reserved, "reserved", "w", false, "If card is on reserved list")
|
||||
cardCmd.Flags().BoolVarP(&foil, "foil", "f", false, "If card is foil list")
|
||||
cardCmd.Flags().BoolVarP(&drawImg, "image", "g", false, "Draw card image using kitty format")
|
||||
cardCmd.Flags().BoolVarP(&omitInDeck, "omit-in-deck", "q", false, "Omit cards that are in decks")
|
||||
rootCmd.AddCommand(cardCmd)
|
||||
}
|
||||
|
||||
@ -36,7 +44,7 @@ otherwise you'll get a list of cards as a search result.`,
|
||||
SilenceErrors: true,
|
||||
RunE: func(cmd *cobra.Command, cards []string) error {
|
||||
if len(cards) == 0 {
|
||||
cardList := Cards(rarity, set, sortby, name, oracle, cardType, reserved, foil, 0, 0)
|
||||
cardList := Cards(rarity, set, sortby, name, oracle, cardType, reserved, foil, 0, 0, omitInDeck)
|
||||
showCardList(cardList, detail)
|
||||
} else {
|
||||
ShowCard(cards)
|
||||
@ -65,7 +73,7 @@ func ShowCard(cardids []string) {
|
||||
}
|
||||
}
|
||||
|
||||
func Cards(rarity, set, sortby, name, oracle, cardType string, reserved, foil bool, skip, limit int64) []Card {
|
||||
func Cards(rarity, set, sortby, name, oracle, cardType string, reserved, foil bool, skip, limit int64, omitInDeck bool) []Card {
|
||||
client := storageConnect()
|
||||
coll := &Collection{client.Database("serra").Collection("cards")}
|
||||
defer storageDisconnect(client)
|
||||
@ -138,6 +146,13 @@ func Cards(rarity, set, sortby, name, oracle, cardType string, reserved, foil bo
|
||||
filter = append(filter, bson.E{"serra_count_foil", bson.D{{"$gt", 0}}})
|
||||
}
|
||||
|
||||
if omitInDeck {
|
||||
filter = append(filter, bson.E{ "$expr", bson.D{{"$or",bson.A{
|
||||
bson.D{{"$ne", bson.A{"$serra_count", "$serra_count_deck"}}},
|
||||
bson.D{{"$ne", bson.A{"$serra_count_foil", "$serra_count_foil_deck"}}},
|
||||
}}}})
|
||||
}
|
||||
|
||||
cards, _ := coll.storageFind(filter, sortStage, skip, limit)
|
||||
|
||||
// This is needed because collectornumbers are strings (ie. "23a") but still we
|
||||
@ -165,24 +180,36 @@ func Cards(rarity, set, sortby, name, oracle, cardType string, reserved, foil bo
|
||||
func showCardList(cards []Card, detail bool) {
|
||||
|
||||
var total float64
|
||||
if detail {
|
||||
if drawImg {
|
||||
for _, card := range cards {
|
||||
drawImage(&card)
|
||||
}
|
||||
} else if detail {
|
||||
for _, card := range cards {
|
||||
fmt.Printf("* %dx %s%s%s (%s/%s) %s%.2f%s %s %s %s\n", card.SerraCount+card.SerraCountFoil+card.SerraCountEtched, Purple, card.Name, Reset, card.Set, card.CollectorNumber, Yellow, card.getValue(false), getCurrency(), Background, strings.Replace(card.ScryfallURI, "?utm_source=api", "", 1), Reset)
|
||||
total = total + card.getValue(false)*float64(card.SerraCount) + card.getValue(true)*float64(card.SerraCountFoil)
|
||||
}
|
||||
} else {
|
||||
for _, card := range cards {
|
||||
fmt.Printf("* %dx %s%s%s (%s/%s) %s%.2f%s%s\n", card.SerraCount+card.SerraCountFoil+card.SerraCountEtched, Purple, card.Name, Reset, card.Set, card.CollectorNumber, Yellow, card.getValue(false), getCurrency(), Reset)
|
||||
fmt.Printf("* %dx (%dx) %s%s%s (%s/%s) %s%.2f%s%s\n",
|
||||
card.SerraCount+card.SerraCountFoil+card.SerraCountEtched-card.SerraCountDeck-card.SerraCountFoilDeck-card.SerraCountEtchedDeck,
|
||||
card.SerraCount+card.SerraCountFoil+card.SerraCountEtched,
|
||||
Purple, card.Name, Reset, card.Set, card.CollectorNumber, Yellow, card.getValue(false), getCurrency(), Reset)
|
||||
total = total + card.getValue(false)*float64(card.SerraCount) + card.getValue(true)*float64(card.SerraCountFoil)
|
||||
}
|
||||
}
|
||||
|
||||
if !drawImg {
|
||||
fmt.Printf("\nTotal Value: %s%.2f%s%s\n", Yellow, total, getCurrency(), Reset)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func showCardDetails(card *Card) error {
|
||||
if drawImg {
|
||||
drawImage(card)
|
||||
} else {
|
||||
fmt.Printf("%s%s%s (%s/%s)\n", Purple, card.Name, Reset, card.Set, card.CollectorNumber)
|
||||
fmt.Printf("Available: %d / %d\n", card.SerraCount + card.SerraCountFoil - card.SerraCountDeck - card.SerraCountFoilDeck, card.SerraCount + card.SerraCountFoil)
|
||||
fmt.Printf("Added: %s\n", stringToTime(card.SerraCreated))
|
||||
fmt.Printf("Rarity: %s\n", card.Rarity)
|
||||
fmt.Printf("Scryfall: %s\n", strings.Replace(card.ScryfallURI, "?utm_source=api", "", 1))
|
||||
@ -195,6 +222,22 @@ func showCardDetails(card *Card) error {
|
||||
|
||||
fmt.Printf("\n%sValue History%s\n", Green, Reset)
|
||||
showPriceHistory(card.SerraPrices, "* ", false)
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func drawImage(card *Card) {
|
||||
fmt.Printf("%s - %s (%s/%s)\n", card.Name, card.SetName, card.Set, card.CollectorNumber)
|
||||
data, err := base64.StdEncoding.DecodeString(card.SerraImage64)
|
||||
if err == nil {
|
||||
reader := bytes.NewReader(data)
|
||||
img, err := png.Decode(reader)
|
||||
img = resize.Resize(305, 425, img, resize.Bicubic)
|
||||
if err == nil {
|
||||
kittyimg.Fprintln(os.Stdout, img)
|
||||
}
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
67
pkg/serra/deck.go
Normal file
67
pkg/serra/deck.go
Normal file
@ -0,0 +1,67 @@
|
||||
package serra
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
)
|
||||
|
||||
func init() {
|
||||
deckCmd.Flags().Int64VarP(&count, "count", "c", 1, "Amount of cards to add")
|
||||
deckCmd.Flags().BoolVarP(&foil, "foil", "f", false, "Add foil variant of card")
|
||||
rootCmd.AddCommand(deckCmd)
|
||||
}
|
||||
|
||||
var deckCmd = &cobra.Command{
|
||||
Aliases: []string{"d"},
|
||||
Use: "deck",
|
||||
Short: "Mark a card as in a deck",
|
||||
Long: "Mark a card as in a deck",
|
||||
SilenceErrors: true,
|
||||
RunE: func(cmd *cobra.Command, cards []string) error {
|
||||
deckCards(cards, count)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func deckCards(cards []string, count int64) error {
|
||||
client := storageConnect()
|
||||
coll := &Collection{client.Database("serra").Collection("cards")}
|
||||
l := Logger()
|
||||
defer storageDisconnect(client)
|
||||
|
||||
// Loop over different cards
|
||||
for _, card := range cards {
|
||||
// Extract collector number and set name from card input & trim any leading 0 from collector number
|
||||
|
||||
if !strings.Contains(card, "/") {
|
||||
l.Errorf("Invalid card format %s. Needs to be set/collector number i.e. \"usg/13\"", card)
|
||||
continue
|
||||
}
|
||||
|
||||
setName := strings.ToLower(strings.Split(card, "/")[0])
|
||||
collectorNumber := strings.TrimLeft(strings.Split(card, "/")[1], "0")
|
||||
|
||||
if collectorNumber == "" {
|
||||
l.Errorf("Invalid card format %s. Needs to be set/collector number i.e. \"usg/13\"", card)
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if card is already in collection
|
||||
co, err := coll.storageFind(bson.D{{"set", setName}, {"collectornumber", collectorNumber}}, bson.D{}, 0, 0)
|
||||
if err != nil {
|
||||
l.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
if len(co) >= 1 {
|
||||
modifyCardDeckCount(coll, &co[0], count, foil)
|
||||
} else {
|
||||
l.Errorf("Card not in collection: %s", card)
|
||||
continue
|
||||
}
|
||||
}
|
||||
storageDisconnect(client)
|
||||
return nil
|
||||
}
|
||||
@ -25,7 +25,7 @@ var exportCmd = &cobra.Command{
|
||||
Supports multiple output formats depending on where you want to export your collection.`,
|
||||
SilenceErrors: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
cardList := Cards(rarity, set, sortby, name, oracle, cardType, reserved, foil, 0, 0)
|
||||
cardList := Cards(rarity, set, sortby, name, oracle, cardType, reserved, foil, 0, 0, false)
|
||||
|
||||
// filter out cards that do not reach the minimum amount (--min-count)
|
||||
// this is done after query result because find query constructed does not support
|
||||
@ -46,7 +46,7 @@ var exportCmd = &cobra.Command{
|
||||
case "moxfield":
|
||||
exportMoxfield(cardList)
|
||||
case "json":
|
||||
exportJson(cardList)
|
||||
exportJSON(cardList)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
@ -61,7 +61,7 @@ func exportTCGPowertools(cards []Card) {
|
||||
|
||||
fmt.Println("quantity,cardmarketId,name,set,condition,language,isFoil,isPlayset,price,comment")
|
||||
for _, card := range cards {
|
||||
fmt.Printf("%d,%.0f,%s,%s,EX,German,false,false,%.2f,\n", card.SerraCount, card.CardmarketID, card.Name, card.SetName, card.getValue(false))
|
||||
fmt.Printf("%d,%.0f,%s,%s,EX,German,false,false,%.2f,\n", card.SerraCount+card.SerraCountFoil, card.CardmarketID, card.Name, card.SetName, card.getValue(false))
|
||||
}
|
||||
}
|
||||
|
||||
@ -77,7 +77,7 @@ func exportMoxfield(cards []Card) {
|
||||
|
||||
for _, card := range cards {
|
||||
records = append(records,
|
||||
[]string{fmt.Sprintf("%d", card.SerraCount), card.Name, card.Set, "NM", "English", "FALSE", card.CollectorNumber, "FALSE", "FALSE", ""})
|
||||
[]string{fmt.Sprintf("%d", card.SerraCount+card.SerraCountFoil), card.Name, card.Set, "NM", "English", "FALSE", card.CollectorNumber, "FALSE", "FALSE", ""})
|
||||
}
|
||||
|
||||
for _, record := range records {
|
||||
@ -105,7 +105,7 @@ func exportTCGHome(cards []Card) {
|
||||
|
||||
for _, card := range cards {
|
||||
records = append(records,
|
||||
[]string{fmt.Sprintf("%d", card.SerraCount), card.Name, "", card.Set, card.CollectorNumber, "English", "EX", card.ID, ""})
|
||||
[]string{fmt.Sprintf("%d", card.SerraCount+card.SerraCountFoil), card.Name, "", card.Set, card.CollectorNumber, "English", "EX", card.ID, ""})
|
||||
}
|
||||
|
||||
for _, record := range records {
|
||||
@ -121,7 +121,7 @@ func exportTCGHome(cards []Card) {
|
||||
}
|
||||
}
|
||||
|
||||
func exportJson(cards []Card) {
|
||||
func exportJSON(cards []Card) {
|
||||
ehj, _ := json.MarshalIndent(cards, "", " ")
|
||||
fmt.Println(string(ehj))
|
||||
}
|
||||
|
||||
@ -41,17 +41,25 @@ func Logger() *log.Logger {
|
||||
return l
|
||||
}
|
||||
|
||||
func modifyCardCount(coll *Collection, c *Card, amount int64, foil bool) error {
|
||||
|
||||
func getStoredCard(coll *Collection, c *Card) (Card, error) {
|
||||
// find already existing card
|
||||
sort := bson.D{{"_id", 1}}
|
||||
searchFilter := bson.D{{"_id", c.ID}}
|
||||
l := Logger()
|
||||
storedCards, err := coll.storageFind(searchFilter, sort, 0, 0)
|
||||
if err != nil {
|
||||
return Card{}, err
|
||||
}
|
||||
|
||||
return storedCards[0], nil
|
||||
}
|
||||
|
||||
func modifyCardCount(coll *Collection, c *Card, amount int64, foil bool) error {
|
||||
l := Logger()
|
||||
|
||||
storedCard, err := getStoredCard(coll, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
storedCard := storedCards[0]
|
||||
|
||||
// update card amount
|
||||
var update bson.M
|
||||
@ -87,6 +95,35 @@ func modifyCardCount(coll *Collection, c *Card, amount int64, foil bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func modifyCardDeckCount(coll *Collection, c *Card, amount int64, foil bool) error {
|
||||
l := Logger()
|
||||
|
||||
storedCard, err := getStoredCard(coll, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// update card amount
|
||||
var update bson.M
|
||||
if foil {
|
||||
newAmount := min(max(storedCard.SerraCountFoilDeck + amount, 0), storedCard.SerraCountFoil)
|
||||
l.Infof("%d / %d available", storedCard.SerraCountFoil - newAmount, storedCard.SerraCountFoil)
|
||||
update = bson.M{
|
||||
"$set": bson.M{"serra_count_foil_deck": newAmount},
|
||||
}
|
||||
} else {
|
||||
newAmount := min(max(storedCard.SerraCountDeck + amount, 0), storedCard.SerraCount)
|
||||
l.Infof("%d / %d available", storedCard.SerraCount - newAmount, storedCard.SerraCount)
|
||||
update = bson.M{
|
||||
"$set": bson.M{"serra_count_deck": newAmount},
|
||||
}
|
||||
}
|
||||
|
||||
coll.storageUpdate(bson.M{"_id": bson.M{"$eq": c.ID}}, update)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func findCardByCollectorNumber(coll *Collection, setCode string, collectorNumber string) (*Card, error) {
|
||||
sort := bson.D{{"_id", 1}}
|
||||
searchFilter := bson.D{{"set", setCode}, {"collectornumber", collectorNumber}}
|
||||
@ -271,3 +308,16 @@ func coloredValue(value float64) string {
|
||||
|
||||
return outputColor
|
||||
}
|
||||
|
||||
func askConfirmation(card *Card) bool {
|
||||
drawImage(card)
|
||||
|
||||
fmt.Println("Is this correct (y/n)?")
|
||||
|
||||
var char = 'x'
|
||||
for char != 'y' && char != 'n' {
|
||||
fmt.Scanf("%c\n", &char)
|
||||
}
|
||||
|
||||
return char == 'y'
|
||||
}
|
||||
|
||||
@ -76,7 +76,7 @@ cards you dont own (yet) :)`,
|
||||
})
|
||||
|
||||
for _, card := range missingCards {
|
||||
fmt.Printf("%s%s/%s%s %s%.02f%s%s\t%s (%s)\n", Purple, card.Set, card.CollectorNumber, Reset, Green, card.getValue(false), Reset, getCurrency(), card.Name, card.SetName)
|
||||
fmt.Printf("%s%s/%s\t%s(%s, %shttps://scryfall.com/card/%s/%s%s)\t%s%.02f%s%s\t%s (%s)\n", Purple, card.Set, card.CollectorNumber, Reset, string([]rune(card.Rarity)[0]), Background, card.Set, card.CollectorNumber, Reset, Green, card.getValue(false), Reset, getCurrency(), card.Name, card.SetName)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@ -26,7 +26,7 @@ var removeCmd = &cobra.Command{
|
||||
RunE: func(cmd *cobra.Command, cards []string) error {
|
||||
|
||||
if interactive {
|
||||
removeCardsInteractive(unique, set)
|
||||
removeCardsInteractive(set)
|
||||
} else {
|
||||
removeCards(cards, count)
|
||||
}
|
||||
@ -34,7 +34,7 @@ var removeCmd = &cobra.Command{
|
||||
},
|
||||
}
|
||||
|
||||
func removeCardsInteractive(unique bool, set string) {
|
||||
func removeCardsInteractive(set string) {
|
||||
l := Logger()
|
||||
|
||||
if len(set) == 0 {
|
||||
@ -103,6 +103,10 @@ func removeCards(cards []string, count int64) error {
|
||||
continue
|
||||
}
|
||||
|
||||
if !askConfirmation(c) {
|
||||
continue
|
||||
}
|
||||
|
||||
if foil && c.SerraCountFoil == 1 && c.SerraCount == 0 || !foil && c.SerraCount == 1 && c.SerraCountFoil == 0 {
|
||||
coll.storageRemove(bson.M{"_id": c.ID})
|
||||
l.Infof("\"%s\" (%.2f%s) removed", c.Name, c.getValue(foil), getCurrency())
|
||||
|
||||
@ -1,3 +1,7 @@
|
||||
// Package serra
|
||||
//
|
||||
// It implements base functions and also cli wrappers
|
||||
// The entire tool consists only of this one package.
|
||||
package serra
|
||||
|
||||
import (
|
||||
@ -13,11 +17,13 @@ var (
|
||||
cmc int64
|
||||
count int64
|
||||
detail bool
|
||||
drawImg bool
|
||||
foil bool
|
||||
format string
|
||||
interactive bool
|
||||
limit float64
|
||||
name string
|
||||
omitInDeck bool
|
||||
oracle string
|
||||
port uint64
|
||||
rarity string
|
||||
|
||||
@ -2,11 +2,14 @@ package serra
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
@ -17,9 +20,13 @@ type Card struct {
|
||||
SerraCount int64 `bson:"serra_count"`
|
||||
SerraCountFoil int64 `bson:"serra_count_foil"`
|
||||
SerraCountEtched int64 `bson:"serra_count_etched"`
|
||||
SerraCountDeck int64 `bson:"serra_count_deck"`
|
||||
SerraCountFoilDeck int64 `bson:"serra_count_foil_deck"`
|
||||
SerraCountEtchedDeck int64 `bson:"serra_count_etched_deck"`
|
||||
SerraPrices []PriceEntry `bson:"serra_prices"`
|
||||
SerraCreated primitive.DateTime `bson:"serra_created"`
|
||||
SerraUpdated primitive.DateTime `bson:"serra_updated"`
|
||||
SerraImage64 string `bson:"serra_image"`
|
||||
|
||||
Artist string `json:"artist"`
|
||||
ArtistIds []string `json:"artist_ids"`
|
||||
@ -119,18 +126,119 @@ type Card struct {
|
||||
Variation bool `json:"variation"`
|
||||
}
|
||||
|
||||
// Getter for currency specific value
|
||||
func (c Card) getValue(foil bool) float64 {
|
||||
if getCurrency() == EUR {
|
||||
if foil {
|
||||
return c.Prices.EurFoil
|
||||
type BulkIndex struct {
|
||||
Object string `json:"object"`
|
||||
HasMore bool `json:"has_more"`
|
||||
Data []struct {
|
||||
Object string `json:"object"`
|
||||
ID string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
URI string `json:"uri"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Size int `json:"size"`
|
||||
DownloadURI string `json:"download_uri"`
|
||||
ContentType string `json:"content_type"`
|
||||
ContentEncoding string `json:"content_encoding"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
func fetchBulkDownloadURL() (string, error) {
|
||||
url := "https://api.scryfall.com/bulk-data"
|
||||
downloadURL := ""
|
||||
|
||||
// Make an HTTP GET request
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
log.Fatalf("Error fetching data: %v", err)
|
||||
}
|
||||
return c.Prices.Eur
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Read the response body
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Fatalf("Error reading response body: %v", err)
|
||||
}
|
||||
if foil {
|
||||
return c.Prices.UsdFoil
|
||||
|
||||
// Unmarshal the JSON response
|
||||
var bulkData BulkIndex
|
||||
if err := json.Unmarshal(body, &bulkData); err != nil {
|
||||
log.Fatalf("Error unmarshaling JSON: %v", err)
|
||||
}
|
||||
return c.Prices.Usd
|
||||
|
||||
// Find and print the unique cards URL
|
||||
for _, item := range bulkData.Data {
|
||||
if item.Type == "default_cards" {
|
||||
downloadURL = item.DownloadURI
|
||||
}
|
||||
}
|
||||
|
||||
return downloadURL, nil
|
||||
}
|
||||
|
||||
func downloadBulkData(downloadURL string) (string, error) {
|
||||
|
||||
// Create a temporary directory
|
||||
tempDir, err := os.MkdirTemp("", "download")
|
||||
if err != nil {
|
||||
log.Fatalf("Error creating temporary directory: %v", err)
|
||||
}
|
||||
// defer os.RemoveAll(tempDir) // Clean up the directory when done
|
||||
|
||||
// Create a temporary file in the temporary directory
|
||||
tempFile, err := os.CreateTemp(tempDir, "downloaded-*.json") // Adjust the extension if necessary
|
||||
if err != nil {
|
||||
log.Fatalf("Error creating temporary file: %v", err)
|
||||
}
|
||||
// defer tempFile.Close() // Ensure we close the file when we're done
|
||||
|
||||
// Download the file
|
||||
resp, err := http.Get(downloadURL)
|
||||
if err != nil {
|
||||
log.Fatalf("Error downloading file: %v", err)
|
||||
}
|
||||
defer resp.Body.Close() // Make sure to close the response body
|
||||
|
||||
// Check for a successful response
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
log.Fatalf("Error: received status code %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
// Copy the response body to the temporary file
|
||||
_, err = io.Copy(tempFile, resp.Body)
|
||||
if err != nil {
|
||||
log.Fatalf("Error saving file: %v", err)
|
||||
}
|
||||
|
||||
return tempFile.Name(), nil
|
||||
}
|
||||
|
||||
func loadBulkFile(bulkFilePath string) ([]Card, error) {
|
||||
|
||||
var cards []Card
|
||||
fileBytes, _ := os.ReadFile(bulkFilePath)
|
||||
defer os.Remove(bulkFilePath)
|
||||
|
||||
err := json.Unmarshal(fileBytes, &cards)
|
||||
if err != nil {
|
||||
fmt.Println("Error unmarshalling bulk file:", err)
|
||||
return cards, nil
|
||||
}
|
||||
|
||||
return cards, nil
|
||||
|
||||
}
|
||||
|
||||
func getCardFromBulk(cards []Card, setName, collectorNumber string) (*Card, error) {
|
||||
var foundCard Card
|
||||
for _, v := range cards {
|
||||
if v.CollectorNumber == collectorNumber && v.Set == setName {
|
||||
foundCard = v
|
||||
return &foundCard, nil
|
||||
}
|
||||
}
|
||||
return &Card{}, fmt.Errorf("Card %s/%s not found in bulk data", setName, collectorNumber)
|
||||
}
|
||||
|
||||
type PriceEntry struct {
|
||||
@ -170,6 +278,20 @@ type Set struct {
|
||||
URI string `json:"uri"`
|
||||
}
|
||||
|
||||
// Getter for currency specific value
|
||||
func (c Card) getValue(foil bool) float64 {
|
||||
if getCurrency() == EUR {
|
||||
if foil {
|
||||
return c.Prices.EurFoil
|
||||
}
|
||||
return c.Prices.Eur
|
||||
}
|
||||
if foil {
|
||||
return c.Prices.UsdFoil
|
||||
}
|
||||
return c.Prices.Usd
|
||||
}
|
||||
|
||||
func fetchCard(setName, collectorNumber string) (*Card, error) {
|
||||
resp, err := http.Get(fmt.Sprintf("https://api.scryfall.com/cards/%s/%s/", setName, collectorNumber))
|
||||
if err != nil {
|
||||
@ -181,7 +303,7 @@ func fetchCard(setName, collectorNumber string) (*Card, error) {
|
||||
return &Card{}, fmt.Errorf("Card %s/%s not found", setName, collectorNumber)
|
||||
}
|
||||
|
||||
//We Read the response body on the line below.
|
||||
//we read the response body on the line below.
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Fatalf("%s", err)
|
||||
@ -191,7 +313,6 @@ func fetchCard(setName, collectorNumber string) (*Card, error) {
|
||||
r := bytes.NewReader(body)
|
||||
decoder := json.NewDecoder(r)
|
||||
val := &Card{}
|
||||
|
||||
err = decoder.Decode(val)
|
||||
if err != nil {
|
||||
log.Fatalf("%s", err)
|
||||
@ -204,11 +325,24 @@ func fetchCard(setName, collectorNumber string) (*Card, error) {
|
||||
val.Prices.Date = primitive.NewDateTimeFromTime(time.Now())
|
||||
val.SerraPrices = append(val.SerraPrices, val.Prices)
|
||||
|
||||
imgResp, imgErr := http.Get(val.ImageUris.Png)
|
||||
if imgErr != nil {
|
||||
log.Fatalln(err)
|
||||
return &Card{}, err
|
||||
}
|
||||
bytes, readErr := io.ReadAll(imgResp.Body)
|
||||
if readErr != nil {
|
||||
log.Fatalln(err)
|
||||
return &Card{}, err
|
||||
}
|
||||
|
||||
val.SerraImage64 = base64.StdEncoding.EncodeToString(bytes)
|
||||
|
||||
return val, nil
|
||||
}
|
||||
|
||||
func fetchSets() (*SetList, error) {
|
||||
// TODO better URL Building...
|
||||
// TODO: better URL Building...
|
||||
resp, err := http.Get("https://api.scryfall.com/sets")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
|
||||
@ -92,7 +92,7 @@ func showValueStats(coll *Collection, totalcoll *Collection) {
|
||||
l.Error(err)
|
||||
foilValue = 0
|
||||
}
|
||||
count_all, err := getFloat64(stats[0]["count_all"])
|
||||
countAll, err := getFloat64(stats[0]["count_all"])
|
||||
if err != nil {
|
||||
l.Error(err)
|
||||
foilValue = 0
|
||||
@ -101,7 +101,7 @@ func showValueStats(coll *Collection, totalcoll *Collection) {
|
||||
fmt.Printf("Total: %s%.2f%s%s\n", Pink, totalValue, getCurrency(), Reset)
|
||||
fmt.Printf("Normal: %s%.2f%s%s\n", Pink, normalValue, getCurrency(), Reset)
|
||||
fmt.Printf("Foils: %s%.2f%s%s\n", Pink, foilValue, getCurrency(), Reset)
|
||||
fmt.Printf("Average Card: %s%.2f%s%s\n", Pink, totalValue/count_all, getCurrency(), Reset)
|
||||
fmt.Printf("Average Card: %s%.2f%s%s\n", Pink, totalValue/countAll, getCurrency(), Reset)
|
||||
total, _ := totalcoll.storageFindTotal()
|
||||
|
||||
fmt.Printf("History: \n")
|
||||
@ -120,11 +120,11 @@ func showReservedListStats(coll *Collection) {
|
||||
}}},
|
||||
})
|
||||
|
||||
var count_reserved int32
|
||||
var countReserved int32
|
||||
if len(reserved) > 0 {
|
||||
count_reserved = reserved[0]["count"].(int32)
|
||||
countReserved = reserved[0]["count"].(int32)
|
||||
}
|
||||
fmt.Printf("Reserved List: %s%d%s\n", Yellow, count_reserved, Reset)
|
||||
fmt.Printf("Reserved List: %s%d%s\n", Yellow, countReserved, Reset)
|
||||
}
|
||||
|
||||
func showRarityStats(coll *Collection) {
|
||||
@ -150,7 +150,7 @@ func showRarityStats(coll *Collection) {
|
||||
func showCardsAddedPerMonth(coll *Collection) {
|
||||
fmt.Printf("\n%sCards added over time%s\n", Green, Reset)
|
||||
type Caot struct {
|
||||
Id struct {
|
||||
ID struct {
|
||||
Year int32 `mapstructure:"year"`
|
||||
Month int32 `mapstructure:"month"`
|
||||
} `mapstructure:"_id"`
|
||||
@ -178,7 +178,7 @@ func showCardsAddedPerMonth(coll *Collection) {
|
||||
for _, mo := range caot {
|
||||
moo := new(Caot)
|
||||
mapstructure.Decode(mo, moo)
|
||||
fmt.Printf("%d-%02d: %s%d%s\n", moo.Id.Year, moo.Id.Month, Purple, moo.Count, Reset)
|
||||
fmt.Printf("%d-%02d: %s%d%s\n", moo.ID.Year, moo.ID.Month, Purple, moo.Count, Reset)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -17,7 +17,8 @@ type Total struct {
|
||||
Value []PriceEntry `bson:"value"`
|
||||
}
|
||||
|
||||
// https://siongui.github.io/2017/02/11/go-add-method-function-to-type-in-external-package/
|
||||
// Collection Struct
|
||||
// reason: https://siongui.github.io/2017/02/11/go-add-method-function-to-type-in-external-package/
|
||||
type Collection struct {
|
||||
*mongo.Collection
|
||||
}
|
||||
|
||||
23
pkg/serra/undeck.go
Normal file
23
pkg/serra/undeck.go
Normal file
@ -0,0 +1,23 @@
|
||||
package serra
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
undeckCmd.Flags().Int64VarP(&count, "count", "c", 1, "Amount of cards to add")
|
||||
undeckCmd.Flags().BoolVarP(&foil, "foil", "f", false, "Add foil variant of card")
|
||||
rootCmd.AddCommand(undeckCmd)
|
||||
}
|
||||
|
||||
var undeckCmd = &cobra.Command{
|
||||
Aliases: []string{"u"},
|
||||
Use: "undeck",
|
||||
Short: "Unmark a card as in a deck",
|
||||
Long: "Unmark a card as in a deck",
|
||||
SilenceErrors: true,
|
||||
RunE: func(cmd *cobra.Command, cards []string) error {
|
||||
deckCards(cards, -count)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@ -19,8 +19,8 @@ func init() {
|
||||
var updateCmd = &cobra.Command{
|
||||
Aliases: []string{"u"},
|
||||
Use: "update",
|
||||
Short: "Update card values from scryfall",
|
||||
Long: `The update mechanism iterates over each card in your collection and fetches its price. After all cards you own in a set are updated, the set value will update. After all Sets are updated, the whole collection value is updated.`,
|
||||
Short: "update card values from scryfall",
|
||||
Long: `the update mechanism iterates over each card in your collection and fetches its price. after all cards you own in a set are updated, the set value will update. after all sets are updated, the whole collection value is updated.`,
|
||||
SilenceErrors: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
@ -49,11 +49,35 @@ var updateCmd = &cobra.Command{
|
||||
{"usdfoil", bson.D{{"$sum", bson.D{{"$multiply", bson.A{"$last_price.usd_foil", "$serra_count_foil"}}}}}},
|
||||
}}}
|
||||
|
||||
l.Info("Fetching bulk data from scryfall...")
|
||||
downloadURL, err := fetchBulkDownloadURL()
|
||||
if err != nil {
|
||||
l.Error("Could not extract bulk download URL:", err)
|
||||
return err
|
||||
}
|
||||
l.Infof("Found latest bulkfile url: %s", downloadURL)
|
||||
|
||||
l.Info("Downloading bulk data file...")
|
||||
bulkFilePath, err := downloadBulkData(downloadURL)
|
||||
if err != nil {
|
||||
l.Error("Could not fetch bulk json from scryfall", err)
|
||||
return err
|
||||
}
|
||||
|
||||
l.Info("Loading bulk data file...")
|
||||
updatedCards, err := loadBulkFile(bulkFilePath)
|
||||
if err != nil {
|
||||
l.Error("Could not load bulk file:", err)
|
||||
return err
|
||||
}
|
||||
l.Infof("Successfully loaded %d cards. Starting Update.", len(updatedCards))
|
||||
|
||||
sets, _ := fetchSets()
|
||||
for _, set := range sets.Data {
|
||||
|
||||
// When downloading new sets, PriceList needs to be initialized
|
||||
// This query silently fails if set was already downloaded. Not nice but ok for now.
|
||||
// TODO: make this not fail silently
|
||||
set.SerraPrices = []PriceEntry{}
|
||||
setscoll.storageAddSet(&set)
|
||||
|
||||
@ -74,13 +98,13 @@ var updateCmd = &cobra.Command{
|
||||
SaucerHead: "[green]>[reset]",
|
||||
SaucerPadding: " ",
|
||||
BarStart: "|",
|
||||
BarEnd: set.Name,
|
||||
BarEnd: "| " + set.Name,
|
||||
}),
|
||||
)
|
||||
|
||||
for _, card := range cards {
|
||||
bar.Add(1)
|
||||
updatedCard, err := fetchCard(card.Set, card.CollectorNumber)
|
||||
updatedCard, err := getCardFromBulk(updatedCards, card.Set, card.CollectorNumber)
|
||||
if err != nil {
|
||||
l.Error(err)
|
||||
continue
|
||||
@ -115,7 +139,6 @@ var updateCmd = &cobra.Command{
|
||||
"$set": bson.M{"serra_updated": p.Date, "cardcount": set.CardCount},
|
||||
"$push": bson.M{"serra_prices": p},
|
||||
}
|
||||
// fmt.Printf("Set %s%s%s (%s) is now worth %s%.02f EUR%s\n", Pink, set.Name, Reset, set.Code, Yellow, setvalue[0]["value"], Reset)
|
||||
setscoll.storageUpdate(bson.M{"code": bson.M{"$eq": set.Code}}, setUpdate)
|
||||
}
|
||||
|
||||
@ -130,7 +153,7 @@ var updateCmd = &cobra.Command{
|
||||
tmpCard := Card{}
|
||||
tmpCard.Prices = t
|
||||
|
||||
fmt.Printf("\n%sUpdating total value of collection to: %s%.02f%s%s\n", Green, Yellow, tmpCard.getValue(false)+tmpCard.getValue(true), getCurrency(), Reset)
|
||||
l.Info("\n%sUpdating total value of collection to: %s%.02f%s%s\n", Green, Yellow, tmpCard.getValue(false)+tmpCard.getValue(true), getCurrency(), Reset)
|
||||
totalcoll.storageAddTotal(t)
|
||||
|
||||
return nil
|
||||
|
||||
@ -71,7 +71,7 @@ func landingPage(c *gin.Context) {
|
||||
sets := Sets("release")
|
||||
|
||||
// Fetch all results based on filter criteria
|
||||
cards := Cards("", query.Set, query.Sort, query.Name, "", "", false, false, query.Page*int64(limit), limit)
|
||||
cards := Cards("", query.Set, query.Sort, query.Name, "", "", false, false, query.Page*int64(limit), limit, false)
|
||||
|
||||
// Construct quick way for counting results
|
||||
filter := bson.D{}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user