initial commit

This commit is contained in:
Gitea
2024-01-24 16:12:16 +03:00
commit 5b5dc2165c
23 changed files with 1846 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
*.json
*.zst
badger/

9
assets/category.fbs Normal file
View File

@ -0,0 +1,9 @@
namespace internal.encoding.fbs;
table Category {
id:long;
name:string;
}
root_type Category;

23
assets/gooditem.fbs Normal file
View File

@ -0,0 +1,23 @@
namespace internal.encoding.fbs;
table GoodItem {
sku:string;
photo:string;
name:string;
description:string;
category:string;
type:string;
producer:string;
pack:short;
step:short;
price:float;
tariff:float;
cart:long;
stock:short;
}
table GoodItems {
items:[GoodItem];
}
root_type GoodItems;

444
cmd/converter/main.go Normal file
View File

@ -0,0 +1,444 @@
package main
import (
"bufio"
"context"
"crypto/rand"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"os/signal"
"sync"
"sync/atomic"
"time"
"git.loyso.art/frx/eway/internal/config"
"git.loyso.art/frx/eway/internal/entity"
"git.loyso.art/frx/eway/internal/storage"
xbadger "git.loyso.art/frx/eway/internal/storage/badger"
badger "github.com/dgraph-io/badger/v4"
"github.com/dgraph-io/badger/v4/pb"
"github.com/rodaine/table"
"github.com/rs/zerolog"
"github.com/urfave/cli"
)
type appSettings struct {
Badger config.Badger
}
func main() {
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer func() {
cancel()
}()
var corewg sync.WaitGroup
err := runcli(ctx, &corewg)
if err != nil {
fmt.Fprintf(os.Stderr, "unable to handle app: %v", err)
corewg.Wait()
os.Exit(1)
}
corewg.Wait()
os.Exit(0)
}
func runcli(ctx context.Context, wg *sync.WaitGroup) (err error) {
tsSet := func(wr *zerolog.ConsoleWriter) {
wr.TimeFormat = time.RFC3339
}
log := zerolog.New(zerolog.NewConsoleWriter(tsSet)).Level(zerolog.DebugLevel).With().Timestamp().Str("app", "converter").Logger()
defer func() {
log.Info().Err(err).Msg("app finished")
}()
ctx = log.WithContext(ctx)
log.Info().Msg("making badger")
db, err := xbadger.Open(ctx, "badger/", log)
if err != nil {
return fmt.Errorf("opening badger: %w", err)
}
go func() {
var item atomic.Uint64
err = db.Subscribe(
ctx,
func(kvlist *badger.KVList) error {
kvs := kvlist.GetKv()
for _, kv := range kvs {
count := item.Add(1)
log.Debug().Bytes("key", kv.GetKey()).Uint64("count", count).Msg("inspecting")
}
return nil
},
[]pb.Match{
{
Prefix: []byte("!!category!!"),
},
{
Prefix: []byte("!!goodsitem!!"),
},
},
)
log.Err(err).Msg("subscribing")
}()
maxBatch := db.MaxBatchCount()
log.Info().Int("max_batch", int(maxBatch)).Msg("max batch settings")
client, err := xbadger.NewClient(db)
if err != nil {
return fmt.Errorf("making new client: %w", err)
}
wg.Add(1)
defer func() {
defer wg.Done()
println("closing client")
errClose := client.Close()
if errClose != nil {
log.Warn().Err(errClose).Msg("unable to close client")
}
println("flushing db")
errSync := db.Sync()
if errSync != nil {
log.Warn().Err(errSync).Msg("unable to sync db")
}
// time.Sleep(time.Second * 5)
println("closing db")
errClose = db.Close()
if errClose != nil {
log.Warn().Err(errClose).Msg("unable to close db")
}
}()
app := setupCLI(ctx, client, maxBatch)
return app.Run(os.Args)
}
func setupCLI(ctx context.Context, r storage.Repository, maxBatch int64) *cli.App {
app := cli.NewApp()
app.Commands = cli.Commands{
newImportCmd(ctx, r, maxBatch),
newViewCmd(ctx, r),
}
return app
}
func newImportCmd(ctx context.Context, r storage.Repository, maxBatch int64) cli.Command {
return cli.Command{
Name: "import",
Usage: "category for importing data from sources",
Flags: []cli.Flag{
// &cli.StringFlag{
// Name: "config",
// Usage: "path to config",
// Value: "config.json",
// TakesFile: true,
// },
&cli.BoolFlag{
Name: "verbose",
Usage: "set logger to debug mode",
},
},
Before: func(c *cli.Context) error {
if c.Bool("verbose") {
zerolog.SetGlobalLevel(zerolog.DebugLevel)
}
return nil
},
Subcommands: cli.Commands{
newImportFromFileCmd(ctx, r, maxBatch),
},
}
}
func newViewCmd(ctx context.Context, r storage.Repository) cli.Command {
return cli.Command{
Name: "view",
Usage: "Set of commands to view the data inside db",
Subcommands: []cli.Command{
newViewCategoriesCmd(ctx, r),
newViewItemsCmd(ctx, r),
},
}
}
func newViewCategoriesCmd(ctx context.Context, r storage.Repository) cli.Command {
return cli.Command{
Name: "categories",
Usage: "Set of commands to work with categories",
Subcommands: []cli.Command{
newViewCategoriesListCmd(ctx, r),
},
}
}
func newViewItemsCmd(ctx context.Context, r storage.Repository) cli.Command {
return cli.Command{
Name: "items",
Usage: "Set of command to work with items",
Subcommands: cli.Commands{
newViewItemsCountCmd(ctx, r),
},
}
}
func newViewItemsCountCmd(ctx context.Context, r storage.Repository) cli.Command {
return cli.Command{
Name: "count",
Usage: "iterates over collection and counts number of items",
Action: viewItemsCount(ctx, r),
}
}
func newViewCategoriesListCmd(ctx context.Context, r storage.Repository) cli.Command {
return cli.Command{
Name: "list",
Usage: "lists stored categories stored in database",
Flags: []cli.Flag{
&cli.IntFlag{
Name: "limit",
Usage: "limits output to selected items",
Value: 20,
},
&cli.IntFlag{
Name: "page",
Usage: "in case of limit, selects page",
Value: 0,
},
&cli.BoolFlag{
Name: "with-total",
Usage: "prints total count of categories",
},
},
Action: viewCategoriesListAction(ctx, r),
}
}
func viewItemsCount(ctx context.Context, r storage.Repository) cli.ActionFunc {
f := func(c *cli.Context) error {
// itemChan, err := r.GoodsItem().ListIter(ctx, 10)
// if err != nil {
// return fmt.Errorf("getting list iter: %w", err)
// }
//
var count int
// for range itemChan {
// count++
// }
//
items, err := r.GoodsItem().List(ctx)
if err != nil {
return fmt.Errorf("getting list: %w", err)
}
zerolog.Ctx(ctx).Info().Int("count", count).Int("list_count", len(items)).Msg("read all items")
return nil
}
return wrapActionFunc(ctx, f)
}
func viewCategoriesListAction(ctx context.Context, r storage.Repository) cli.ActionFunc {
f := func(c *cli.Context) error {
categories, err := r.Category().List(ctx)
if err != nil {
return fmt.Errorf("listing categories: %w", err)
}
limit := c.Int("limit")
page := c.Int("page")
total := len(categories)
if page == 0 {
page = 1
}
if limit > 0 {
offset := (page - 1) * limit
if offset > len(categories) {
offset = len(categories) - 1
}
limit = offset + limit
if limit > len(categories) {
limit = len(categories)
}
categories = categories[offset:limit]
}
tbl := table.New("ID", "Name")
for _, category := range categories {
if category.ID == 0 && category.Name == "" {
continue
}
tbl.AddRow(category.ID, category.Name)
}
tbl.Print()
if c.Bool("with-total") {
zerolog.Ctx(ctx).Info().Int("count", total).Msg("total categories stats")
}
return nil
}
return wrapActionFunc(ctx, f)
}
func newImportFromFileCmd(ctx context.Context, r storage.Repository, maxBatch int64) cli.Command {
return cli.Command{
Name: "fromfile",
Usage: "imports from file into db",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "src",
Value: "",
Usage: "source of the data.",
Required: true,
},
},
Action: handleConvert(ctx, r, maxBatch),
}
}
func handleConvert(ctx context.Context, r storage.Repository, maxBatch int64) cli.ActionFunc {
f := func(c *cli.Context) error {
filesrc := c.String("src")
log := zerolog.Ctx(ctx)
log.Debug().Str("filepath", filesrc).Msg("importing data from file")
productsFile, err := os.Open(filesrc)
if err != nil {
return fmt.Errorf("opening file: %w", err)
}
defer func() {
errClose := productsFile.Close()
if errClose != nil {
log.Warn().Err(errClose).Msg("unable to close file")
}
}()
var (
goodsItem entity.GoodsItem
// goodsItems []entity.GoodsItem
failedToInsert int
)
seenCategories := make(map[string]struct{})
categories, err := r.Category().List(ctx)
if err != nil {
return fmt.Errorf("listing categories: %w", err)
}
for _, category := range categories {
seenCategories[category.Name] = struct{}{}
}
bfile := bufio.NewReader(productsFile)
for {
line, _, err := bfile.ReadLine()
if err != nil {
if errors.Is(err, io.EOF) {
break
}
return fmt.Errorf("reading line: %w", err)
}
err = json.Unmarshal(line, &goodsItem)
if err != nil {
// log.Warn().Err(err).Str("line", string(line)).Msg("unable to unmarshal line into item")
failedToInsert++
continue
}
_, err = r.GoodsItem().UpsertMany(ctx, goodsItem)
if err != nil {
return fmt.Errorf("unable to upsert new item: %w", err)
}
// goodsItems = append(goodsItems, goodsItem)
if _, ok := seenCategories[goodsItem.Type]; ok {
continue
}
_, err = r.Category().Create(ctx, goodsItem.Type)
if err != nil {
return fmt.Errorf("unable to create new category: %w", err)
}
log.Debug().Any("category", goodsItem.Type).Msg("inserted new category")
seenCategories[goodsItem.Type] = struct{}{}
}
// log.Debug().Int("count", len(goodsItems)).Int("failed", failedToInsert).Msg("preparing to upload")
//
// start := time.Now()
// batchSize := int(maxBatch)
// for i := 0; i < len(goodsItems); i += batchSize {
// to := i + batchSize
// if to > len(goodsItems) {
// to = len(goodsItems)
// }
//
// _, err = r.GoodsItem().UpsertMany(ctx, goodsItems[i:to]...)
// if err != nil {
// return fmt.Errorf("upserting items: %w", err)
// }
// log.Debug().Int("count", to-i).Msg("inserted batch")
// time.Sleep(time.Second)
// }
// log.Debug().Dur("elapsed", time.Since(start)).Msg("upload finished")
//
time.Sleep(time.Second * 30)
return nil
}
time.Sleep(time.Second * 10)
return wrapActionFunc(ctx, f)
}
func wrapActionFunc(ctx context.Context, next cli.ActionFunc) cli.ActionFunc {
return func(c *cli.Context) error {
var data [3]byte
_, _ = rand.Read(data[:])
reqid := hex.EncodeToString(data[:])
log := zerolog.Ctx(ctx).With().Str("reqid", reqid).Logger()
ctx = log.WithContext(ctx)
start := time.Now()
defer func() {
log.Info().Dur("elapsed", time.Since(start)).Msg("command completed")
}()
log.Info().Msg("command execution started")
return next(c)
}
}

