Files
kurious/internal/kurious/ports/http/server.go
Aleksandr Trushkin 9088caf600 fix: unify ErrNotFound — remove domain.ErrNotFound, use errors.ErrNotFound everywhere
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
2026-06-28 07:56:13 +00:00

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
}