Files
kurious/internal/kurious/ports/http/course.go
2024-01-10 00:02:40 +03:00

276 lines
6.9 KiB
Go

package http
import (
"encoding/json"
"log/slog"
"net/http"
"sort"
"strconv"
"git.loyso.art/frx/kurious/internal/common/errors"
"git.loyso.art/frx/kurious/internal/common/xcontext"
"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/query"
"git.loyso.art/frx/kurious/internal/kurious/domain"
"git.loyso.art/frx/kurious/internal/kurious/service"
"github.com/gorilla/mux"
)
type courseServer struct {
app service.Application
log *slog.Logger
}
type pagination struct {
nextPageToken string
perPage int
}
func parsePaginationFromQuery(r *http.Request) (out pagination, err error) {
query := r.URL.Query()
out.nextPageToken = query.Get("next")
if query.Has("per_page") {
out.perPage, err = strconv.Atoi(query.Get("per_page"))
if err != nil {
return out, errors.NewValidationError("per_page", "bad per_page value")
}
} else {
out.perPage = 50
}
return out, nil
}
func parseListCoursesParams(r *http.Request) (out listCoursesParams, err error) {
out.pagination, err = parsePaginationFromQuery(r)
if err != nil {
return out, err
}
vars := mux.Vars(r)
out.learningType = vars["learning_type"]
out.courseThematic = vars["thematic_type"]
return out, nil
}
type listCoursesParams struct {
pagination
courseThematic string
learningType string
}
type baseInfo struct {
ID string
Name string
Description string
}
type categoryInfo struct {
baseInfo
Subcategories []subcategoryInfo
}
type subcategoryInfo struct {
baseInfo
Courses []domain.Course
}
type listCoursesTemplateParams struct {
Categories []categoryInfo
NextPageToken string
}
func mapDomainCourseToTemplate(in ...domain.Course) listCoursesTemplateParams {
coursesBySubcategory := make(map[string][]domain.Course, len(in))
subcategoriesByCategories := make(map[string]map[string]struct{}, len(in))
xslices.ForEach(in, func(c domain.Course) {
coursesBySubcategory[c.Thematic] = append(coursesBySubcategory[c.Thematic], c)
if _, ok := subcategoriesByCategories[c.LearningType]; !ok {
subcategoriesByCategories[c.LearningType] = map[string]struct{}{}
}
subcategoriesByCategories[c.LearningType][c.Thematic] = struct{}{}
})
var out listCoursesTemplateParams
for category, subcategoryMap := range subcategoriesByCategories {
outCategory := categoryInfo{}
outCategory.ID = category
outCategory.Name = category
outCategory.Description = ""
for subcategory := range subcategoryMap {
outSubCategory := subcategoryInfo{
Courses: coursesBySubcategory[subcategory],
}
outSubCategory.ID = subcategory
outSubCategory.Name = subcategory
outSubCategory.Description = ""
outCategory.Subcategories = append(outCategory.Subcategories, outSubCategory)
}
sort.Slice(outCategory.Subcategories, func(i, j int) bool {
return outCategory.Subcategories[i].ID < outCategory.Subcategories[j].ID
})
out.Categories = append(out.Categories, outCategory)
}
sort.Slice(out.Categories, func(i, j int) bool {
return out.Categories[i].ID < out.Categories[j].ID
})
return out
}
func (c courseServer) List(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
params, err := parseListCoursesParams(r)
if handleError(ctx, err, w, c.log, "unable to parse list courses params") {
return
}
result, err := c.app.Queries.ListCourses.Handle(ctx, query.ListCourse{
CourseThematic: params.courseThematic,
LearningType: params.learningType,
Limit: params.perPage,
NextPageToken: params.nextPageToken,
})
if handleError(ctx, err, w, c.log, "unable to list courses") {
return
}
courses := result.Courses
templateCourses := mapDomainCourseToTemplate(courses...)
templateCourses.NextPageToken = result.NextPageToken
err = getCoreTemplate(ctx, c.log).ExecuteTemplate(w, "courses", templateCourses)
if handleError(ctx, err, w, c.log, "unable to execute template") {
return
}
}
func (c courseServer) Get(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
id := mux.Vars(r)["course_id"]
course, err := c.app.Queries.GetCourse.Handle(ctx, query.GetCourse{
ID: id,
})
if handleError(ctx, err, w, c.log, "unable to get course") {
return
}
payload, err := json.MarshalIndent(course, "", " ")
if handleError(ctx, err, w, c.log, "unable to marshal json") {
return
}
w.Header().Set("content-type", "application/json")
w.Header().Set("content-length", strconv.Itoa(len(payload)))
_, err = w.Write([]byte(payload))
if err != nil {
xcontext.LogWithWarnError(ctx, c.log, err, "unable to write a message")
}
}
func (c courseServer) GetShort(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
id := mux.Vars(r)["course_id"]
course, err := c.app.Queries.GetCourse.Handle(ctx, query.GetCourse{
ID: id,
})
if handleError(ctx, err, w, c.log, "unable to get course") {
return
}
err = getCoreTemplate(ctx, c.log).ExecuteTemplate(w, "course_info", course)
if handleError(ctx, err, w, c.log, "unable to execute template") {
return
}
}
func (c courseServer) RenderEditDescription(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
id := mux.Vars(r)["course_id"]
course, err := c.app.Queries.GetCourse.Handle(ctx, query.GetCourse{
ID: id,
})
if handleError(ctx, err, w, c.log, "unable to get course") {
return
}
err = getCoreTemplate(ctx, c.log).ExecuteTemplate(w, "edit_description", course)
if handleError(ctx, err, w, c.log, "unable to execute template") {
return
}
}
func (c courseServer) UpdateCourseDescription(w http.ResponseWriter, r *http.Request) {
type requestModel struct {
ID string `json:"-"`
Text string `json:"description"`
}
ctx := r.Context()
var req requestModel
req.ID = mux.Vars(r)["course_id"]
err := json.NewDecoder(r.Body).Decode(&req)
if handleError(ctx, err, w, c.log, "unable to read body") {
return
}
err = c.app.Commands.UpdateCourseDescription.Handle(ctx, command.UpdateCourseDescription{
ID: req.ID,
Description: req.Text,
})
if handleError(ctx, err, w, c.log, "unable to update course description") {
return
}
course, err := c.app.Queries.GetCourse.Handle(ctx, query.GetCourse{
ID: req.ID,
})
if handleError(ctx, err, w, c.log, "unable to get course") {
return
}
err = getCoreTemplate(ctx, c.log).ExecuteTemplate(w, "course_info", course)
if handleError(ctx, err, w, c.log, "unable to execute template") {
return
}
}
func (c courseServer) UdpateDescription(w http.ResponseWriter, r *http.Request) {
type requestModel struct {
ID string `json:"id"`
Text string `json:"text"`
}
ctx := r.Context()
var req requestModel
err := json.NewDecoder(r.Body).Decode(&req)
if handleError(ctx, err, w, c.log, "unable to read body") {
return
}
err = c.app.Commands.UpdateCourseDescription.Handle(ctx, command.UpdateCourseDescription{
ID: req.ID,
Description: req.Text,
})
if handleError(ctx, err, w, c.log, "unable to update course description") {
return
}
w.WriteHeader(http.StatusOK)
}