34
go.mod Normal file
View File

@ -0,0 +1,34 @@
module git.loyso.art/frx/eway
go 1.21.4
require (
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/dgraph-io/badger/v4 v4.2.0 // indirect
github.com/dgraph-io/ristretto v0.1.1 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/go-resty/resty/v2 v2.10.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/glog v1.0.0 // indirect
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/snappy v0.0.3 // indirect
github.com/google/flatbuffers v23.5.26+incompatible // indirect
github.com/karlseguin/ccache/v3 v3.0.5 // indirect
github.com/kataras/tablewriter v0.0.0-20180708051242-e063d29b7c23 // indirect
github.com/klauspost/compress v1.12.3 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/rodaine/table v1.1.1 // indirect
github.com/rs/zerolog v1.31.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/urfave/cli v1.22.14 // indirect
go.opencensus.io v0.22.5 // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/sys v0.13.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
)

177
go.sum Normal file
View File

@ -0,0 +1,177 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgraph-io/badger/v4 v4.2.0 h1:kJrlajbXXL9DFTNuhhu9yCx7JJa4qpYWxtE8BzuWsEs=
github.com/dgraph-io/badger/v4 v4.2.0/go.mod h1:qfCqhPoWDFJRx1gp5QwwyGo8xk1lbHUxvK9nK0OGAak=
github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8=
github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/go-resty/resty/v2 v2.10.0 h1:Qla4W/+TMmv0fOeeRqzEpXPLfTUnR5HZ1+lGs+CkiCo=
github.com/go-resty/resty/v2 v2.10.0/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ=
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/flatbuffers v23.5.26+incompatible h1:M9dgRyhJemaM4Sw8+66GHBu8ioaQmyPLg1b8VwK5WJg=
github.com/google/flatbuffers v23.5.26+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/karlseguin/ccache/v3 v3.0.5 h1:hFX25+fxzNjsRlREYsoGNa2LoVEw5mPF8wkWq/UnevQ=
github.com/karlseguin/ccache/v3 v3.0.5/go.mod h1:qxC372+Qn+IBj8Pe3KvGjHPj0sWwEF7AeZVhsNPZ6uY=
github.com/kataras/tablewriter v0.0.0-20180708051242-e063d29b7c23 h1:M8exrBzuhWcU6aoHJlHWPe4qFjVKzkMGRal78f5jRRU=
github.com/kataras/tablewriter v0.0.0-20180708051242-e063d29b7c23/go.mod h1:kBSna6b0/RzsOcOZf515vAXwSsXYusl2U7SA0XP09yI=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.12.3 h1:G5AfA94pHPysR56qqrkO2pxEexdDzrpFJ6yt/VqWxVU=
github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rodaine/table v1.1.1 h1:zBliy3b4Oj6JRmncse2Z85WmoQvDrXOYuy0JXCt8Qz8=
github.com/rodaine/table v1.1.1/go.mod h1:iqTRptjn+EVcrVBYtNMlJ2wrJZa3MpULUmcXFpfcziA=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A=
github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/urfave/cli v1.22.14 h1:ebbhrRiGK2i4naQJr+1Xj92HXZCrK7MsyTS/ob3HnAk=
github.com/urfave/cli v1.22.14/go.mod h1:X0eDS6pD6Exaclxm99NJ3FiCDRED7vIHpx2mDOHLvkA=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
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=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@ -0,0 +1,5 @@
package config
type Badger struct {
Path string `json:"path"`
}

