add show course and map names

This commit is contained in:
Aleksandr Trushkin
2024-01-08 18:31:35 +03:00
parent 48f5d80f7a
commit 2c0564f68c
22 changed files with 166 additions and 90 deletions

View File

@ -13,6 +13,7 @@ import (
"git.loyso.art/frx/kurious/internal/common/config" "git.loyso.art/frx/kurious/internal/common/config"
"git.loyso.art/frx/kurious/internal/common/xcontext" "git.loyso.art/frx/kurious/internal/common/xcontext"
"git.loyso.art/frx/kurious/internal/common/xlog" "git.loyso.art/frx/kurious/internal/common/xlog"
"git.loyso.art/frx/kurious/internal/kurious/adapters"
"git.loyso.art/frx/kurious/internal/kurious/ports" "git.loyso.art/frx/kurious/internal/kurious/ports"
"git.loyso.art/frx/kurious/internal/kurious/service" "git.loyso.art/frx/kurious/internal/kurious/service"
) )
@ -43,19 +44,39 @@ func app(ctx context.Context) error {
log := config.NewSLogger(cfg.Log) log := config.NewSLogger(cfg.Log)
app, err := service.NewApplication(ctx, service.ApplicationConfig{
LogConfig: cfg.Log,
YDB: cfg.YDB,
})
if err != nil {
return fmt.Errorf("making new application: %w", err)
}
sravniClient, err := sravni.NewClient(ctx, log, cfg.DebugHTTP) sravniClient, err := sravni.NewClient(ctx, log, cfg.DebugHTTP)
if err != nil { if err != nil {
return fmt.Errorf("making sravni client: %w", err) return fmt.Errorf("making sravni client: %w", err)
} }
mainPageState, err := sravniClient.GetMainPageState()
if err != nil {
return fmt.Errorf("getting main page state: %w", err)
}
dictionaries := mainPageState.Props.InitialReduxState.Dictionaries.Data
dictFieldsAsMap := func(fields ...sravni.Field) map[string]string {
out := make(map[string]string, len(fields))
for _, field := range fields {
out[field.Value] = field.Name
}
return out
}
courseThematcisMapped := dictFieldsAsMap(dictionaries.CourseThematics.Fields...)
learningTypeMapped := dictFieldsAsMap(dictionaries.LearningType.Fields...)
mapper := adapters.NewMemoryMapper(courseThematcisMapped, learningTypeMapped)
app, err := service.NewApplication(ctx, service.ApplicationConfig{
LogConfig: cfg.Log,
YDB: cfg.YDB,
}, mapper)
if err != nil {
return fmt.Errorf("making new application: %w", err)
}
bgProcess := ports.NewBackgroundProcess(ctx, log) bgProcess := ports.NewBackgroundProcess(ctx, log)
err = bgProcess.RegisterSyncSravniHandler(ctx, app, sravniClient, cfg.SyncSravniCron) err = bgProcess.RegisterSyncSravniHandler(ctx, app, sravniClient, cfg.SyncSravniCron)
if err != nil { if err != nil {

View File

@ -9,7 +9,7 @@ import (
"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"
"git.loyso.art/frx/kurious/internal/common/xslice" "git.loyso.art/frx/kurious/internal/common/xslices"
"github.com/teris-io/cli" "github.com/teris-io/cli"
) )
@ -188,7 +188,7 @@ func (a *productsFilterCountAction) parse(args []string, options map[string]stri
filterNotEmpty := func(value string) bool { filterNotEmpty := func(value string) bool {
return value != "" return value != ""
} }
a.params.courseThematic = xslice.Filter( a.params.courseThematic = xslices.Filter(
strings.Split(options[courseThematicOptName], ","), strings.Split(options[courseThematicOptName], ","),
filterNotEmpty, filterNotEmpty,
) )

View File

@ -10,9 +10,11 @@ import (
"os/signal" "os/signal"
"time" "time"
"git.loyso.art/frx/kurious/internal/common/client/sravni"
"git.loyso.art/frx/kurious/internal/common/config" "git.loyso.art/frx/kurious/internal/common/config"
"git.loyso.art/frx/kurious/internal/common/generator" "git.loyso.art/frx/kurious/internal/common/generator"
"git.loyso.art/frx/kurious/internal/common/xcontext" "git.loyso.art/frx/kurious/internal/common/xcontext"
"git.loyso.art/frx/kurious/internal/kurious/adapters"
xhttp "git.loyso.art/frx/kurious/internal/kurious/ports/http" xhttp "git.loyso.art/frx/kurious/internal/kurious/ports/http"
"git.loyso.art/frx/kurious/internal/kurious/service" "git.loyso.art/frx/kurious/internal/kurious/service"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
@ -46,10 +48,35 @@ func app(ctx context.Context) error {
log := config.NewSLogger(cfg.Log) log := config.NewSLogger(cfg.Log)
sravniClient, err := sravni.NewClient(ctx, log, false)
if err != nil {
return fmt.Errorf("unable to make new sravni client: %w", err)
}
mainPageState, err := sravniClient.GetMainPageState()
if err != nil {
return fmt.Errorf("getting main page state: %w", err)
}
dictionaries := mainPageState.Props.InitialReduxState.Dictionaries.Data
dictFieldsAsMap := func(fields ...sravni.Field) map[string]string {
out := make(map[string]string, len(fields))
for _, field := range fields {
out[field.Value] = field.Name
}
return out
}
courseThematcisMapped := dictFieldsAsMap(dictionaries.CourseThematics.Fields...)
learningTypeMapped := dictFieldsAsMap(dictionaries.LearningType.Fields...)
mapper := adapters.NewMemoryMapper(courseThematcisMapped, learningTypeMapped)
app, err := service.NewApplication(ctx, service.ApplicationConfig{ app, err := service.NewApplication(ctx, service.ApplicationConfig{
LogConfig: cfg.Log, LogConfig: cfg.Log,
YDB: cfg.YDB, YDB: cfg.YDB,
}) }, mapper)
if err != nil { if err != nil {
return fmt.Errorf("making new application: %w", err) return fmt.Errorf("making new application: %w", err)
} }

View File

@ -11,7 +11,7 @@ import (
"time" "time"
"git.loyso.art/frx/kurious/internal/common/errors" "git.loyso.art/frx/kurious/internal/common/errors"
"git.loyso.art/frx/kurious/pkg/slices" "git.loyso.art/frx/kurious/internal/common/xslices"
"git.loyso.art/frx/kurious/pkg/xdefault" "git.loyso.art/frx/kurious/pkg/xdefault"
"github.com/go-resty/resty/v2" "github.com/go-resty/resty/v2"
@ -52,8 +52,8 @@ func NewClient(ctx context.Context, log *slog.Logger, debug bool) (c *client, er
return nil, err return nil, err
} }
getQuerySet := func(fields []field) querySet { getQuerySet := func(fields []Field) querySet {
items := slices.Map(fields, func(f field) string { items := xslices.Map(fields, func(f Field) string {
return f.Value return f.Value
}) })

View File

@ -39,7 +39,7 @@ type ReduxMetadata struct {
} `json:"data"` } `json:"data"`
} }
type field struct { type Field struct {
Name string `json:"name"` Name string `json:"name"`
Value string `json:"value"` Value string `json:"value"`
} }
@ -51,7 +51,7 @@ type ReduxDictionaryContainer struct {
UserID string `json:"userId"` UserID string `json:"userId"`
Created time.Time `json:"created"` Created time.Time `json:"created"`
Updated time.Time `json:"updated"` Updated time.Time `json:"updated"`
Fields []field `json:"fields"` Fields []Field `json:"fields"`
} }
type ReduxDictionaries struct { type ReduxDictionaries struct {

View File

@ -27,6 +27,7 @@ type YDB struct {
DSN string DSN string
Auth YCAuth Auth YCAuth
ShutdownDuration time.Duration ShutdownDuration time.Duration
DebugYDB bool
} }
func (ydb *YDB) UnmarshalJSON(data []byte) error { func (ydb *YDB) UnmarshalJSON(data []byte) error {

View File

@ -1,4 +1,4 @@
package xslice package xslices
func Filter[T any](values []T, f func(T) bool) []T { func Filter[T any](values []T, f func(T) bool) []T {
out := make([]T, 0, len(values)) out := make([]T, 0, len(values))

View File

@ -1,9 +1,9 @@
package xslice_test package xslices_test
import ( import (
"testing" "testing"
"git.loyso.art/frx/kurious/internal/common/xslice" "git.loyso.art/frx/kurious/internal/common/xslices"
) )
func TestFilterInplace(t *testing.T) { func TestFilterInplace(t *testing.T) {
@ -43,7 +43,7 @@ func TestFilterInplace(t *testing.T) {
for _, tc := range tt { for _, tc := range tt {
tc := tc tc := tc
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
gotLen := xslice.FilterInplace(tc.in, tc.check) gotLen := xslices.FilterInplace(tc.in, tc.check)
if gotLen != tc.expLen { if gotLen != tc.expLen {
t.Errorf("exp %d got %d", tc.expLen, gotLen) t.Errorf("exp %d got %d", tc.expLen, gotLen)
} }

View File

@ -1,4 +1,4 @@
package xslice package xslices
func ForEach[T any](items []T, f func(T)) { func ForEach[T any](items []T, f func(T)) {
for _, item := range items { for _, item := range items {

View File

@ -1,4 +1,4 @@
package xslice package xslices
func Map[T, U any](in []T, f func(T) U) []U { func Map[T, U any](in []T, f func(T) U) []U {
out := make([]U, len(in)) out := make([]U, len(in))

View File

@ -0,0 +1,21 @@
package adapters
type inMemoryMapper struct {
courseThematicsByID map[string]string
learningTypeByID map[string]string
}
func NewMemoryMapper(courseThematics, learningType map[string]string) inMemoryMapper {
return inMemoryMapper{
courseThematicsByID: courseThematics,
learningTypeByID: learningType,
}
}
func (m inMemoryMapper) CourseThematicNameByID(id string) string {
return m.courseThematicsByID[id]
}
func (m inMemoryMapper) LearningTypeNameByID(id string) string {
return m.learningTypeByID[id]
}

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"log/slog" "log/slog"
"os"
"path" "path"
"strings" "strings"
"text/template" "text/template"
@ -17,10 +18,12 @@ import (
"git.loyso.art/frx/kurious/pkg/xdefault" "git.loyso.art/frx/kurious/pkg/xdefault"
"github.com/ydb-platform/ydb-go-sdk/v3" "github.com/ydb-platform/ydb-go-sdk/v3"
ydblog "github.com/ydb-platform/ydb-go-sdk/v3/log"
"github.com/ydb-platform/ydb-go-sdk/v3/table" "github.com/ydb-platform/ydb-go-sdk/v3/table"
"github.com/ydb-platform/ydb-go-sdk/v3/table/options" "github.com/ydb-platform/ydb-go-sdk/v3/table/options"
"github.com/ydb-platform/ydb-go-sdk/v3/table/result/named" "github.com/ydb-platform/ydb-go-sdk/v3/table/result/named"
"github.com/ydb-platform/ydb-go-sdk/v3/table/types" "github.com/ydb-platform/ydb-go-sdk/v3/table/types"
"github.com/ydb-platform/ydb-go-sdk/v3/trace"
yc "github.com/ydb-platform/ydb-go-yc" yc "github.com/ydb-platform/ydb-go-yc"
) )
@ -69,9 +72,11 @@ func NewYDBConnection(ctx context.Context, cfg config.YDB, log *slog.Logger) (*Y
yc.WithServiceAccountKeyFileCredentials(auth.Path), yc.WithServiceAccountKeyFileCredentials(auth.Path),
) )
} }
if cfg.DebugYDB {
opts = append(opts, opts = append(opts,
ydb.WithDialTimeout(time.Second*3), ydb.WithLogger(ydblog.Default(os.Stdout, ydblog.WithMinLevel(ydblog.DEBUG)), trace.DetailsAll),
) )
}
db, err := ydb.Open( db, err := ydb.Open(
ctx, ctx,
@ -81,6 +86,14 @@ func NewYDBConnection(ctx context.Context, cfg config.YDB, log *slog.Logger) (*Y
if err != nil { if err != nil {
return nil, fmt.Errorf("opening connection: %w", err) return nil, fmt.Errorf("opening connection: %w", err)
} }
endpoints, err := db.Discovery().Discover(ctx)
if err != nil {
return nil, fmt.Errorf("discovering endpoints: %w", err)
}
for _, endpoint := range endpoints {
xcontext.LogInfo(ctx, log, "discovered endpoint", slog.String("value", endpoint.Address()))
}
return &YDBConnection{ return &YDBConnection{
Driver: db, Driver: db,
@ -163,11 +176,9 @@ func (r *ydbCourseRepository) List(
query, err := qtParams.render() query, err := qtParams.render()
if err != nil { if err != nil {
return result, fmt.Errorf("rendering: %w", err) return result, fmt.Errorf("rendering query params: %w", err)
} }
xcontext.LogInfo(ctx, r.log, "planning to run query", slog.String("query", query), slog.String("opts", tableParamOptsToString(opts...)))
courses := make([]domain.Course, 0, 1_000) courses := make([]domain.Course, 0, 1_000)
readTx := table.TxControl( readTx := table.TxControl(
table.BeginTx( table.BeginTx(
@ -176,11 +187,9 @@ func (r *ydbCourseRepository) List(
table.CommitTx(), table.CommitTx(),
) )
xcontext.LogInfo(ctx, r.log, "executing do")
err = r.db.Table().Do( err = r.db.Table().Do(
ctx, ctx,
func(ctx context.Context, s table.Session) error { func(ctx context.Context, s table.Session) error {
xcontext.LogInfo(ctx, r.log, "inside do")
start := time.Now() start := time.Now()
defer func() { defer func() {
since := time.Since(start) since := time.Since(start)
@ -194,8 +203,6 @@ func (r *ydbCourseRepository) List(
queryParams := table.NewQueryParameters(opts...) queryParams := table.NewQueryParameters(opts...)
xcontext.LogDebug(ctx, r.log, "executing")
_, res, err := s.Execute( _, res, err := s.Execute(
ctx, readTx, query, queryParams, ctx, readTx, query, queryParams,
options.WithCollectStatsModeBasic(), options.WithCollectStatsModeBasic(),
@ -204,14 +211,10 @@ func (r *ydbCourseRepository) List(
return fmt.Errorf("executing: %w", err) return fmt.Errorf("executing: %w", err)
} }
xcontext.LogDebug(ctx, r.log, "checking")
if !res.NextResultSet(ctx) || !res.HasNextRow() { if !res.NextResultSet(ctx) || !res.HasNextRow() {
return nil return nil
} }
xcontext.LogDebug(ctx, r.log, "scanning")
for res.NextRow() { for res.NextRow() {
var cdb courseDB var cdb courseDB
err = res.ScanNamed(cdb.getNamedValues()...) err = res.ScanNamed(cdb.getNamedValues()...)
@ -393,7 +396,6 @@ func createCourseParamsAsStruct(params domain.CreateCourseParams) types.Value {
} }
func (r *ydbCourseRepository) CreateBatch(ctx context.Context, params ...domain.CreateCourseParams) error { func (r *ydbCourseRepository) CreateBatch(ctx context.Context, params ...domain.CreateCourseParams) error {
// -- PRAGMA TablePathPrefix("courses");
const upsertQuery = `DECLARE $courseData AS List<Struct< const upsertQuery = `DECLARE $courseData AS List<Struct<
id: Text, id: Text,
external_id: Optional<Text>, external_id: Optional<Text>,
@ -680,8 +682,8 @@ func mapCourseDB(cdb courseDB) domain.Course {
Name: cdb.Name, Name: cdb.Name,
SourceType: st, SourceType: st,
SourceName: nullable.NewValuePtr(cdb.SourceName), SourceName: nullable.NewValuePtr(cdb.SourceName),
Thematic: cdb.CourseThematic, ThematicID: cdb.CourseThematic,
LearningType: cdb.LearningType, LearningTypeID: cdb.LearningType,
OrganizationID: cdb.OrganizationID, OrganizationID: cdb.OrganizationID,
OriginLink: cdb.OriginLink, OriginLink: cdb.OriginLink,
ImageLink: cdb.ImageLink, ImageLink: cdb.ImageLink,
@ -738,11 +740,10 @@ WHERE {{ range .Conditions }}{{.}}{{end}}
var querySelect = template.Must(template.New("").Parse(queryTemplateSelect)) var querySelect = template.Must(template.New("").Parse(queryTemplateSelect))
func tableParamOptsToString(in ...table.ParameterOption) string { // func tableParamOptsToString(in ...table.ParameterOption) string {
var sb strings.Builder // var sb strings.Builder
for _, opt := range in { // for _, opt := range in {
sb.WriteString(opt.Name() + "(" + opt.Value().Type().String() + ");") // sb.WriteString(opt.Name() + "(" + opt.Value().Type().String() + ");")
} // }
// return sb.String()
return sb.String() // }
}

View File

@ -8,7 +8,7 @@ import (
"git.loyso.art/frx/kurious/internal/common/decorator" "git.loyso.art/frx/kurious/internal/common/decorator"
"git.loyso.art/frx/kurious/internal/common/nullable" "git.loyso.art/frx/kurious/internal/common/nullable"
"git.loyso.art/frx/kurious/internal/common/xslice" "git.loyso.art/frx/kurious/internal/common/xslices"
"git.loyso.art/frx/kurious/internal/kurious/domain" "git.loyso.art/frx/kurious/internal/kurious/domain"
) )
@ -78,7 +78,7 @@ func NewCreateCoursesHandler(
} }
func (h createCoursesHandler) Handle(ctx context.Context, cmd CreateCourses) error { func (h createCoursesHandler) Handle(ctx context.Context, cmd CreateCourses) error {
params := xslice.Map(cmd.Courses, func(in CreateCourse) (out domain.CreateCourseParams) { params := xslices.Map(cmd.Courses, func(in CreateCourse) (out domain.CreateCourseParams) {
return domain.CreateCourseParams(in) return domain.CreateCourseParams(in)
}) })
err := h.repo.CreateBatch(ctx, params...) err := h.repo.CreateBatch(ctx, params...)

View File

@ -6,6 +6,7 @@ import (
"log/slog" "log/slog"
"git.loyso.art/frx/kurious/internal/common/decorator" "git.loyso.art/frx/kurious/internal/common/decorator"
"git.loyso.art/frx/kurious/internal/common/xslices"
"git.loyso.art/frx/kurious/internal/kurious/domain" "git.loyso.art/frx/kurious/internal/kurious/domain"
) )
@ -24,14 +25,17 @@ type ListCourseHandler decorator.QueryHandler[ListCourse, domain.ListCoursesResu
type listCourseHandler struct { type listCourseHandler struct {
repo domain.CourseRepository repo domain.CourseRepository
mapper domain.CourseMapper
} }
func NewListCourseHandler( func NewListCourseHandler(
repo domain.CourseRepository, repo domain.CourseRepository,
mapper domain.CourseMapper,
log *slog.Logger, log *slog.Logger,
) ListCourseHandler { ) ListCourseHandler {
h := listCourseHandler{ h := listCourseHandler{
repo: repo, repo: repo,
mapper: mapper,
} }
return decorator.AddQueryDecorators(h, log) return decorator.AddQueryDecorators(h, log)
} }
@ -43,7 +47,6 @@ func (h listCourseHandler) Handle(ctx context.Context, query ListCourse) (out do
out.Courses = make([]domain.Course, 0, query.Limit) out.Courses = make([]domain.Course, 0, query.Limit)
} }
for { for {
print("listing")
result, err := h.repo.List(ctx, domain.ListCoursesParams{ result, err := h.repo.List(ctx, domain.ListCoursesParams{
CourseThematic: query.CourseThematic, CourseThematic: query.CourseThematic,
LearningType: query.LearningType, LearningType: query.LearningType,
@ -56,6 +59,13 @@ func (h listCourseHandler) Handle(ctx context.Context, query ListCourse) (out do
return out, fmt.Errorf("listing courses: %w", err) return out, fmt.Errorf("listing courses: %w", err)
} }
result.Courses = xslices.Map(result.Courses, func(in domain.Course) domain.Course {
in.LearningType = h.mapper.LearningTypeNameByID(in.LearningTypeID)
in.Thematic = h.mapper.CourseThematicNameByID(in.ThematicID)
return in
})
out.Courses = append(out.Courses, result.Courses...) out.Courses = append(out.Courses, result.Courses...)
out.NextPageToken = result.NextPageToken out.NextPageToken = result.NextPageToken

View File

@ -28,8 +28,12 @@ type Course struct {
FullPrice float64 FullPrice float64
// Discount for the course. // Discount for the course.
Discount float64 Discount float64
Thematic string Thematic string
ThematicID string
LearningType string LearningType string
LearningTypeID string
// Duration for the course. It will be splitted in values like: // Duration for the course. It will be splitted in values like:
// full month / full day / full hour. // full month / full day / full hour.

View File

@ -0,0 +1,6 @@
package domain
type CourseMapper interface {
CourseThematicNameByID(string) string
LearningTypeNameByID(string) string
}

View File

@ -14,7 +14,7 @@ import (
"git.loyso.art/frx/kurious/internal/common/generator" "git.loyso.art/frx/kurious/internal/common/generator"
"git.loyso.art/frx/kurious/internal/common/nullable" "git.loyso.art/frx/kurious/internal/common/nullable"
"git.loyso.art/frx/kurious/internal/common/xcontext" "git.loyso.art/frx/kurious/internal/common/xcontext"
"git.loyso.art/frx/kurious/internal/common/xslice" "git.loyso.art/frx/kurious/internal/common/xslices"
"git.loyso.art/frx/kurious/internal/kurious/app/command" "git.loyso.art/frx/kurious/internal/kurious/app/command"
"git.loyso.art/frx/kurious/internal/kurious/app/query" "git.loyso.art/frx/kurious/internal/kurious/app/query"
"git.loyso.art/frx/kurious/internal/kurious/domain" "git.loyso.art/frx/kurious/internal/kurious/domain"
@ -126,7 +126,7 @@ func (h *syncSravniHandler) Handle(ctx context.Context) (err error) {
return fmt.Errorf("loading educational products: %w", err) return fmt.Errorf("loading educational products: %w", err)
} }
xslice.ForEach(buffer, func(c sravni.Course) { xslices.ForEach(buffer, func(c sravni.Course) {
c.Learningtype = []string{learningType.Value} c.Learningtype = []string{learningType.Value}
c.CourseThematics = []string{courseThematic} c.CourseThematics = []string{courseThematic}
courses = append(courses, c) courses = append(courses, c)
@ -155,7 +155,7 @@ func (h *syncSravniHandler) Handle(ctx context.Context) (err error) {
continue continue
} }
xslice.ForEach(courses, func(c sravni.Course) { xslices.ForEach(courses, func(c sravni.Course) {
h.knownExternalIDs[c.ID] = struct{}{} h.knownExternalIDs[c.ID] = struct{}{}
}) })
@ -212,7 +212,7 @@ func (h *syncSravniHandler) loadEducationalProducts(ctx context.Context, learnin
} }
func (h *syncSravniHandler) filterByCache(courses []sravni.Course) (toInsert []sravni.Course) { func (h *syncSravniHandler) filterByCache(courses []sravni.Course) (toInsert []sravni.Course) {
toCut := xslice.FilterInplace(courses, xslice.Not(h.isCached)) toCut := xslices.FilterInplace(courses, xslices.Not(h.isCached))
return courses[:toCut] return courses[:toCut]
} }
@ -222,7 +222,7 @@ func (h *syncSravniHandler) isCached(course sravni.Course) bool {
} }
func (h *syncSravniHandler) insertValues(ctx context.Context, courses []sravni.Course) error { func (h *syncSravniHandler) insertValues(ctx context.Context, courses []sravni.Course) error {
courseParams := xslice.Map(courses, courseAsCreateCourseParams) courseParams := xslices.Map(courses, courseAsCreateCourseParams)
err := h.svc.Commands.InsertCourses.Handle(ctx, command.CreateCourses{ err := h.svc.Commands.InsertCourses.Handle(ctx, command.CreateCourses{
Courses: courseParams, Courses: courseParams,
}) })
@ -249,7 +249,7 @@ func (h *syncSravniHandler) fillCaches(ctx context.Context) error {
h.knownExternalIDs = make(map[string]struct{}, len(courses)) h.knownExternalIDs = make(map[string]struct{}, len(courses))
xslice.ForEach(courses, func(c domain.Course) { xslices.ForEach(courses, func(c domain.Course) {
if !c.ExternalID.Valid() { if !c.ExternalID.Valid() {
return return
} }

View File

@ -9,7 +9,7 @@ import (
"git.loyso.art/frx/kurious/internal/common/errors" "git.loyso.art/frx/kurious/internal/common/errors"
"git.loyso.art/frx/kurious/internal/common/xcontext" "git.loyso.art/frx/kurious/internal/common/xcontext"
"git.loyso.art/frx/kurious/internal/common/xslice" "git.loyso.art/frx/kurious/internal/common/xslices"
"git.loyso.art/frx/kurious/internal/kurious/app/command" "git.loyso.art/frx/kurious/internal/kurious/app/command"
"git.loyso.art/frx/kurious/internal/kurious/app/query" "git.loyso.art/frx/kurious/internal/kurious/app/query"
"git.loyso.art/frx/kurious/internal/kurious/domain" "git.loyso.art/frx/kurious/internal/kurious/domain"
@ -90,7 +90,7 @@ type listCoursesTemplateParams struct {
func mapDomainCourseToTemplate(in ...domain.Course) listCoursesTemplateParams { func mapDomainCourseToTemplate(in ...domain.Course) listCoursesTemplateParams {
coursesBySubcategory := make(map[string][]domain.Course, len(in)) coursesBySubcategory := make(map[string][]domain.Course, len(in))
subcategoriesByCategories := make(map[string]map[string]struct{}, len(in)) subcategoriesByCategories := make(map[string]map[string]struct{}, len(in))
xslice.ForEach(in, func(c domain.Course) { xslices.ForEach(in, func(c domain.Course) {
coursesBySubcategory[c.Thematic] = append(coursesBySubcategory[c.Thematic], c) coursesBySubcategory[c.Thematic] = append(coursesBySubcategory[c.Thematic], c)
if _, ok := subcategoriesByCategories[c.LearningType]; !ok { if _, ok := subcategoriesByCategories[c.LearningType]; !ok {
subcategoriesByCategories[c.LearningType] = map[string]struct{}{} subcategoriesByCategories[c.LearningType] = map[string]struct{}{}
@ -216,7 +216,7 @@ func (c courseServer) RenderEditDescription(w http.ResponseWriter, r *http.Reque
func (c courseServer) UpdateCourseDescription(w http.ResponseWriter, r *http.Request) { func (c courseServer) UpdateCourseDescription(w http.ResponseWriter, r *http.Request) {
type requestModel struct { type requestModel struct {
ID string `json:"-"` ID string `json:"-"`
Text string `json:"text"` Text string `json:"description"`
} }
ctx := r.Context() ctx := r.Context()

View File

@ -9,7 +9,7 @@ import (
"path" "path"
"git.loyso.art/frx/kurious/internal/common/xcontext" "git.loyso.art/frx/kurious/internal/common/xcontext"
"git.loyso.art/frx/kurious/internal/common/xslice" "git.loyso.art/frx/kurious/internal/common/xslices"
) )
const baseTemplatePath = "./internal/kurious/ports/http/templates/" const baseTemplatePath = "./internal/kurious/ports/http/templates/"
@ -22,7 +22,7 @@ func must[T any](t T, err error) T {
} }
func scanFiles() []string { func scanFiles() []string {
entries := xslice.Map( entries := xslices.Map(
must(os.ReadDir(baseTemplatePath)), must(os.ReadDir(baseTemplatePath)),
func(v fs.DirEntry) string { func(v fs.DirEntry) string {
return path.Join(baseTemplatePath, v.Name()) return path.Join(baseTemplatePath, v.Name())

View File

@ -9,13 +9,16 @@
</div> </div>
<div class="card-content"> <div class="card-content">
<div class="media-content"> <div class="media-content">
<p class="title is-5">{{ .Name }}</p> <p class="title is-5" onclick="location.href='{{ .OriginLink }}'">{{ .Name }}</p>
<p class="subtitle is-8">{{ .Description }}</p> <p class="subtitle is-8">{{ .Description }}</p>
</div> </div>
<div class="content"> <div class="content">
<p>{{ .FullPrice }} rub.</p> <p>{{ .FullPrice }} rub.</p>
</div> </div>
<button class="button" onclick="location.href='{{ .OriginLink }}'">
Show Course
</button>
<button class="button" hx-get="/courses/{{ .ID }}/editdesc"> <button class="button" hx-get="/courses/{{ .ID }}/editdesc">
Edit description Edit description
</button> </button>
@ -49,7 +52,7 @@
<div class="field-body"> <div class="field-body">
<div class="field"> <div class="field">
<div class="control"> <div class="control">
<textarea class="textarea" placeholder="Description">{{ .Description }}</textarea> <textarea class="textarea" name="description" placeholder="Description">{{ .Description }}</textarea>
</div> </div>
</div> </div>
</div> </div>
@ -59,7 +62,7 @@
</div> </div>
<div class="field is-grouped"> <div class="field is-grouped">
<p class="control"> <p class="control">
<button class="button is-primary is-link" hx-include="closest .control"> <button class="button is-primary is-link" hx-include="[name='description']">
Submit Submit
</button> </button>
</p> </p>
@ -69,8 +72,6 @@
</button> </button>
</p> </p>
</div> </div>
<!-- <button class="btn">Submit</button>
<button class="btn" hx-get="/courses/{{ .ID }}/short">Cancel</button> -->
</form> </form>
{{ end }} {{ end }}

View File

@ -11,6 +11,7 @@ import (
"git.loyso.art/frx/kurious/internal/kurious/app" "git.loyso.art/frx/kurious/internal/kurious/app"
"git.loyso.art/frx/kurious/internal/kurious/app/command" "git.loyso.art/frx/kurious/internal/kurious/app/command"
"git.loyso.art/frx/kurious/internal/kurious/app/query" "git.loyso.art/frx/kurious/internal/kurious/app/query"
"git.loyso.art/frx/kurious/internal/kurious/domain"
) )
type ApplicationConfig struct { type ApplicationConfig struct {
@ -25,7 +26,7 @@ type Application struct {
closers []io.Closer closers []io.Closer
} }
func NewApplication(ctx context.Context, cfg ApplicationConfig) (Application, error) { func NewApplication(ctx context.Context, cfg ApplicationConfig, mapper domain.CourseMapper) (Application, error) {
log := config.NewSLogger(cfg.LogConfig) log := config.NewSLogger(cfg.LogConfig)
ydbConnection, err := adapters.NewYDBConnection(ctx, cfg.YDB, log.With(slog.String("db", "ydb"))) ydbConnection, err := adapters.NewYDBConnection(ctx, cfg.YDB, log.With(slog.String("db", "ydb")))
if err != nil { if err != nil {
@ -43,7 +44,7 @@ func NewApplication(ctx context.Context, cfg ApplicationConfig) (Application, er
}, },
Queries: app.Queries{ Queries: app.Queries{
GetCourse: query.NewGetCourseHandler(courseadapter, log), GetCourse: query.NewGetCourseHandler(courseadapter, log),
ListCourses: query.NewListCourseHandler(courseadapter, log), ListCourses: query.NewListCourseHandler(courseadapter, mapper, log),
}, },
} }

View File

@ -1,17 +0,0 @@
package slices
// Map slice from one type to another one.
func Map[S any, E any](s []S, f func(S) E) []E {
out := make([]E, len(s))
for i := range s {
out[i] = f(s[i])
}
return out
}
func ForEach[S any](s []S, f func(S)) {
for i := range s {
f(s[i])
}
}