add filters to cli and client
This commit is contained in:
@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"git.loyso.art/frx/kurious/internal/common/client/sravni"
|
"git.loyso.art/frx/kurious/internal/common/client/sravni"
|
||||||
"git.loyso.art/frx/kurious/internal/common/errors"
|
"git.loyso.art/frx/kurious/internal/common/errors"
|
||||||
@ -35,9 +36,17 @@ func setupAPICommand(ctx context.Context) cli.Command {
|
|||||||
WithOption(offsetOption).
|
WithOption(offsetOption).
|
||||||
WithAction(newListProductAction(ctx))
|
WithAction(newListProductAction(ctx))
|
||||||
})
|
})
|
||||||
|
apiEducationFilterCount := buildCLICommand(func() cli.Command {
|
||||||
|
return cli.NewCommand("filter_count", "Loads counts of returned entities").
|
||||||
|
WithOption(learningTypeOpt).
|
||||||
|
WithOption(courseThematic).
|
||||||
|
WithOption(learningSelectionOpt).
|
||||||
|
WithAction(newProductsFilterCountAction(ctx))
|
||||||
|
})
|
||||||
|
|
||||||
apiEducation := cli.NewCommand("education", "Education related category").
|
apiEducation := cli.NewCommand("education", "Education related category").
|
||||||
WithCommand(apiEducationListProducts)
|
WithCommand(apiEducationListProducts).
|
||||||
|
WithCommand(apiEducationFilterCount)
|
||||||
|
|
||||||
return cli.NewCommand("api", "Interaction with API").
|
return cli.NewCommand("api", "Interaction with API").
|
||||||
WithCommand(apiEducation)
|
WithCommand(apiEducation)
|
||||||
@ -127,7 +136,7 @@ func (a *listProductsAction) parse(args []string, options map[string]string) err
|
|||||||
func (a *listProductsAction) handle() error {
|
func (a *listProductsAction) handle() error {
|
||||||
params := sravni.ListEducationProductsParams{
|
params := sravni.ListEducationProductsParams{
|
||||||
LearningType: a.params.learningType,
|
LearningType: a.params.learningType,
|
||||||
CoursesThematics: a.params.courseThematic,
|
CoursesThematics: []string{a.params.courseThematic},
|
||||||
Limit: a.params.limit,
|
Limit: a.params.limit,
|
||||||
Offset: a.params.offset,
|
Offset: a.params.offset,
|
||||||
}
|
}
|
||||||
@ -140,3 +149,65 @@ func (a *listProductsAction) handle() error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type productsFilterCountActionParams struct {
|
||||||
|
learningType string
|
||||||
|
courseThematic []string
|
||||||
|
learningSelectionType string
|
||||||
|
}
|
||||||
|
|
||||||
|
type productsFilterCountAction struct {
|
||||||
|
*baseAction
|
||||||
|
|
||||||
|
client sravni.Client
|
||||||
|
params productsFilterCountActionParams
|
||||||
|
}
|
||||||
|
|
||||||
|
func newProductsFilterCountAction(ctx context.Context) cli.Action {
|
||||||
|
action := &productsFilterCountAction{
|
||||||
|
baseAction: newBaseAction(ctx),
|
||||||
|
}
|
||||||
|
|
||||||
|
return asCLIAction(action)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *productsFilterCountAction) parse(args []string, options map[string]string) error {
|
||||||
|
err := a.baseAction.parse(args, options)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
a.params.learningType, ok = options[learningTypeOptName]
|
||||||
|
if !ok {
|
||||||
|
return errors.SimpleError(learningTypeOptName + " is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
a.params.courseThematic = strings.Split(options[courseThematicOptName], ",")
|
||||||
|
a.params.learningSelectionType = options[learningTypeSelectionOptName]
|
||||||
|
|
||||||
|
client, err := makeSravniClient(a.ctx, a.log, options)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("making sravni client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
a.client = client
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *productsFilterCountAction) handle() error {
|
||||||
|
params := sravni.ListEducationProductsParams{
|
||||||
|
LearningType: a.params.learningType,
|
||||||
|
CoursesThematics: a.params.courseThematic,
|
||||||
|
}
|
||||||
|
result, err := a.client.ListEducationalProductsFilterCount(a.ctx, params)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("listing education products: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
a.log.InfoContext(a.ctx, "list education products result", slog.Any("result", result))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@ -29,7 +29,11 @@ type Client interface {
|
|||||||
ListEducationalProducts(
|
ListEducationalProducts(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
params ListEducationProductsParams,
|
params ListEducationProductsParams,
|
||||||
) (result ListEducationProductsResponse, err error)
|
) (result listEducationProductsResponse, err error)
|
||||||
|
ListEducationalProductsFilterCount(
|
||||||
|
ctx context.Context,
|
||||||
|
params ListEducationProductsParams,
|
||||||
|
) (result ProductsFilterCount, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClient(ctx context.Context, log *slog.Logger, debug bool) (c *client, err error) {
|
func NewClient(ctx context.Context, log *slog.Logger, debug bool) (c *client, err error) {
|
||||||
@ -75,7 +79,12 @@ func (c *client) GetMainPageState() (*PageState, error) {
|
|||||||
|
|
||||||
type ListEducationProductsParams struct {
|
type ListEducationProductsParams struct {
|
||||||
LearningType string
|
LearningType string
|
||||||
CoursesThematics string
|
CoursesThematics []string
|
||||||
|
CourseGraphics []string
|
||||||
|
CourseLevels []string
|
||||||
|
CourseFormats []string
|
||||||
|
CourseDurations []string
|
||||||
|
CourseTypes []string
|
||||||
|
|
||||||
SortBy string
|
SortBy string
|
||||||
Limit int
|
Limit int
|
||||||
@ -125,7 +134,7 @@ const (
|
|||||||
FilterGraphicTerm FilterGraphic = "courseTimeTermNew"
|
FilterGraphicTerm FilterGraphic = "courseTimeTermNew"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ListEducationProductsRequest struct {
|
type listEducationProductsRequest struct {
|
||||||
Fingerprint string `json:"fingerPrint,omitempty"`
|
Fingerprint string `json:"fingerPrint,omitempty"`
|
||||||
ProductName string `json:"productName,omitempty"`
|
ProductName string `json:"productName,omitempty"`
|
||||||
Location string `json:"location"`
|
Location string `json:"location"`
|
||||||
@ -163,7 +172,7 @@ type ListEducationProductsRequest struct {
|
|||||||
SortDirection string `json:"sortDirection"`
|
SortDirection string `json:"sortDirection"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ListEducationProductsResponse struct {
|
type listEducationProductsResponse struct {
|
||||||
Items []Course `json:"items"`
|
Items []Course `json:"items"`
|
||||||
Organizations map[string]Organization `json:"organizations"`
|
Organizations map[string]Organization `json:"organizations"`
|
||||||
|
|
||||||
@ -174,7 +183,7 @@ type ListEducationProductsResponse struct {
|
|||||||
func (c *client) ListEducationalProducts(
|
func (c *client) ListEducationalProducts(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
params ListEducationProductsParams,
|
params ListEducationProductsParams,
|
||||||
) (result ListEducationProductsResponse, err error) {
|
) (result listEducationProductsResponse, err error) {
|
||||||
const urlPath = "/v1/education/products"
|
const urlPath = "/v1/education/products"
|
||||||
const defaultLimit = 1
|
const defaultLimit = 1
|
||||||
const defaultSortProp = "advertising.position"
|
const defaultSortProp = "advertising.position"
|
||||||
@ -186,15 +195,17 @@ func (c *client) ListEducationalProducts(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !c.validLearningTypes.hasValue(params.LearningType) {
|
if !c.validLearningTypes.hasValue(params.LearningType) {
|
||||||
return result, errors.NewValidationError("learning_type", "bad value")
|
return result, errors.NewValidationError("learning_type", "unknown value")
|
||||||
}
|
}
|
||||||
if params.CoursesThematics != "" && !c.validCourseThematics.hasValue(params.CoursesThematics) {
|
for _, ct := range params.CoursesThematics {
|
||||||
return result, errors.NewValidationError("courses_thematics", "bad value")
|
if !c.validCourseThematics.hasValue(ct) {
|
||||||
|
return result, errors.NewValidationError("courses_thematics", "unknown value "+ct)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reqParams := ListEducationProductsRequest{
|
reqParams := listEducationProductsRequest{
|
||||||
LearningType: valueAsArray(params.LearningType),
|
LearningType: valueAsArray(params.LearningType),
|
||||||
CoursesThematics: valueAsArray(params.CoursesThematics),
|
CoursesThematics: params.CoursesThematics,
|
||||||
ProductName: productName,
|
ProductName: productName,
|
||||||
Fields: defaultProductFields,
|
Fields: defaultProductFields,
|
||||||
SortProperty: defaultSortProp, // mayber sort by price?
|
SortProperty: defaultSortProp, // mayber sort by price?
|
||||||
@ -212,12 +223,11 @@ func (c *client) ListEducationalProducts(
|
|||||||
Offset: params.Offset,
|
Offset: params.Offset,
|
||||||
}
|
}
|
||||||
|
|
||||||
req := c.http.R().
|
resp, err := c.http.R().
|
||||||
SetBody(reqParams).
|
SetBody(reqParams).
|
||||||
SetResult(&result).
|
SetResult(&result).
|
||||||
EnableTrace()
|
EnableTrace().
|
||||||
|
Post(c.makeEducationURL(urlPath))
|
||||||
resp, err := req.Post(c.makeEducationURL(urlPath))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return result, fmt.Errorf("making request: %w", err)
|
return result, fmt.Errorf("making request: %w", err)
|
||||||
}
|
}
|
||||||
@ -229,6 +239,88 @@ func (c *client) ListEducationalProducts(
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type educationProductFilterCountRequest struct {
|
||||||
|
Filters educationProductFilter `json:"filters"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type educationProductFilter struct {
|
||||||
|
AdvertisingOnly bool `json:"advertisingOnly"`
|
||||||
|
Location string `json:"location"`
|
||||||
|
LearningTypes []string `json:"learningTypes"`
|
||||||
|
CoursesThematics []string `json:"coursesThematics"`
|
||||||
|
CourseGraphics []string `json:"courseGraphics"`
|
||||||
|
CourseLevels []string `json:"courseLevels"`
|
||||||
|
CourseFormats []string `json:"courseFormats"`
|
||||||
|
CourseDurations []string `json:"courseDurations"`
|
||||||
|
CourseTypes []string `json:"courseTypes"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type boolableDict map[int]int
|
||||||
|
type nameableDict map[string]int
|
||||||
|
|
||||||
|
type ProductsFilterCount struct {
|
||||||
|
IsCourseProfession boolableDict `json:"isCourseProfession"` // 0: count, 1: count eq to false + true
|
||||||
|
CourseLevels nameableDict `json:"courseLevels"`
|
||||||
|
CourseGraphics nameableDict `json:"courseGraphics"`
|
||||||
|
OrganizationIDs nameableDict `json:"organizationIds"`
|
||||||
|
HasTrialPeriod boolableDict `json:"hasTrialPeriod"`
|
||||||
|
HasMentor boolableDict `json:"hasMentor"`
|
||||||
|
HasJobGuarantee boolableDict `json:"hasJobGuarantee"`
|
||||||
|
CourseFormats nameableDict `json:"courseFormats"`
|
||||||
|
CourseDurations nameableDict `json:"courseDurations"`
|
||||||
|
CoursesThematics nameableDict `json:"coursesThematics"`
|
||||||
|
LearningTypes nameableDict `json:"learningTypes"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) ListEducationalProductsFilterCount(
|
||||||
|
ctx context.Context,
|
||||||
|
params ListEducationProductsParams,
|
||||||
|
) (result ProductsFilterCount, err error) {
|
||||||
|
const urlPath = "/v2/education/prodicts/fitter/count"
|
||||||
|
if err = c.checkClientInited(); err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !c.validLearningTypes.hasValue(params.LearningType) {
|
||||||
|
return result, errors.NewValidationError("learning_type", "unknown value")
|
||||||
|
}
|
||||||
|
for _, ct := range params.CoursesThematics {
|
||||||
|
if !c.validCourseThematics.hasValue(ct) {
|
||||||
|
return result, errors.NewValidationError("courses_thematics", "unknown value "+ct)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reqParams := educationProductFilterCountRequest{
|
||||||
|
Filters: educationProductFilter{
|
||||||
|
AdvertisingOnly: false,
|
||||||
|
Location: "",
|
||||||
|
LearningTypes: valueAsArray(params.LearningType),
|
||||||
|
CoursesThematics: params.CoursesThematics,
|
||||||
|
CourseGraphics: params.CourseGraphics,
|
||||||
|
CourseLevels: params.CourseLevels,
|
||||||
|
CourseFormats: params.CourseFormats,
|
||||||
|
CourseDurations: params.CourseDurations,
|
||||||
|
CourseTypes: params.CourseTypes,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var respData DataContainer[ProductsFilterCount]
|
||||||
|
resp, err := c.http.R().
|
||||||
|
SetBody(reqParams).
|
||||||
|
SetResult(&result).
|
||||||
|
EnableTrace().
|
||||||
|
Post(c.makeEducationURL(urlPath))
|
||||||
|
if err != nil {
|
||||||
|
return result, fmt.Errorf("making request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.IsError() {
|
||||||
|
return result, fmt.Errorf("bad status code %d: %w", resp.StatusCode(), errors.ErrUnexpectedStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
return respData.Data, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *client) makeEducationURL(path string) string {
|
func (c *client) makeEducationURL(path string) string {
|
||||||
if c.cachedMainPageInfo == nil {
|
if c.cachedMainPageInfo == nil {
|
||||||
return ""
|
return ""
|
||||||
|
|||||||
@ -12,6 +12,6 @@ func (NoopClient) GetMainPageState() (*PageState, error) {
|
|||||||
return nil, errors.ErrNotImplemented
|
return nil, errors.ErrNotImplemented
|
||||||
}
|
}
|
||||||
|
|
||||||
func (NoopClient) ListEducationalProducts(context.Context, ListEducationProductsParams) (ListEducationProductsResponse, error) {
|
func (NoopClient) ListEducationalProducts(context.Context, ListEducationProductsParams) (listEducationProductsResponse, error) {
|
||||||
return ListEducationProductsResponse{}, errors.ErrNotImplemented
|
return listEducationProductsResponse{}, errors.ErrNotImplemented
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user