64
internal/core/base.go Normal file
View File

@ -0,0 +1,64 @@
package core
import (
"context"
"fmt"
"time"
"github.com/rs/zerolog"
)
type BaseAction interface{}
type Action[Q, R any] interface {
BaseAction
Do(context.Context, Q) (R, error)
}
type ActionDecorator[Q, R any, A Action[Q, R]] interface {
Action[Q, R]
}
type baseAction struct {
env *Env
}
func newBaseAction(env *Env) baseAction {
return baseAction{
env: env,
}
}
func applyDecorators[Q, R any, A Action[Q, R]](action A) ActionDecorator[Q, R, A] {
return logActionDecorator[Q, R, A]{
action: action,
}
}
type logActionDecorator[Q, R any, A Action[Q, R]] struct {
action Action[Q, R]
}
func (d logActionDecorator[Q, R, A]) Do(ctx context.Context, params Q) (result R, err error) {
actionName := getTypeName[A]()
start := time.Now()
log := zerolog.Ctx(ctx).With().Str("action_name", actionName).Logger()
ctx = log.WithContext(ctx)
result, err = d.action.Do(ctx, params)
elapsed := time.Since(start)
if err != nil {
log.Warn().Err(err).Dur("elapsed", elapsed).Msg("action failed")
}
log.Info().Dur("elapsed", elapsed).Msg("action successed")
return result, err
}
func getTypeName[T any]() string {
var t T
out := fmt.Sprintf("%T", t)
return out
}

View File

@ -0,0 +1,45 @@
package core
import (
"context"
"fmt"
"git.loyso.art/frx/eway/internal/entity"
)
type CreateGoodsItemParams struct {
GoodsItems []entity.GoodsItem
}
type CreateGoodsItemResult struct {
GoodsItems []entity.GoodsItem
}
type createGoodsItemAction struct {
baseAction
}
type CreateGoodsItemAction Action[CreateGoodsItemParams, CreateGoodsItemResult]
func NewCreateGoodsItemAction(
env *Env,
) ActionDecorator[CreateGoodsItemParams, CreateGoodsItemResult, CreateGoodsItemAction] {
ba := newBaseAction(env)
action := &createGoodsItemAction{
baseAction: ba,
}
return applyDecorators(action)
}
func (a *createGoodsItemAction) Do(
ctx context.Context,
params CreateGoodsItemParams,
) (result CreateGoodsItemResult, err error) {
result.GoodsItems, err = a.env.repository.GoodsItem().UpsertMany(ctx, params.GoodsItems...)
if err != nil {
return result, fmt.Errorf("upserting items: %w", err)
}
return result, nil
}

