283 lines
7.1 KiB
Go
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
|
|
}
|