Files
kurious/cmd/dev/sravnicli/ydb.go
2023-12-04 01:24:24 +03:00

283 lines
7.1 KiB
Go

package main
import (
"context"
stderrors "errors"
"fmt"
"log/slog"
"strconv"
"time"
"git.loyso.art/frx/kurious/internal/common/errors"
"git.loyso.art/frx/kurious/internal/common/xcontext"
"git.loyso.art/frx/kurious/internal/kurious/domain"
"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().WithType(cli.TypeString)).
WithAction(newYDBCoursesGetAction(ctx))
})
coursesCreate := buildCLICommand(func() cli.Command {
return cli.NewCommand("create", "Creates course").
WithOption(cli.NewOption("generate-id", "Generates id").WithType(cli.TypeBool)).
WithOption(cli.NewOption("name", "Sets course name")).
WithOption(cli.NewOption("source-name", "Sets source name")).
WithOption(cli.NewOption("organization-id", "Sets organization id")).
WithOption(cli.NewOption("origin-link", "Sets origin link")).
WithOption(cli.NewOption("image-link", "Sets image link")).
WithOption(cli.NewOption("desc", "Sets description link")).
WithOption(cli.NewOption("full-price", "Sets full price").WithType(cli.TypeNumber)).
WithOption(cli.NewOption("discount", "Sets discount").WithType(cli.TypeNumber)).
WithOption(cli.NewOption("duration", "Sets duration")).
WithOption(cli.NewOption("starts-at", "Sets starts at")).
WithAction(NewYDBCoursesCreateAction(ctx))
})
courses := cli.NewCommand("courses", "Courses commands").
WithCommand(coursesGet).
WithCommand(coursesCreate)
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
}
func NewYDBCoursesCreateAction(ctx context.Context) cli.Action {
action := &ydbCoursesCreateAction{
baseAction: newBaseAction(ctx),
}
return asCLIAction(action)
}
type ydbCoursesCreateAction struct {
*baseAction
generateID bool
id string
name string
sourceName string
organizationID string
originLink string
imageLink string
description string
fullPrice float64
discount float64
duration time.Duration
startsAt time.Time
}
func (a *ydbCoursesCreateAction) getCreateParams() domain.CreateCourseParams {
if a.generateID {
a.id = strconv.FormatInt(time.Now().Unix(), 10)
}
out := domain.CreateCourseParams{
ID: a.id,
Name: a.name,
SourceType: domain.SourceTypeParsed,
OrganizationID: a.organizationID,
OriginLink: a.originLink,
ImageLink: a.imageLink,
Description: a.description,
FullPrice: a.fullPrice,
Discount: a.discount,
Duration: a.duration,
StartsAt: a.startsAt,
}
if a.sourceName != "" {
out.SourceName.Set(a.sourceName)
}
return out
}
func (a *ydbCoursesCreateAction) parse(params []string, options map[string]string) error {
err := a.baseAction.parse(params, options)
if err != nil {
return err
}
if err != nil {
return fmt.Errorf("parsing full-price: %w", err)
}
a.discount, err = strconv.ParseFloat(options["discount"], 64)
if err != nil {
return fmt.Errorf("parsing discount: %w", err)
}
a.fullPrice, err = strconv.ParseFloat(options["full-price"], 64)
if err != nil {
return fmt.Errorf("parsing full-price: %w", err)
}
if genid, ok := options["generate-id"]; ok {
a.generateID, err = strconv.ParseBool(genid)
if err != nil {
return fmt.Errorf("parsing generate-id: %w", err)
}
}
if duration, ok := options["duration"]; ok {
a.duration, err = time.ParseDuration(duration)
if err != nil {
return fmt.Errorf("parsing duration: %w", err)
}
}
if startsAt, ok := options["starts-at"]; ok {
a.startsAt, err = time.Parse(time.RFC3339, startsAt)
if err != nil {
return fmt.Errorf("parsing time: %w", err)
}
}
a.name = options["name"]
a.sourceName = options["source-name"]
a.organizationID = options["organization-id"]
a.originLink = options["origin-link"]
a.imageLink = options["image-link"]
a.description = options["desc"]
return nil
}
func (a *ydbCoursesCreateAction) 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
}
}
}()
params := a.getCreateParams()
repository := ydbConn.CourseRepository()
_, err = repository.Create(a.ctx, params)
if err != nil {
return fmt.Errorf("creating course: %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()
courses := make([]domain.Course, 0, len(a.courseIDs))
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.String("id", courseID))
courses = append(courses, course)
}
for _, course := range courses {
err = a.out(course)
if err != nil {
xcontext.LogError(a.ctx, a.log, "unable to write course", slog.Any("error", err))
}
}
return nil
}