11
internal/core/env.go Normal file
View File

@ -0,0 +1,11 @@
package core
import (
"git.loyso.art/frx/eway/internal/entity"
"git.loyso.art/frx/eway/internal/storage"
)
type Env struct {
repository storage.Repository
mapper entity.Mapper
}

View File

@ -0,0 +1,49 @@
package core
import (
"context"
"fmt"
"git.loyso.art/frx/eway/internal/entity"
)
type GetCategoryParams struct {
ID int64
Name string
}
type GetCategoryResult struct {
Category entity.Category
}
type GetCategoryAction Action[GetCategoryParams, GetCategoryResult]
type getCategoryAction struct {
baseAction
}
func NewGetCategoryAction(env *Env) ActionDecorator[GetCategoryParams, GetCategoryResult, GetCategoryAction] {
ba := newBaseAction(env)
action := &getCategoryAction{
baseAction: ba,
}
return applyDecorators(action)
}
func (a *getCategoryAction) Do(ctx context.Context, params GetCategoryParams) (result GetCategoryResult, err error) {
id := params.ID
if params.Name != "" {
id, err = a.env.mapper.CategoryNameToID(ctx, params.Name)
if err != nil {
return result, fmt.Errorf("resolving category id: %w", err)
}
}
result.Category, err = a.env.repository.Category().Get(ctx, id)
if err != nil {
return result, fmt.Errorf("getting category: %w", err)
}
return result, nil
}

View File

@ -0,0 +1,38 @@
package core
import (
"context"
"fmt"
"git.loyso.art/frx/eway/internal/entity"
)
type ListCategoriesParams struct{}
type ListCategoriesResult struct {
Categories []entity.Category
}
type listCategoriesAction struct {
baseAction
}
type ListCategoriesAction Action[ListCategoriesParams, ListCategoriesResult]
func NewListCategoriesAction(env *Env) ActionDecorator[ListCategoriesParams, ListCategoriesResult, ListCategoriesAction] {
ba := newBaseAction(env)
action := &listCategoriesAction{
baseAction: ba,
}
return applyDecorators(action)
}
func (l *listCategoriesAction) Do(ctx context.Context, params ListCategoriesParams) (result ListCategoriesResult, err error) {
result.Categories, err = l.env.repository.Category().List(ctx)
if err != nil {
return result, fmt.Errorf("listing: %w", err)
}
return result, nil
}

View File

@ -0,0 +1,75 @@
// Code generated by the FlatBuffers compiler. DO NOT EDIT.
package fbs
import (
flatbuffers "github.com/google/flatbuffers/go"
)
type Category struct {
_tab flatbuffers.Table
}
func GetRootAsCategory(buf []byte, offset flatbuffers.UOffsetT) *Category {
n := flatbuffers.GetUOffsetT(buf[offset:])
x := &Category{}
x.Init(buf, n+offset)
return x
}
func FinishCategoryBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) {
builder.Finish(offset)
}
func GetSizePrefixedRootAsCategory(buf []byte, offset flatbuffers.UOffsetT) *Category {
n := flatbuffers.GetUOffsetT(buf[offset+flatbuffers.SizeUint32:])
x := &Category{}
x.Init(buf, n+offset+flatbuffers.SizeUint32)
return x
}
func FinishSizePrefixedCategoryBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) {
builder.FinishSizePrefixed(offset)
}
func (rcv *Category) Init(buf []byte, i flatbuffers.UOffsetT) {
rcv._tab.Bytes = buf
rcv._tab.Pos = i
}
func (rcv *Category) Table() flatbuffers.Table {
return rcv._tab
}
func (rcv *Category) Id() int64 {
o := flatbuffers.UOffsetT(rcv._tab.Offset(4))
if o != 0 {
return rcv._tab.GetInt64(o + rcv._tab.Pos)
}
return 0
}
func (rcv *Category) MutateId(n int64) bool {
return rcv._tab.MutateInt64Slot(4, n)
}
func (rcv *Category) Name() []byte {
o := flatbuffers.UOffsetT(rcv._tab.Offset(6))
if o != 0 {
return rcv._tab.ByteVector(o + rcv._tab.Pos)
}
return nil
}
func CategoryStart(builder *flatbuffers.Builder) {
builder.StartObject(2)
}
func CategoryAddId(builder *flatbuffers.Builder, id int64) {
builder.PrependInt64Slot(0, id, 0)
}
func CategoryAddName(builder *flatbuffers.Builder, name flatbuffers.UOffsetT) {
builder.PrependUOffsetTSlot(1, flatbuffers.UOffsetT(name), 0)
}
func CategoryEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT {
return builder.EndObject()
}

View File

