able to get product
This commit is contained in:
13
cmd/dev/sravnicli/config.go
Normal file
13
cmd/dev/sravnicli/config.go
Normal file
@ -0,0 +1,13 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"git.loyso.art/frx/kurious/internal/common/config"
|
||||
)
|
||||
|
||||
var parseConfigOnce = sync.Once{}
|
||||
|
||||
type cliConfig struct {
|
||||
YDB config.YDB `json:"ydb"`
|
||||
}
|
||||
@ -2,11 +2,15 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
|
||||
"git.loyso.art/frx/kurious/internal/infrastructure/interfaceadapters/courses/sravni"
|
||||
"git.loyso.art/frx/kurious/internal/common/client/sravni"
|
||||
"git.loyso.art/frx/kurious/internal/common/config"
|
||||
"git.loyso.art/frx/kurious/internal/common/errors"
|
||||
"git.loyso.art/frx/kurious/internal/kurious/adapters"
|
||||
|
||||
"github.com/teris-io/cli"
|
||||
)
|
||||
@ -19,6 +23,75 @@ const (
|
||||
var limitOption = cli.NewOption("limit", "Limits amount of items to return").WithType(cli.TypeInt)
|
||||
var offsetOption = cli.NewOption("offset", "Offsets items to return").WithType(cli.TypeInt)
|
||||
|
||||
type actionWrapper func(next cli.Action) cli.Action
|
||||
|
||||
func buildCLICommand(f func() cli.Command) cliCommand {
|
||||
return makeCLICommand(f(), actionWrapperParseConfig)
|
||||
}
|
||||
|
||||
func makeCLICommand(cmd cli.Command, wrappers ...actionWrapper) cliCommand {
|
||||
return cliCommand{
|
||||
Command: cmd,
|
||||
actionWrappers: wrappers,
|
||||
}
|
||||
}
|
||||
|
||||
type cliCommand struct {
|
||||
cli.Command
|
||||
|
||||
actionWrappers []actionWrapper
|
||||
}
|
||||
|
||||
// WithCommand is disabled since it adds action wrappers and
|
||||
// not tested in other cases.
|
||||
func (c cliCommand) WithCommand(cli.Command) cli.Command {
|
||||
panic("wrapped in cliCommand is expected to be leaf command")
|
||||
}
|
||||
|
||||
func (c cliCommand) Action() cli.Action {
|
||||
out := c.Command.Action()
|
||||
if out == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, wrapper := range c.actionWrappers {
|
||||
out = wrapper(out)
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func actionWrapperParseConfig(next cli.Action) cli.Action {
|
||||
return func(args []string, options map[string]string) int {
|
||||
var result int
|
||||
parseConfigOnce.Do(func() {
|
||||
cfgpath, ok := options["config"]
|
||||
if !ok || cfgpath == "" {
|
||||
cfgpath = defaultConfigPath
|
||||
}
|
||||
|
||||
payload, err := os.ReadFile(cfgpath)
|
||||
if err != nil {
|
||||
slog.Error("unable to read config file", slog.Any("err", err))
|
||||
result = -1
|
||||
return
|
||||
}
|
||||
|
||||
err = json.Unmarshal(payload, ¤tConfig)
|
||||
if err != nil {
|
||||
slog.Error("unable to unmarshal config file", slog.Any("err", err))
|
||||
result = -1
|
||||
return
|
||||
}
|
||||
})
|
||||
if result != 0 {
|
||||
return result
|
||||
}
|
||||
|
||||
return next(args, options)
|
||||
}
|
||||
}
|
||||
|
||||
func makeLogger(options map[string]string) *slog.Logger {
|
||||
level := slog.LevelInfo
|
||||
if _, ok := options[debugOptName]; ok {
|
||||
@ -48,3 +121,41 @@ func makeSravniClient(ctx context.Context, log *slog.Logger, options map[string]
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
type baseAction struct {
|
||||
ctx context.Context
|
||||
log *slog.Logger
|
||||
}
|
||||
|
||||
func (ba *baseAction) getYDBConnection() (*adapters.YDBConnection, error) {
|
||||
if currentConfig.YDB == (config.YDB{}) {
|
||||
return nil, errors.SimpleError("no ydb config set")
|
||||
}
|
||||
|
||||
ydbConn, err := adapters.NewYDBConnection(ba.ctx, currentConfig.YDB, ba.log.With(slog.String("db", "ydb")))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("making new ydb course repository: %w", err)
|
||||
}
|
||||
|
||||
return ydbConn, nil
|
||||
}
|
||||
|
||||
func (ba *baseAction) parse(_ []string, options map[string]string) (err error) {
|
||||
ba.log = makeLogger(options).With(slog.String("component", "action"))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ba *baseAction) handle() error {
|
||||
return errors.ErrNotImplemented
|
||||
}
|
||||
|
||||
func (ba *baseAction) context() context.Context {
|
||||
return ba.ctx
|
||||
}
|
||||
|
||||
func newBaseAction(ctx context.Context) *baseAction {
|
||||
return &baseAction{
|
||||
ctx: ctx,
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,24 +12,18 @@ import (
|
||||
"github.com/teris-io/cli"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultConfigPath = "config_cli.json"
|
||||
)
|
||||
|
||||
var defaultOutput = os.Stdout
|
||||
var currentConfig = cliConfig{}
|
||||
|
||||
func main() {
|
||||
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
|
||||
defer cancel()
|
||||
|
||||
log := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
|
||||
Level: slog.LevelDebug,
|
||||
ReplaceAttr: func(_ []string, a slog.Attr) slog.Attr {
|
||||
if a.Key == slog.TimeKey {
|
||||
a.Value = slog.Int64Value(a.Value.Time().Unix())
|
||||
}
|
||||
|
||||
return a
|
||||
},
|
||||
}))
|
||||
|
||||
ec, err := app(ctx, log)
|
||||
ec, err := app(ctx)
|
||||
if err != nil {
|
||||
slog.ErrorContext(ctx, "unable to run app", slog.Any("error", err))
|
||||
}
|
||||
@ -76,17 +70,20 @@ func setupCLI(ctx context.Context) cli.App {
|
||||
WithCommand(mainPageState)
|
||||
|
||||
apiCategory := setupAPICommand(ctx)
|
||||
ydbCategory := setupYDBCommand(ctx)
|
||||
description := fmt.Sprintf("sravni dev cli %s (%s)", kurious.Version(), kurious.Commit())
|
||||
cliApp := cli.New(description).
|
||||
WithOption(cli.NewOption("verbose", "Verbose execution").WithChar('v').WithType(cli.TypeBool)).
|
||||
WithOption(cli.NewOption("json", "JSON outpu format").WithType(cli.TypeBool)).
|
||||
WithOption(cli.NewOption("config", "Path to config").WithChar('c').WithType(cli.TypeString)).
|
||||
WithCommand(mainCategory).
|
||||
WithCommand(apiCategory)
|
||||
WithCommand(apiCategory).
|
||||
WithCommand(ydbCategory)
|
||||
|
||||
return cliApp
|
||||
}
|
||||
|
||||
func app(ctx context.Context, log *slog.Logger) (exitCode int, err error) {
|
||||
func app(ctx context.Context) (exitCode int, err error) {
|
||||
devCLI := setupCLI(ctx)
|
||||
exitCode = devCLI.Run(os.Args, defaultOutput)
|
||||
|
||||
|
||||
@ -6,8 +6,8 @@ import (
|
||||
"log/slog"
|
||||
"strconv"
|
||||
|
||||
"git.loyso.art/frx/kurious/internal/domain"
|
||||
"git.loyso.art/frx/kurious/internal/infrastructure/interfaceadapters/courses/sravni"
|
||||
"git.loyso.art/frx/kurious/internal/common/client/sravni"
|
||||
"git.loyso.art/frx/kurious/internal/common/errors"
|
||||
|
||||
"github.com/teris-io/cli"
|
||||
)
|
||||
@ -25,12 +25,14 @@ func setupAPICommand(ctx context.Context) cli.Command {
|
||||
WithChar('t').
|
||||
WithType(cli.TypeString)
|
||||
|
||||
apiEducationListProducts := cli.NewCommand("list_products", "List products by some filters").
|
||||
WithOption(learningTypeOpt).
|
||||
WithOption(courseThematic).
|
||||
WithOption(limitOption).
|
||||
WithOption(offsetOption).
|
||||
WithAction(newListProductAction(ctx))
|
||||
apiEducationListProducts := buildCLICommand(func() cli.Command {
|
||||
return cli.NewCommand("list_products", "List products by some filters").
|
||||
WithOption(learningTypeOpt).
|
||||
WithOption(courseThematic).
|
||||
WithOption(limitOption).
|
||||
WithOption(offsetOption).
|
||||
WithAction(newListProductAction(ctx))
|
||||
})
|
||||
|
||||
apiEducation := cli.NewCommand("education", "Education related category").
|
||||
WithCommand(apiEducationListProducts)
|
||||
@ -64,36 +66,6 @@ func asCLIAction(a action) cli.Action {
|
||||
}
|
||||
}
|
||||
|
||||
type baseAction struct {
|
||||
ctx context.Context
|
||||
client sravni.Client
|
||||
log *slog.Logger
|
||||
}
|
||||
|
||||
func (ba *baseAction) parse(_ []string, options map[string]string) (err error) {
|
||||
ba.log = makeLogger(options).With(slog.String("component", "action"))
|
||||
ba.client, err = makeSravniClient(ba.ctx, ba.log, options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ba *baseAction) handle() error {
|
||||
return domain.ErrNotImplemented
|
||||
}
|
||||
|
||||
func (ba baseAction) context() context.Context {
|
||||
return ba.ctx
|
||||
}
|
||||
|
||||
func newBaseAction(ctx context.Context) *baseAction {
|
||||
return &baseAction{
|
||||
ctx: ctx,
|
||||
}
|
||||
}
|
||||
|
||||
type listProductsActionParams struct {
|
||||
learningType string
|
||||
courseThematic string
|
||||
@ -104,6 +76,7 @@ type listProductsActionParams struct {
|
||||
type listProductsAction struct {
|
||||
*baseAction
|
||||
|
||||
client sravni.Client
|
||||
params listProductsActionParams
|
||||
}
|
||||
|
||||
@ -125,11 +98,11 @@ func (a *listProductsAction) parse(args []string, options map[string]string) err
|
||||
|
||||
a.params.learningType, ok = options[learningTypeOptName]
|
||||
if !ok {
|
||||
return domain.SimpleError("learning_type is empty")
|
||||
return errors.SimpleError("learning_type is empty")
|
||||
}
|
||||
a.params.courseThematic, ok = options[courseThematicOptName]
|
||||
if !ok {
|
||||
return domain.SimpleError("course_thematic is empty")
|
||||
return errors.SimpleError("course_thematic is empty")
|
||||
}
|
||||
|
||||
if value, ok := options[limitOption.Key()]; ok {
|
||||
@ -139,6 +112,13 @@ func (a *listProductsAction) parse(args []string, options map[string]string) err
|
||||
a.params.offset, _ = strconv.Atoi(value)
|
||||
}
|
||||
|
||||
client, err := makeSravniClient(a.ctx, a.log, options)
|
||||
if err != nil {
|
||||
return fmt.Errorf("making sravni client: %w", err)
|
||||
}
|
||||
|
||||
a.client = client
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
132
cmd/dev/sravnicli/ydb.go
Normal file
132
cmd/dev/sravnicli/ydb.go
Normal file
@ -0,0 +1,132 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
stderrors "errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
|
||||
"git.loyso.art/frx/kurious/internal/common/errors"
|
||||
"git.loyso.art/frx/kurious/internal/common/xcontext"
|
||||
"github.com/teris-io/cli"
|
||||
)
|
||||
|
||||
func setupYDBCommand(ctx context.Context) cli.Command {
|
||||
migrationApply := buildCLICommand(func() cli.Command {
|
||||
return cli.NewCommand("apply", "Applies all known migrations").
|
||||
WithAction(newYDBMigrateApplyAction(ctx))
|
||||
})
|
||||
|
||||
migration := cli.NewCommand("migration", "Migration commands").
|
||||
WithCommand(migrationApply)
|
||||
|
||||
coursesGet := buildCLICommand(func() cli.Command {
|
||||
return cli.NewCommand("get", "Fetches one or more courses").
|
||||
WithArg(cli.NewArg("ids", "List of course ids").AsOptional()).
|
||||
WithAction(newYDBCoursesGetAction(ctx))
|
||||
})
|
||||
|
||||
courses := cli.NewCommand("courses", "Courses commands").
|
||||
WithCommand(coursesGet)
|
||||
|
||||
return cli.NewCommand("ydb", "YDB related actions").
|
||||
WithCommand(migration).
|
||||
WithCommand(courses)
|
||||
}
|
||||
|
||||
type ydbMigrateApplyAction struct {
|
||||
*baseAction
|
||||
}
|
||||
|
||||
func newYDBMigrateApplyAction(ctx context.Context) cli.Action {
|
||||
action := &ydbMigrateApplyAction{
|
||||
baseAction: newBaseAction(ctx),
|
||||
}
|
||||
|
||||
return asCLIAction(action)
|
||||
}
|
||||
|
||||
func (a *ydbMigrateApplyAction) handle() error {
|
||||
ydbConn, err := a.getYDBConnection()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
errClose := ydbConn.Close()
|
||||
if errClose != nil {
|
||||
xcontext.LogError(a.ctx, a.log, "unable to close repository", slog.Any("error", err))
|
||||
if err == nil {
|
||||
err = errClose
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
repository := ydbConn.CourseRepository()
|
||||
err = repository.CreateCourseTable(a.ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating course table: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type ydbCoursesGetAction struct {
|
||||
*baseAction
|
||||
|
||||
courseIDs []string
|
||||
}
|
||||
|
||||
func newYDBCoursesGetAction(ctx context.Context) cli.Action {
|
||||
action := &ydbCoursesGetAction{
|
||||
baseAction: newBaseAction(ctx),
|
||||
}
|
||||
|
||||
return asCLIAction(action)
|
||||
}
|
||||
|
||||
func (a *ydbCoursesGetAction) parse(params []string, options map[string]string) error {
|
||||
err := a.baseAction.parse(params, options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(params) == 0 {
|
||||
return errors.NewValidationError("params", "no course ids provided")
|
||||
}
|
||||
|
||||
a.courseIDs = make([]string, len(params))
|
||||
copy(a.courseIDs, params)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *ydbCoursesGetAction) handle() error {
|
||||
ydbConn, err := a.getYDBConnection()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
errClose := ydbConn.Close()
|
||||
if errClose != nil {
|
||||
xcontext.LogError(a.ctx, a.log, "unable to close repository", slog.Any("error", err))
|
||||
if err == nil {
|
||||
err = errClose
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
repository := ydbConn.CourseRepository()
|
||||
for _, courseID := range a.courseIDs {
|
||||
course, err := repository.Get(a.ctx, courseID)
|
||||
if err != nil && !stderrors.Is(err, errors.ErrNotFound) {
|
||||
return fmt.Errorf("creating course table: %w", err)
|
||||
} else if stderrors.Is(err, errors.ErrNotFound) {
|
||||
xcontext.LogWarn(a.ctx, a.log, "course not found", slog.String("id", courseID))
|
||||
continue
|
||||
}
|
||||
|
||||
xcontext.LogInfo(a.ctx, a.log, "fetched course", slog.Any("item", course))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user