able to get product

This commit is contained in:
Gitea
2023-11-30 00:39:51 +03:00
parent 606b94e35b
commit 414dc87091
19 changed files with 2204 additions and 77 deletions

View 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"`
}

View File

@ -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, &currentConfig)
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,
}
}

View File

@ -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)

View File

@ -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
View 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
}