@ -0,0 +1,216 @@
// Code generated by the FlatBuffers compiler. DO NOT EDIT.
package fbs
import (
flatbuffers "github.com/google/flatbuffers/go"
)
type GoodItem struct {
_tab flatbuffers.Table
}
func GetRootAsGoodItem(buf []byte, offset flatbuffers.UOffsetT) *GoodItem {
n := flatbuffers.GetUOffsetT(buf[offset:])
x := &GoodItem{}
x.Init(buf, n+offset)
return x
}
func FinishGoodItemBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) {
builder.Finish(offset)
}
func GetSizePrefixedRootAsGoodItem(buf []byte, offset flatbuffers.UOffsetT) *GoodItem {
n := flatbuffers.GetUOffsetT(buf[offset+flatbuffers.SizeUint32:])
x := &GoodItem{}
x.Init(buf, n+offset+flatbuffers.SizeUint32)
return x
}
func FinishSizePrefixedGoodItemBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) {
builder.FinishSizePrefixed(offset)
}
func (rcv *GoodItem) Init(buf []byte, i flatbuffers.UOffsetT) {
rcv._tab.Bytes = buf
rcv._tab.Pos = i
}
func (rcv *GoodItem) Table() flatbuffers.Table {
return rcv._tab
}
func (rcv *GoodItem) Sku() []byte {
o := flatbuffers.UOffsetT(rcv._tab.Offset(4))
if o != 0 {
return rcv._tab.ByteVector(o + rcv._tab.Pos)
}
return nil
}
func (rcv *GoodItem) Photo() []byte {
o := flatbuffers.UOffsetT(rcv._tab.Offset(6))
if o != 0 {
return rcv._tab.ByteVector(o + rcv._tab.Pos)
}
return nil
}
func (rcv *GoodItem) Name() []byte {
o := flatbuffers.UOffsetT(rcv._tab.Offset(8))
if o != 0 {
return rcv._tab.ByteVector(o + rcv._tab.Pos)
}
return nil
}
func (rcv *GoodItem) Description() []byte {
o := flatbuffers.UOffsetT(rcv._tab.Offset(10))
if o != 0 {
return rcv._tab.ByteVector(o + rcv._tab.Pos)
}
return nil
}
func (rcv *GoodItem) Category() []byte {
o := flatbuffers.UOffsetT(rcv._tab.Offset(12))
if o != 0 {
return rcv._tab.ByteVector(o + rcv._tab.Pos)
}
return nil
}
func (rcv *GoodItem) Type() []byte {
o := flatbuffers.UOffsetT(rcv._tab.Offset(14))
if o != 0 {
return rcv._tab.ByteVector(o + rcv._tab.Pos)
}
return nil
}
func (rcv *GoodItem) Producer() []byte {
o := flatbuffers.UOffsetT(rcv._tab.Offset(16))
if o != 0 {
return rcv._tab.ByteVector(o + rcv._tab.Pos)
}
return nil
}
func (rcv *GoodItem) Pack() int16 {
o := flatbuffers.UOffsetT(rcv._tab.Offset(18))
if o != 0 {
return rcv._tab.GetInt16(o + rcv._tab.Pos)
}
return 0
}
func (rcv *GoodItem) MutatePack(n int16) bool {
return rcv._tab.MutateInt16Slot(18, n)
}
func (rcv *GoodItem) Step() int16 {
o := flatbuffers.UOffsetT(rcv._tab.Offset(20))
if o != 0 {
return rcv._tab.GetInt16(o + rcv._tab.Pos)
}
return 0
}
func (rcv *GoodItem) MutateStep(n int16) bool {
return rcv._tab.MutateInt16Slot(20, n)
}
func (rcv *GoodItem) Price() float32 {
o := flatbuffers.UOffsetT(rcv._tab.Offset(22))
if o != 0 {
return rcv._tab.GetFloat32(o + rcv._tab.Pos)
}
return 0.0
}
func (rcv *GoodItem) MutatePrice(n float32) bool {
return rcv._tab.MutateFloat32Slot(22, n)
}
func (rcv *GoodItem) Tariff() float32 {
o := flatbuffers.UOffsetT(rcv._tab.Offset(24))
if o != 0 {
return rcv._tab.GetFloat32(o + rcv._tab.Pos)
}
return 0.0
}
func (rcv *GoodItem) MutateTariff(n float32) bool {
return rcv._tab.MutateFloat32Slot(24, n)
}
func (rcv *GoodItem) Cart() int64 {
o := flatbuffers.UOffsetT(rcv._tab.Offset(26))
if o != 0 {
return rcv._tab.GetInt64(o + rcv._tab.Pos)
}
return 0
}
func (rcv *GoodItem) MutateCart(n int64) bool {
return rcv._tab.MutateInt64Slot(26, n)
}
func (rcv *GoodItem) Stock() int16 {
o := flatbuffers.UOffsetT(rcv._tab.Offset(28))
if o != 0 {
return rcv._tab.GetInt16(o + rcv._tab.Pos)
}
return 0
}
func (rcv *GoodItem) MutateStock(n int16) bool {
return rcv._tab.MutateInt16Slot(28, n)
}
func GoodItemStart(builder *flatbuffers.Builder) {
builder.StartObject(13)
}
func GoodItemAddSku(builder *flatbuffers.Builder, sku flatbuffers.UOffsetT) {
builder.PrependUOffsetTSlot(0, flatbuffers.UOffsetT(sku), 0)
}
func GoodItemAddPhoto(builder *flatbuffers.Builder, photo flatbuffers.UOffsetT) {
builder.PrependUOffsetTSlot(1, flatbuffers.UOffsetT(photo), 0)
}
func GoodItemAddName(builder *flatbuffers.Builder, name flatbuffers.UOffsetT) {
builder.PrependUOffsetTSlot(2, flatbuffers.UOffsetT(name), 0)
}
func GoodItemAddDescription(builder *flatbuffers.Builder, description flatbuffers.UOffsetT) {
builder.PrependUOffsetTSlot(3, flatbuffers.UOffsetT(description), 0)
}
func GoodItemAddCategory(builder *flatbuffers.Builder, category flatbuffers.UOffsetT) {
builder.PrependUOffsetTSlot(4, flatbuffers.UOffsetT(category), 0)
}
func GoodItemAddType(builder *flatbuffers.Builder, type_ flatbuffers.UOffsetT) {
builder.PrependUOffsetTSlot(5, flatbuffers.UOffsetT(type_), 0)
}
func GoodItemAddProducer(builder *flatbuffers.Builder, producer flatbuffers.UOffsetT) {
builder.PrependUOffsetTSlot(6, flatbuffers.UOffsetT(producer), 0)
}
func GoodItemAddPack(builder *flatbuffers.Builder, pack int16) {
builder.PrependInt16Slot(7, pack, 0)
}
func GoodItemAddStep(builder *flatbuffers.Builder, step int16) {
builder.PrependInt16Slot(8, step, 0)
}
func GoodItemAddPrice(builder *flatbuffers.Builder, price float32) {
builder.PrependFloat32Slot(9, price, 0.0)
}
func GoodItemAddTariff(builder *flatbuffers.Builder, tariff float32) {
builder.PrependFloat32Slot(10, tariff, 0.0)
}
func GoodItemAddCart(builder *flatbuffers.Builder, cart int64) {
builder.PrependInt64Slot(11, cart, 0)
}
func GoodItemAddStock(builder *flatbuffers.Builder, stock int16) {
builder.PrependInt16Slot(12, stock, 0)
}
func GoodItemEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT {
return builder.EndObject()
}

