add filters to cli and client

This commit is contained in:
Aleksandr Trushkin
2023-12-14 20:40:35 +03:00
parent 30c361eb34
commit e80d5145cf
3 changed files with 181 additions and 18 deletions

View File

@ -5,6 +5,7 @@ import (
"fmt"
"log/slog"
"strconv"
"strings"
"git.loyso.art/frx/kurious/internal/common/client/sravni"
"git.loyso.art/frx/kurious/internal/common/errors"
@ -35,9 +36,17 @@ func setupAPICommand(ctx context.Context) cli.Command {
WithOption(offsetOption).
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").
WithCommand(apiEducationListProducts)
WithCommand(apiEducationListProducts).
WithCommand(apiEducationFilterCount)
return cli.NewCommand("api", "Interaction with API").
WithCommand(apiEducation)
@ -127,7 +136,7 @@ func (a *listProductsAction) parse(args []string, options map[string]string) err
func (a *listProductsAction) handle() error {
params := sravni.ListEducationProductsParams{
LearningType: a.params.learningType,
CoursesThematics: a.params.courseThematic,
CoursesThematics: []string{a.params.courseThematic},
Limit: a.params.limit,
Offset: a.params.offset,
}
@ -140,3 +149,65 @@ func (a *listProductsAction) handle() error {
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
}

View File

@ -29,7 +29,11 @@ type Client interface {
ListEducationalProducts(
ctx context.Context,
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) {
@ -75,7 +79,12 @@ func (c *client) GetMainPageState() (*PageState, error) {
type ListEducationProductsParams struct {
LearningType string
CoursesThematics string
CoursesThematics []string
CourseGraphics []string
CourseLevels []string
CourseFormats []string
CourseDurations []string
CourseTypes []string
SortBy string
Limit int
@ -125,7 +134,7 @@ const (
FilterGraphicTerm FilterGraphic = "courseTimeTermNew"
)
type ListEducationProductsRequest struct {
type listEducationProductsRequest struct {
Fingerprint string `json:"fingerPrint,omitempty"`
ProductName string `json:"productName,omitempty"`
Location string `json:"location"`
@ -163,7 +172,7 @@ type ListEducationProductsRequest struct {
SortDirection string `json:"sortDirection"`
}
type ListEducationProductsResponse struct {
type listEducationProductsResponse struct {
Items []Course `json:"items"`
Organizations map[string]Organization `json:"organizations"`
@ -174,7 +183,7 @@ type ListEducationProductsResponse struct {
func (c *client) ListEducationalProducts(
ctx context.Context,
params ListEducationProductsParams,
) (result ListEducationProductsResponse, err error) {
) (result listEducationProductsResponse, err error) {
const urlPath = "/v1/education/products"
const defaultLimit = 1
const defaultSortProp = "advertising.position"
@ -186,15 +195,17 @@ func (c *client) ListEducationalProducts(
}
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) {
return result, errors.NewValidationError("courses_thematics", "bad value")
for _, ct := range params.CoursesThematics {
if !c.validCourseThematics.hasValue(ct) {
return result, errors.NewValidationError("courses_thematics", "unknown value "+ct)
}
}
reqParams := ListEducationProductsRequest{
reqParams := listEducationProductsRequest{
LearningType: valueAsArray(params.LearningType),
CoursesThematics: valueAsArray(params.CoursesThematics),
CoursesThematics: params.CoursesThematics,
ProductName: productName,
Fields: defaultProductFields,
SortProperty: defaultSortProp, // mayber sort by price?
@ -212,12 +223,11 @@ func (c *client) ListEducationalProducts(
Offset: params.Offset,
}
req := c.http.R().
resp, err := c.http.R().
SetBody(reqParams).
SetResult(&result).
EnableTrace()
resp, err := req.Post(c.makeEducationURL(urlPath))
EnableTrace().
Post(c.makeEducationURL(urlPath))
if err != nil {
return result, fmt.Errorf("making request: %w", err)
}
@ -229,6 +239,88 @@ func (c *client) ListEducationalProducts(
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 {
if c.cachedMainPageInfo == nil {
return ""

View File

@ -12,6 +12,6 @@ func (NoopClient) GetMainPageState() (*PageState, error) {
return nil, errors.ErrNotImplemented
}
func (NoopClient) ListEducationalProducts(context.Context, ListEducationProductsParams) (ListEducationProductsResponse, error) {
return ListEducationProductsResponse{}, errors.ErrNotImplemented
func (NoopClient) ListEducationalProducts(context.Context, ListEducationProductsParams) (listEducationProductsResponse, error) {
return listEducationProductsResponse{}, errors.ErrNotImplemented
}