Responds to PR #5 review comment: consolidate on common/errors.ErrNotFound instead of having two separate error types (domain.PlainError and common/errors.SimpleError) for the same sentinel. - Remove domain.ErrNotFound and domain.PlainError type - Keep domain.ErrNotImplemented as alias to common/errors.ErrNotImplemented - Update sqlite_organization_repository to return cerrors.ErrNotFound - Update sqlite_learning_category_repository to return cerrors.ErrNotFound - Update server.go handleError to check only errors.ErrNotFound - Update test to assert errors.ErrNotFound - Remove unused domain import from server.go
150 lines
3.2 KiB
Go
150 lines
3.2 KiB
Go
package http
|
|
|
|
import (
|
|
"context"
|
|
stderrors "errors"
|
|
"log/slog"
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"git.loyso.art/frx/kurious/internal/common/errors"
|
|
"git.loyso.art/frx/kurious/internal/common/xcontext"
|
|
"git.loyso.art/frx/kurious/internal/kurious/service"
|
|
"git.loyso.art/frx/kurious/pkg/xdefault"
|
|
|
|
"github.com/gorilla/mux"
|
|
"go.opentelemetry.io/otel/codes"
|
|
"go.opentelemetry.io/otel/trace"
|
|
)
|
|
|
|
type Server struct {
|
|
app service.Application
|
|
log *slog.Logger
|
|
}
|
|
|
|
func NewServer(app service.Application, log *slog.Logger) Server {
|
|
return Server{
|
|
app: app,
|
|
log: log,
|
|
}
|
|
}
|
|
|
|
func (s Server) Courses() courseTemplServer {
|
|
return courseTemplServer(s)
|
|
}
|
|
|
|
func handleError(ctx context.Context, err error, w http.ResponseWriter, log *slog.Logger, msg string) bool {
|
|
if err == nil {
|
|
return false
|
|
}
|
|
|
|
span := trace.SpanFromContext(ctx)
|
|
span.RecordError(err)
|
|
span.SetStatus(codes.Error, "error during handling request")
|
|
|
|
var errorString string
|
|
var code int
|
|
valErr := new(errors.ValidationError)
|
|
switch {
|
|
case stderrors.As(err, &valErr):
|
|
errorString = valErr.Error()
|
|
code = http.StatusBadRequest
|
|
case stderrors.Is(err, errors.ErrNotFound):
|
|
errorString = err.Error()
|
|
code = http.StatusNotFound
|
|
default:
|
|
errorString = "internal server error"
|
|
code = http.StatusInternalServerError
|
|
}
|
|
|
|
xcontext.LogWithWarnError(ctx, log, err, msg, slog.Int("status_code", code), slog.String("response", errorString))
|
|
|
|
http.Error(w, errorString, code)
|
|
|
|
return true
|
|
}
|
|
|
|
type pagination struct {
|
|
NextPageToken string
|
|
PerPage int
|
|
Page int
|
|
}
|
|
|
|
func parsePaginationFromQuery(r *http.Request) (out pagination, err error) {
|
|
query := r.URL.Query()
|
|
|
|
if query.Has("next") && query.Has("page") {
|
|
return out, errors.NewValidationError("next", `could not be set together with "page"`)
|
|
}
|
|
|
|
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 = 20
|
|
}
|
|
if out.PerPage < 1 {
|
|
out.PerPage = 1
|
|
} else if out.PerPage > 100 {
|
|
out.PerPage = 100
|
|
}
|
|
if query.Has("page") {
|
|
out.Page, err = strconv.Atoi(query.Get("page"))
|
|
if err != nil {
|
|
return out, errors.NewValidationError("page", "bad page value")
|
|
}
|
|
} else if !query.Has("next") {
|
|
out.Page = 1
|
|
}
|
|
if out.Page < 1 && !query.Has("next") {
|
|
out.Page = 1
|
|
}
|
|
|
|
return out, nil
|
|
}
|
|
|
|
const (
|
|
LearningTypePathParam = "learning_type"
|
|
ThematicTypePathParam = "thematic_type"
|
|
)
|
|
|
|
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[LearningTypePathParam]
|
|
out.CourseThematic = vars[ThematicTypePathParam]
|
|
|
|
out.School = r.URL.Query().Get("school_id")
|
|
out.OrderBy = xdefault.WithFallback(r.URL.Query().Get("order_by"), "price")
|
|
|
|
if r.URL.Query().Has("asc") {
|
|
out.Ascending, _ = strconv.ParseBool(r.URL.Query().Get("asc"))
|
|
}
|
|
|
|
return out, nil
|
|
}
|
|
|
|
type listCoursesParams struct {
|
|
pagination
|
|
|
|
CourseThematic string
|
|
LearningType string
|
|
School string
|
|
OrderBy string
|
|
Ascending bool
|
|
}
|
|
|
|
type IDNamePair struct {
|
|
ID string
|
|
Name string
|
|
IsActive bool
|
|
}
|