View File

@ -0,0 +1,75 @@
// Code generated by the FlatBuffers compiler. DO NOT EDIT.
package fbs
import (
flatbuffers "github.com/google/flatbuffers/go"
)
type GoodItems struct {
_tab flatbuffers.Table
}
func GetRootAsGoodItems(buf []byte, offset flatbuffers.UOffsetT) *GoodItems {
n := flatbuffers.GetUOffsetT(buf[offset:])
x := &GoodItems{}
x.Init(buf, n+offset)
return x
}
func FinishGoodItemsBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) {
builder.Finish(offset)
}
func GetSizePrefixedRootAsGoodItems(buf []byte, offset flatbuffers.UOffsetT) *GoodItems {
n := flatbuffers.GetUOffsetT(buf[offset+flatbuffers.SizeUint32:])
x := &GoodItems{}
x.Init(buf, n+offset+flatbuffers.SizeUint32)
return x
}
func FinishSizePrefixedGoodItemsBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) {
builder.FinishSizePrefixed(offset)
}
func (rcv *GoodItems) Init(buf []byte, i flatbuffers.UOffsetT) {
rcv._tab.Bytes = buf
rcv._tab.Pos = i
}
func (rcv *GoodItems) Table() flatbuffers.Table {
return rcv._tab
}
func (rcv *GoodItems) Items(obj *GoodItem, j int) bool {
o := flatbuffers.UOffsetT(rcv._tab.Offset(4))
if o != 0 {
x := rcv._tab.Vector(o)
x += flatbuffers.UOffsetT(j) * 4
x = rcv._tab.Indirect(x)
obj.Init(rcv._tab.Bytes, x)
return true
}
return false
}
func (rcv *GoodItems) ItemsLength() int {
o := flatbuffers.UOffsetT(rcv._tab.Offset(4))
if o != 0 {
return rcv._tab.VectorLen(o)
}
return 0
}
func GoodItemsStart(builder *flatbuffers.Builder) {
builder.StartObject(1)
}
func GoodItemsAddItems(builder *flatbuffers.Builder, items flatbuffers.UOffsetT) {
builder.PrependUOffsetTSlot(0, flatbuffers.UOffsetT(items), 0)
}
func GoodItemsStartItemsVector(builder *flatbuffers.Builder, numElems int) flatbuffers.UOffsetT {
return builder.StartVector(4, numElems, 4)
}
func GoodItemsEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT {
return builder.EndObject()
}

View File

@ -0,0 +1,140 @@
package fbs
import (
"sync"
"git.loyso.art/frx/eway/internal/entity"
flatbuffers "github.com/google/flatbuffers/go"
)
var builderPool = sync.Pool{
New: func() any {
builder := flatbuffers.NewBuilder(1024)
return builder
},
}
func getBuilder() *flatbuffers.Builder {
builder := builderPool.Get().(*flatbuffers.Builder)
return builder
}
func putBuilder(builder *flatbuffers.Builder) {
builder.Reset()
builderPool.Put(builder)
}
func MakeDomainGoodItems(in ...entity.GoodsItem) []byte {
builder := getBuilder()
defer putBuilder(builder)
offsets := make([]flatbuffers.UOffsetT, 0, len(in))
for _, item := range in {
inFB := makeDomainGoodItem(builder, item)
offsets = append(offsets, inFB)
}
GoodItemsStartItemsVector(builder, len(offsets))
for i := len(offsets) - 1; i >= 0; i-- {
builder.PrependUOffsetT(offsets[i])
}
goodItemsVec := builder.EndVector(len(offsets))
GoodItemsStart(builder)
GoodItemsAddItems(builder, goodItemsVec)
out := GoodItemsEnd(builder)
builder.Finish(out)
return builder.FinishedBytes()
}
func MakeDomainGoodItemFinished(in entity.GoodsItem) []byte {
builder := getBuilder()
defer putBuilder(builder)
item := makeDomainGoodItem(builder, in)
builder.Finish(item)
return builder.FinishedBytes()
}
func makeDomainGoodItem(builder *flatbuffers.Builder, in entity.GoodsItem) flatbuffers.UOffsetT {
sku := builder.CreateString(in.Articul)
photo := builder.CreateString(in.Photo)
name := builder.CreateString(in.Name)
desc := builder.CreateString(in.Description)
var cat flatbuffers.UOffsetT
if in.Category != "" {
cat = builder.CreateString(in.Category)
}
t := builder.CreateString(in.Type)
producer := builder.CreateString(in.Producer)
GoodItemStart(builder)
GoodItemAddSku(builder, sku)
GoodItemAddPhoto(builder, photo)
GoodItemAddName(builder, name)
GoodItemAddDescription(builder, desc)
if in.Category != "" {
GoodItemAddCategory(builder, cat)
}
GoodItemAddType(builder, t)
GoodItemAddProducer(builder, producer)
GoodItemAddPack(builder, int16(in.Pack))
GoodItemAddStep(builder, int16(in.Step))
GoodItemAddPrice(builder, float32(in.Price))
GoodItemAddTariff(builder, float32(in.TariffPrice))
GoodItemAddCart(builder, int64(in.Cart))
GoodItemAddStock(builder, int16(in.Stock))
return GoodItemEnd(builder)
}
func ParseGoodsItem(data []byte) (item entity.GoodsItem) {
itemFBS := GetRootAsGoodItem(data, 0)
item.Articul = string(itemFBS.Sku())
item.Photo = string(itemFBS.Photo())
item.Name = string(itemFBS.Name())
item.Description = string(itemFBS.Description())
if value := itemFBS.Category(); value != nil {
item.Category = string(value)
}
item.Type = string(itemFBS.Type())
item.Producer = string(itemFBS.Producer())
item.Pack = int(itemFBS.Pack())
item.Step = int(itemFBS.Step())
item.Price = float64(itemFBS.Price())
item.TariffPrice = float64(itemFBS.Tariff())
item.Cart = itemFBS.Cart()
item.Stock = int(itemFBS.Stock())
return item
}
func ParseCategory(data []byte) (category entity.Category) {
categoryFBS := GetRootAsCategory(data, 0)
category.ID = categoryFBS.Id()
category.Name = string(categoryFBS.Name())
return category
}
func MakeCategoryFinished(category entity.Category) []byte {
builder := getBuilder()
defer putBuilder(builder)
offset := makeCategory(builder, category)
builder.Finish(offset)
return builder.FinishedBytes()
}
func makeCategory(builder *flatbuffers.Builder, category entity.Category) flatbuffers.UOffsetT {
name := builder.CreateString(category.Name)
CategoryStart(builder)
CategoryAddId(builder, category.ID)
CategoryAddName(builder, name)
return CategoryEnd(builder)
}

