282 lines
8.0 KiB
Go
282 lines
8.0 KiB
Go
package http
|
|
|
|
import (
|
|
"encoding/json"
|
|
"log/slog"
|
|
"net/http"
|
|
"slices"
|
|
|
|
"git.loyso.art/frx/kurious/internal/common/xslices"
|
|
"git.loyso.art/frx/kurious/internal/kurious/app/query"
|
|
"git.loyso.art/frx/kurious/internal/kurious/domain"
|
|
"git.loyso.art/frx/kurious/internal/kurious/ports/http/bootstrap"
|
|
"git.loyso.art/frx/kurious/internal/kurious/service"
|
|
|
|
"go.opentelemetry.io/otel"
|
|
"go.opentelemetry.io/otel/attribute"
|
|
"go.opentelemetry.io/otel/codes"
|
|
"go.opentelemetry.io/otel/trace"
|
|
)
|
|
|
|
var (
|
|
paramsAttr = attribute.Key("params")
|
|
|
|
webtracer = otel.Tracer("http")
|
|
)
|
|
|
|
type courseTemplServer struct {
|
|
app service.Application
|
|
log *slog.Logger
|
|
}
|
|
|
|
func makeTemplListCoursesParams(counts map[string]domain.LearningTypeStat, in ...domain.Course) bootstrap.ListCoursesParams {
|
|
coursesBySubcategory := make(map[string][]bootstrap.CourseInfo, len(in))
|
|
subcategoriesByCategories := make(map[string]map[string]struct{}, len(in))
|
|
categoryByID := make(map[string]bootstrap.CategoryBaseInfo, len(in))
|
|
|
|
xslices.ForEach(in, func(c domain.Course) {
|
|
courseInfo := bootstrap.CourseInfo{
|
|
ID: c.ID,
|
|
Name: c.Name,
|
|
FullPrice: int(c.FullPrice),
|
|
ImageLink: c.ImageLink,
|
|
OriginLink: c.OriginLink,
|
|
}
|
|
|
|
coursesBySubcategory[c.ThematicID] = append(coursesBySubcategory[c.ThematicID], courseInfo)
|
|
|
|
if _, ok := subcategoriesByCategories[c.LearningTypeID]; !ok {
|
|
subcategoriesByCategories[c.LearningTypeID] = map[string]struct{}{}
|
|
}
|
|
subcategoriesByCategories[c.LearningTypeID][c.ThematicID] = struct{}{}
|
|
|
|
if _, ok := categoryByID[c.LearningTypeID]; !ok {
|
|
categoryByID[c.LearningTypeID] = bootstrap.CategoryBaseInfo{
|
|
ID: c.LearningTypeID,
|
|
Name: c.LearningType,
|
|
}
|
|
}
|
|
if _, ok := categoryByID[c.ThematicID]; !ok {
|
|
categoryByID[c.ThematicID] = bootstrap.CategoryBaseInfo{
|
|
ID: c.ThematicID,
|
|
Name: c.Thematic,
|
|
Count: counts[c.LearningTypeID].CourseThematic[c.ThematicID],
|
|
}
|
|
}
|
|
})
|
|
|
|
var out bootstrap.ListCoursesParams
|
|
for categoryID, subcategoriesID := range subcategoriesByCategories {
|
|
outCategory := bootstrap.CategoryContainer{
|
|
CategoryBaseInfo: categoryByID[categoryID],
|
|
}
|
|
|
|
for subcategoryID := range subcategoriesID {
|
|
outSubcategory := bootstrap.SubcategoryContainer{
|
|
CategoryBaseInfo: categoryByID[subcategoryID],
|
|
Courses: coursesBySubcategory[subcategoryID],
|
|
}
|
|
|
|
outCategory.Subcategories = append(outCategory.Subcategories, outSubcategory)
|
|
}
|
|
|
|
out.Categories = append(out.Categories, outCategory)
|
|
}
|
|
|
|
return out
|
|
}
|
|
|
|
func (c courseTemplServer) List(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
|
|
var span trace.Span
|
|
ctx, span = webtracer.Start(ctx, "http_server.list")
|
|
defer func() {
|
|
span.End()
|
|
}()
|
|
|
|
stats := bootstrap.MakeNewStats(10_240, 2_560_000, 1800)
|
|
|
|
pathParams, err := parseListCoursesParams(r)
|
|
if handleError(ctx, err, w, c.log, "unable to parse list courses params") {
|
|
return
|
|
}
|
|
|
|
jsonParams, _ := json.Marshal(pathParams)
|
|
span.SetAttributes(paramsAttr.String(string(jsonParams)))
|
|
|
|
var offset int
|
|
if pathParams.Page > 0 {
|
|
offset = (pathParams.Page - 1) * pathParams.PerPage
|
|
}
|
|
listCoursesResult, err := c.app.Queries.ListCourses.Handle(ctx, query.ListCourse{
|
|
CourseThematic: pathParams.CourseThematic,
|
|
LearningType: pathParams.LearningType,
|
|
Limit: pathParams.PerPage,
|
|
NextPageToken: pathParams.NextPageToken,
|
|
Offset: offset,
|
|
})
|
|
if handleError(ctx, err, w, c.log, "unable to list courses") {
|
|
return
|
|
}
|
|
|
|
statsresult, err := c.app.Queries.ListCourseStatistics.Handle(ctx, query.ListCoursesStats{})
|
|
if handleError(ctx, err, w, c.log, "unable to load stats") {
|
|
return
|
|
}
|
|
params := makeTemplListCoursesParams(statsresult.StatsByLearningType, listCoursesResult.Courses...)
|
|
|
|
learningTypeResult, err := c.app.Queries.ListLearningTypes.Handle(ctx, query.ListLearningTypes{})
|
|
if handleError(ctx, err, w, c.log, "unable to list learning types") {
|
|
return
|
|
}
|
|
|
|
params.FilterForm.AvailableLearningTypes = xslices.Map(learningTypeResult.LearningTypes, func(in query.LearningType) bootstrap.Category {
|
|
outcategory := bootstrap.Category{
|
|
ID: in.ID,
|
|
Name: in.Name,
|
|
}
|
|
if in.ID == pathParams.LearningType {
|
|
params.FilterForm.ActiveLearningType = outcategory
|
|
}
|
|
|
|
return outcategory
|
|
})
|
|
|
|
if pathParams.LearningType != "" {
|
|
courseThematicsResult, err := c.app.Queries.ListCourseThematics.Handle(ctx, query.ListCourseThematics{
|
|
LearningTypeID: pathParams.LearningType,
|
|
})
|
|
if handleError(ctx, err, w, c.log, "unable to list course thematics") {
|
|
return
|
|
}
|
|
|
|
params.FilterForm.AvailableCourseThematics = xslices.Map(courseThematicsResult.CourseThematics, func(in query.CourseThematic) bootstrap.Category {
|
|
outcategory := bootstrap.Category{
|
|
ID: in.ID,
|
|
Name: in.Name,
|
|
}
|
|
if pathParams.CourseThematic == in.ID {
|
|
params.FilterForm.BreadcrumbsParams.ActiveCourseThematic = outcategory
|
|
}
|
|
|
|
return outcategory
|
|
})
|
|
}
|
|
|
|
params = bootstrap.ListCoursesParams{
|
|
FilterForm: bootstrap.FilterFormParams{
|
|
BreadcrumbsParams: bootstrap.BreadcrumbsParams{
|
|
ActiveLearningType: params.FilterForm.ActiveLearningType,
|
|
ActiveCourseThematic: params.FilterForm.ActiveCourseThematic,
|
|
},
|
|
AvailableLearningTypes: params.FilterForm.AvailableLearningTypes,
|
|
AvailableCourseThematics: params.FilterForm.AvailableCourseThematics,
|
|
},
|
|
Categories: params.Categories,
|
|
Pagination: bootstrap.Pagination{
|
|
Page: pathParams.Page,
|
|
TotalPages: listCoursesResult.Count / pathParams.PerPage,
|
|
BaseURL: r.URL.Path,
|
|
},
|
|
}
|
|
|
|
c.log.DebugContext(
|
|
ctx, "params rendered",
|
|
slog.Int("course_thematic", len(params.FilterForm.AvailableCourseThematics)),
|
|
slog.Int("learning_type", len(params.FilterForm.AvailableLearningTypes)),
|
|
slog.Int("items", len(listCoursesResult.Courses)),
|
|
slog.Int("page", params.Pagination.Page),
|
|
slog.Int("total_pages", params.Pagination.TotalPages),
|
|
)
|
|
|
|
slices.SortFunc(params.Categories, func(lhs, rhs bootstrap.CategoryContainer) int {
|
|
if lhs.Count > rhs.Count {
|
|
return 1
|
|
} else if lhs.Count < rhs.Count {
|
|
return -1
|
|
} else {
|
|
return 0
|
|
}
|
|
})
|
|
|
|
span.AddEvent("starting to render")
|
|
err = bootstrap.ListCourses(bootstrap.PageCourses, stats, params).Render(ctx, w)
|
|
span.AddEvent("render finished")
|
|
|
|
if handleError(ctx, err, w, c.log, "unable to render list courses") {
|
|
return
|
|
}
|
|
|
|
span.SetStatus(codes.Ok, "request completed")
|
|
}
|
|
|
|
func (c courseTemplServer) Index(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
|
|
var span trace.Span
|
|
ctx, span = webtracer.Start(ctx, "http_server.index")
|
|
defer func() {
|
|
span.End()
|
|
}()
|
|
|
|
stats := bootstrap.MakeNewStats(1, 2, 3)
|
|
|
|
coursesResult, err := c.app.Queries.ListCourses.Handle(ctx, query.ListCourse{})
|
|
if handleError(ctx, err, w, c.log, "unable to list courses") {
|
|
return
|
|
}
|
|
|
|
params := bootstrap.MainPageParams{
|
|
Categories: []bootstrap.IndexCourseCategoryItem{},
|
|
}
|
|
|
|
coursesByLearningType := make(map[IDNamePair][]domain.Course)
|
|
xslices.ForEach(coursesResult.Courses, func(in domain.Course) {
|
|
pair := IDNamePair{
|
|
ID: in.LearningTypeID,
|
|
Name: in.LearningType,
|
|
}
|
|
coursesByLearningType[pair] = append(coursesByLearningType[pair], in)
|
|
})
|
|
|
|
for learningTypeInfo, courses := range coursesByLearningType {
|
|
category := bootstrap.IndexCourseCategoryItem{
|
|
ID: learningTypeInfo.ID,
|
|
Name: learningTypeInfo.Name,
|
|
Count: len(courses),
|
|
}
|
|
|
|
xslices.Shuffle(courses)
|
|
if len(courses) > 3 {
|
|
courses = courses[:3]
|
|
}
|
|
|
|
names := xslices.Map(courses, func(in domain.Course) string {
|
|
return in.Name
|
|
})
|
|
|
|
category.ExampleThemes = names
|
|
|
|
params.Categories = append(params.Categories, category)
|
|
}
|
|
|
|
slices.SortFunc(params.Categories, func(lhs, rhs bootstrap.IndexCourseCategoryItem) int {
|
|
if lhs.Count < rhs.Count {
|
|
return 1
|
|
} else if lhs.Count > rhs.Count {
|
|
return -1
|
|
}
|
|
|
|
return 0
|
|
})
|
|
|
|
span.AddEvent("starting to render")
|
|
err = bootstrap.MainPage(bootstrap.PageIndex, stats, params).Render(ctx, w)
|
|
span.AddEvent("render finished")
|
|
if handleError(ctx, err, w, c.log, "rendeting template") {
|
|
return
|
|
}
|
|
span.SetStatus(codes.Ok, "request completed")
|
|
}
|