Compare commits
3 Commits
master
...
4f89f59232
| Author | SHA1 | Date | |
|---|---|---|---|
| 4f89f59232 | |||
| 40e5621eb9 | |||
| 23c29aba1d |
@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strings"
|
||||
@ -54,6 +55,7 @@ func setupHTTP(cfg config.HTTP, srv xhttp.Server, log *slog.Logger) *http.Server
|
||||
router := mux.NewRouter()
|
||||
|
||||
router.Use(
|
||||
middlewareRecovery(log),
|
||||
middlewareCustomWriterInjector(),
|
||||
mux.CORSMethodMiddleware(router),
|
||||
middlewareLogger(log),
|
||||
@ -101,6 +103,29 @@ func setupHTTP(cfg config.HTTP, srv xhttp.Server, log *slog.Logger) *http.Server
|
||||
}
|
||||
}
|
||||
|
||||
func middlewareRecovery(log *slog.Logger) mux.MiddlewareFunc {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
defer func() {
|
||||
rec := recover()
|
||||
if rec == nil {
|
||||
return
|
||||
}
|
||||
if rec == http.ErrAbortHandler {
|
||||
panic(rec)
|
||||
}
|
||||
xcontext.LogWithError(
|
||||
r.Context(), log, fmt.Errorf("%v", rec), "recovered from panic",
|
||||
slog.String("method", r.Method),
|
||||
slog.String("path", r.URL.Path),
|
||||
)
|
||||
http.Error(w, "internal server error", http.StatusInternalServerError)
|
||||
}()
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func middlewareCustomWriterInjector() mux.MiddlewareFunc {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@ -50,7 +50,7 @@ func setupOtelSDK(ctx context.Context, cfg config.Trace) (shutdown shutdownFunc,
|
||||
return err
|
||||
}
|
||||
|
||||
resource, err := makeServiceResource(ctx)
|
||||
resource, err := makeServiceResource(ctx, cfg.Environment)
|
||||
if err != nil {
|
||||
return shutdown, fmt.Errorf("making service resource: %w", err)
|
||||
}
|
||||
@ -102,7 +102,10 @@ type TraceProviderParams struct {
|
||||
Type config.TraceClientType
|
||||
}
|
||||
|
||||
func makeServiceResource(ctx context.Context) (*resource.Resource, error) {
|
||||
func makeServiceResource(ctx context.Context, environment string) (*resource.Resource, error) {
|
||||
if environment == "" {
|
||||
environment = "development"
|
||||
}
|
||||
r, err := resource.New(
|
||||
ctx,
|
||||
resource.WithDetectors(
|
||||
@ -113,7 +116,7 @@ func makeServiceResource(ctx context.Context) (*resource.Resource, error) {
|
||||
resource.WithHost(),
|
||||
resource.WithAttributes(
|
||||
semconv.ServiceName("bigstats:kuriweb"),
|
||||
semconv.DeploymentEnvironment("production"),
|
||||
semconv.DeploymentEnvironment(environment),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
|
||||
@ -29,10 +29,11 @@ func (t *TraceClientType) UnmarshalText(data []byte) error {
|
||||
}
|
||||
|
||||
type Trace struct {
|
||||
Endpoint string `json:"endpoint"`
|
||||
APIKey string `json:"api_key"`
|
||||
APIHeader string `json:"api_header"`
|
||||
Type TraceClientType `json:"type"`
|
||||
Endpoint string `json:"endpoint"`
|
||||
APIKey string `json:"api_key"`
|
||||
APIHeader string `json:"api_header"`
|
||||
Type TraceClientType `json:"type"`
|
||||
Environment string `json:"environment"`
|
||||
|
||||
ShowMetrics bool `json:"show_metrics"`
|
||||
}
|
||||
|
||||
@ -10,7 +10,6 @@ import (
|
||||
"time"
|
||||
|
||||
"git.loyso.art/frx/kurious/internal/common/nullable"
|
||||
"git.loyso.art/frx/kurious/internal/common/xcontext"
|
||||
"git.loyso.art/frx/kurious/internal/common/xslices"
|
||||
"git.loyso.art/frx/kurious/internal/kurious/domain"
|
||||
|
||||
@ -112,7 +111,7 @@ func (r *sqliteCourseRepository) List(
|
||||
|
||||
result.Count, err = r.listCount(ctx, params)
|
||||
if err != nil {
|
||||
xcontext.LogWithWarnError(ctx, r.log, err, "unable to list count")
|
||||
return result, fmt.Errorf("listing count: %w", err)
|
||||
}
|
||||
|
||||
span.SetAttributes(
|
||||
|
||||
@ -85,6 +85,7 @@ func (h *syncSravniHandler) Handle(ctx context.Context) (err error) {
|
||||
courses := make([]sravni.Course, 0, 1024)
|
||||
buffer := make([]sravni.Course, 0, 512)
|
||||
organizations := make([]sravni.Organization, 0, 256)
|
||||
var insertErr error
|
||||
for _, learningType := range learningTypes.Fields {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
@ -174,22 +175,22 @@ func (h *syncSravniHandler) Handle(ctx context.Context) (err error) {
|
||||
|
||||
var insertCourseSuccess bool
|
||||
if len(courses) > 0 {
|
||||
err = h.insertCourses(lctx, courses)
|
||||
if err != nil {
|
||||
xcontext.LogWithError(lctx, h.log, err, "unable to insert courses")
|
||||
if cerr := h.insertCourses(lctx, courses); cerr != nil {
|
||||
xcontext.LogWithError(lctx, h.log, cerr, "unable to insert courses")
|
||||
insertErr = errors.Join(insertErr, cerr)
|
||||
} else {
|
||||
insertCourseSuccess = true
|
||||
}
|
||||
|
||||
insertCourseSuccess = err == nil
|
||||
}
|
||||
|
||||
var insertOrgsSuccess bool
|
||||
if len(organizations) > 0 {
|
||||
err = h.insertOrganizations(lctx, organizations)
|
||||
if err != nil {
|
||||
xcontext.LogWithError(lctx, h.log, err, "unable to insert courses")
|
||||
if oerr := h.insertOrganizations(lctx, organizations); oerr != nil {
|
||||
xcontext.LogWithError(lctx, h.log, oerr, "unable to insert organizations")
|
||||
insertErr = errors.Join(insertErr, oerr)
|
||||
} else {
|
||||
insertOrgsSuccess = true
|
||||
}
|
||||
|
||||
insertOrgsSuccess = err == nil
|
||||
}
|
||||
|
||||
elapsed = time.Since(start) - elapsed
|
||||
@ -205,7 +206,7 @@ func (h *syncSravniHandler) Handle(ctx context.Context) (err error) {
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
return insertErr
|
||||
}
|
||||
|
||||
func (h *syncSravniHandler) loadEducationalProducts(ctx context.Context, learningType, courseThematic string, buf []sravni.Course) ([]sravni.Course, map[string]sravni.Organization, error) {
|
||||
@ -384,8 +385,12 @@ func courseAsCreateCourseParams(course sravni.Course) command.CreateCourse {
|
||||
startAt = *course.DateStart
|
||||
}
|
||||
if course.TimeStart != nil {
|
||||
startAtUnix := startAt.Unix() + course.TimeStart.Unix()
|
||||
startAt = time.Unix(startAtUnix, 0)
|
||||
clock := *course.TimeStart
|
||||
startAt = time.Date(
|
||||
startAt.Year(), startAt.Month(), startAt.Day(),
|
||||
clock.Hour(), clock.Minute(), clock.Second(), clock.Nanosecond(),
|
||||
startAt.Location(),
|
||||
)
|
||||
}
|
||||
|
||||
var courseDuration time.Duration
|
||||
|
||||
@ -23,7 +23,7 @@ import (
|
||||
var (
|
||||
paramsAttr = attribute.Key("params")
|
||||
|
||||
webtracer = otel.Tracer("http")
|
||||
webtracer = otel.Tracer("kuriweb.http")
|
||||
)
|
||||
|
||||
type courseTemplServer struct {
|
||||
@ -213,6 +213,15 @@ func (c courseTemplServer) List(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
})
|
||||
|
||||
totalPages := 0
|
||||
if pathParams.PerPage > 0 {
|
||||
totalPages = listCoursesResult.Count / pathParams.PerPage
|
||||
}
|
||||
currentPage := pathParams.Page
|
||||
if currentPage > 0 && totalPages > 0 && currentPage > totalPages {
|
||||
currentPage = totalPages
|
||||
}
|
||||
|
||||
params = bootstrap.ListCoursesParams{
|
||||
FilterForm: bootstrap.FilterFormParams{
|
||||
Render: true,
|
||||
@ -233,8 +242,8 @@ func (c courseTemplServer) List(w http.ResponseWriter, r *http.Request) {
|
||||
Courses: params.Courses,
|
||||
Categories: params.Categories,
|
||||
Pagination: bootstrap.Pagination{
|
||||
Page: pathParams.Page,
|
||||
TotalPages: listCoursesResult.Count / pathParams.PerPage,
|
||||
Page: currentPage,
|
||||
TotalPages: totalPages,
|
||||
BaseURL: r.URL.Path,
|
||||
},
|
||||
}
|
||||
@ -285,7 +294,10 @@ func (c courseTemplServer) Index(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
stats := bootstrap.MakeNewStats(1, 2, 3)
|
||||
|
||||
coursesResult, err := c.app.Queries.ListCourses.Handle(ctx, query.ListCourse{})
|
||||
const indexCoursesLimit = 200
|
||||
coursesResult, err := c.app.Queries.ListCourses.Handle(ctx, query.ListCourse{
|
||||
Limit: indexCoursesLimit,
|
||||
})
|
||||
if handleError(ctx, err, w, c.log, "unable to list courses") {
|
||||
return
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@ import (
|
||||
|
||||
"git.loyso.art/frx/kurious/internal/common/errors"
|
||||
"git.loyso.art/frx/kurious/internal/common/xcontext"
|
||||
"git.loyso.art/frx/kurious/internal/kurious/domain"
|
||||
"git.loyso.art/frx/kurious/internal/kurious/service"
|
||||
"git.loyso.art/frx/kurious/pkg/xdefault"
|
||||
|
||||
@ -49,7 +50,7 @@ func handleError(ctx context.Context, err error, w http.ResponseWriter, log *slo
|
||||
case stderrors.As(err, &valErr):
|
||||
errorString = valErr.Error()
|
||||
code = http.StatusBadRequest
|
||||
case stderrors.Is(err, errors.ErrNotFound):
|
||||
case stderrors.Is(err, errors.ErrNotFound), stderrors.Is(err, domain.ErrNotFound):
|
||||
errorString = err.Error()
|
||||
code = http.StatusNotFound
|
||||
default:
|
||||
@ -87,14 +88,22 @@ func parsePaginationFromQuery(r *http.Request) (out pagination, err error) {
|
||||
} 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 per_page value")
|
||||
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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user