View File

@ -0,0 +1,6 @@
package entity
type Category struct {
ID int64
Name string
}

11
internal/entity/error.go Normal file
View File

@ -0,0 +1,11 @@
package entity
type SimpleError string
func (err SimpleError) Error() string {
return string(err)
}
const (
ErrNotFound SimpleError = "not found"
)

122
internal/entity/gooditem.go Normal file
View File

@ -0,0 +1,122 @@
package entity
import (
"fmt"
"strconv"
"strings"
"unicode"
)
type GoodsItem struct {
Articul string `json:"sku"`
Photo string `json:"photo"`
Name string `json:"name"`
Description string `json:"description"`
Category string `json:"category"`
Type string `json:"type"`
Producer string `json:"producer"`
Pack int `json:"pack"`
Step int `json:"step"`
Price float64 `json:"price"`
TariffPrice float64 `json:"tariff_price"`
Cart int64 `json:"cart"`
Stock int `json:"stock"`
}
type GoodsItemRaw struct {
SKU string // or articul
Photo string
Certificate string
Configurator []string
Name []string // first element is name, second is description
Category string
Type string
Producer string
Storage string // ?
Pack string // like "20 шт."
Step string
Price string // float actually
TariffPrice string // float
SpecialOffers []any
Cart float64
Other string
}
type MappedGoodsRemnants map[int]GoodsRemnant
type GoodsRemnant [4]int32
func ExtractProductIDs(items []GoodsItem) (out []int) {
out = make([]int, 0, len(items))
for _, item := range items {
out = append(out, int(item.Cart))
}
return out
}
func MakeGoodsItem(
gi GoodsItemRaw,
remnants MappedGoodsRemnants,
) (out GoodsItem, err error) {
var name, desc string
var pack, step int
var price, tariffPrice float64
if len(gi.Name) >= 2 {
name = gi.Name[0]
desc = gi.Name[1]
}
fixSpace := func(in string) string {
return strings.ReplaceAll(in, " ", "")
}
price, err = strconv.ParseFloat(fixSpace(gi.Price), 64)
if err != nil {
return out, fmt.Errorf("parsing price: %w", err)
}
tariffPrice, err = strconv.ParseFloat(fixSpace(gi.TariffPrice), 64)
if err != nil {
return out, fmt.Errorf("parsing tariff_price: %w", err)
}
getDigits := func(in string) string {
var sb strings.Builder
sb.Grow(len(in))
for _, c := range in {
if unicode.IsDigit(c) {
sb.WriteRune(c)
continue
}
break
}
return sb.String()
}
const countTemplate = "%d"
_, err = fmt.Sscanf(getDigits(gi.Pack), countTemplate, &pack)
if err != nil {
return out, fmt.Errorf("getting pack count (%s): %w", gi.Pack, err)
}
_, err = fmt.Sscanf(getDigits(gi.Step), countTemplate, &step)
if err != nil {
return out, fmt.Errorf("getting step count (%s): %w", gi.Step, err)
}
return GoodsItem{
Articul: gi.SKU,
Photo: gi.Photo,
Name: name,
Description: desc,
Category: gi.Category,
Type: gi.Type,
Producer: gi.Producer,
Pack: pack,
Step: step,
Price: price,
TariffPrice: tariffPrice,
Cart: int64(gi.Cart),
Stock: int(remnants[int(gi.Cart)][0]),
}, nil
}

View File

@ -0,0 +1,23 @@
package entity
import "context"
type GoodsItemRepository interface {
ListIter(context.Context, int) (<-chan GoodsItem, error)
List(context.Context) ([]GoodsItem, error)
Get(context.Context, string) (GoodsItem, error)
GetByCart(context.Context, int64) (GoodsItem, error)
UpsertMany(context.Context, ...GoodsItem) ([]GoodsItem, error)
}
type CategoryRepository interface {
List(context.Context) ([]Category, error)
Get(context.Context, int64) (Category, error)
Create(ctx context.Context, name string) (Category, error)
}
type Mapper interface {
CategoryNameToID(context.Context, string) (int64, error)
}

View File

@ -0,0 +1,228 @@
package eway
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"reflect"
"strconv"
"strings"
"git.loyso.art/frx/eway/internal/entity"
"github.com/go-resty/resty/v2"
"github.com/rs/zerolog"
)
type client struct {
http *resty.Client
log zerolog.Logger
}
func NewClientWithSession(sessionid, sessionuser string, log zerolog.Logger) client {
cookies := []*http.Cookie{
{
Name: "session_id",
Value: sessionid,
Domain: "eway.elevel.ru",
HttpOnly: true,
},
{
Name: "session_user",
Value: sessionuser,
Domain: "eway.elevel.ru",
HttpOnly: true,
},
{
Name: "contract",
Value: "6101",
Domain: "eway.elevel.ru",
HttpOnly: true,
},
}
httpclient := resty.New().
SetDebug(false).
SetCookies(cookies).
SetBaseURL("https://eway.elevel.ru/api")
return client{
http: httpclient,
log: log.With().Str("client", "eway").Logger(),
}
}
type getGoodsNewOrder struct {
Column int
Dir string
}
type GetGoodsNewParams struct {
Draw int
Order getGoodsNewOrder
Start int
// 100 is max
Length int
SearchInStocks bool
RemmantsAtleast int
}
type getGoodsNewResponse struct {
Draw string `json:"draw"`
RecordsFiltered int `json:"recordsFiltered"`
RecordsTotal int `json:"recordsTotal"`
Data [][]any `json:"data"`
Replacement bool `json:"replacement"`
}
type goodRemnant [4]int
func parseGoodItem(items []any) (out entity.GoodsItemRaw) {
valueOf := reflect.ValueOf(&out).Elem()
typeOf := valueOf.Type()
numField := valueOf.NumField()
for i := 0; i < numField; i++ {
field := valueOf.Field(i)
fieldType := typeOf.Field(i)
if fieldType.Type.Kind() == reflect.Slice &&
field.Type().Elem().Kind() != reflect.String {
continue
}
itemValue := reflect.ValueOf(items[i])
if items[i] == nil ||
(itemValue.CanAddr() && itemValue.IsNil()) ||
itemValue.IsZero() {
continue
}
if field.Type().Kind() != itemValue.Type().Kind() {
continue
}
// Dirty hack that accepts only strings.
if field.Type().Kind() == reflect.Slice {
values := items[i].([]any)
elemSlice := reflect.MakeSlice(typeOf.Field(i).Type, 0, field.Len())
for _, value := range values {
valueStr, ok := value.(string)
if ok {
elemSlice = reflect.Append(elemSlice, reflect.ValueOf(valueStr))
}
}
field.Set(elemSlice)
continue
}
field.Set(itemValue)
}
return out
}
func mapResponseByOrder(response getGoodsNewResponse) (items []entity.GoodsItemRaw) {
for _, columns := range response.Data {
gi := parseGoodItem(columns)
items = append(items, gi)
}
return items
}
func (c client) GetGoodsRemnants(
ctx context.Context,
productIDs []int,
) (out entity.MappedGoodsRemnants, err error) {
if len(productIDs) == 0 {
return nil, nil
}
productsStr := make([]string, 0, len(productIDs))
for _, sku := range productIDs {
productsStr = append(productsStr, strconv.Itoa(sku))
}
resp, err := c.http.R().
SetFormData(map[string]string{
"products": strings.Join(productsStr, ","),
}).
SetDoNotParseResponse(true).
Post("/goods_remnants")
if err != nil {
return nil, fmt.Errorf("getting goods new: %w", err)
}
defer func() {
err = resp.RawBody().Close()
if err != nil {
c.log.Error().Err(err).Msg("unable to close body")
}
}()
if resp.IsError() {
return nil, errors.New("request was not successful")
}
data, err := io.ReadAll(resp.RawBody())
if err != nil {
return nil, fmt.Errorf("reading raw body: %w", err)
}
c.log.Debug().RawJSON("response", data).Msg("body prepared")
out = make(entity.MappedGoodsRemnants, len(productIDs))
err = json.NewDecoder(bytes.NewReader(data)).Decode(&out)
if err != nil {
return nil, fmt.Errorf("decoding body: %w", err)
}
return out, nil
}
func (c client) GetGoodsNew(
ctx context.Context,
params GetGoodsNewParams,
) (items []entity.GoodsItemRaw, total int, err error) {
var response getGoodsNewResponse
resp, err := c.http.R().
SetFormData(map[string]string{
"draw": strconv.Itoa(params.Draw),
"start": strconv.Itoa(params.Start),
"length": strconv.Itoa(params.Length),
"order[0][column]": "14",
"order[0][dir]": "desc",
"search[value]": "",
"search[regex]": "false",
"search_in_stocks": "on",
"remnants_atleast": "5",
}).
SetQueryParam("category_id", "0").
SetQueryParam("own", "26476"). // user id?
SetDoNotParseResponse(true).
Post("/goods_new")
if err != nil {
return nil, -1, fmt.Errorf("getting goods new: %w", err)
}
defer func() {
err = resp.RawBody().Close()
if err != nil {
c.log.Error().Err(err).Msg("unable to close body")
}
}()
if resp.IsError() {
return nil, -1, errors.New("request was not successful")
}
err = json.NewDecoder(resp.RawBody()).Decode(&response)
if err != nil {
return nil, -1, fmt.Errorf("decoding body: %w", err)
}
return mapResponseByOrder(response), response.RecordsTotal, nil
}

View File

@ -0,0 +1,40 @@
package inmemory
import (
"context"
"fmt"
"git.loyso.art/frx/eway/internal/entity"
"git.loyso.art/frx/eway/internal/storage"
)
type mapper struct {
categoryIDByName map[string]int64
base storage.Repository
}
func NewMapper(ctx context.Context, r storage.Repository) (*mapper, error) {
categories, err := r.Category().List(ctx)
if err != nil {
return nil, fmt.Errorf("listing categories: %w", err)
}
catsByName := make(map[string]int64, len(categories))
for _, category := range categories {
catsByName[category.Name] = category.ID
}
return &mapper{
categoryIDByName: catsByName,
base: r,
}, nil
}
func (m *mapper) CategoryNameToID(ctx context.Context, name string) (int64, error) {
out, ok := m.categoryIDByName[name]
if !ok {
return 0, entity.ErrNotFound
}
return out, nil
}

View File

@ -0,0 +1,8 @@
package storage
import "git.loyso.art/frx/eway/internal/entity"
type Repository interface {
Category() entity.CategoryRepository
GoodsItem() entity.GoodsItemRepository
}