feat: added exercises handler/service/repo

This commit is contained in:
2026-09-04 12:29:44 +02:00
parent f0304cda1a
commit e1f274b744
9 changed files with 561 additions and 4 deletions
+63
View File
@@ -0,0 +1,63 @@
package handler
import (
"log/slog"
"net/http"
"github.com/robindittmar/dttmr-api/internal/api/request"
"github.com/robindittmar/dttmr-api/internal/api/response"
"github.com/robindittmar/dttmr-api/internal/domain"
)
type ExerciseHandler struct {
ExerciseService *domain.ExerciseService
}
func NewExerciseHandler(exerciseService *domain.ExerciseService) *ExerciseHandler {
return &ExerciseHandler{ExerciseService: exerciseService}
}
// GetExercises handles fetching the list of exercises
//
// @Summary Get exercises route
// @Description Gets a list of all exercises
// @Tags Exercise
// @Accept json
// @Produce json
// @Param page query int false "page"
// @Param count query int false "count"
// @Success 200 {object} response.Paginated[domain.Exercise]
// @Error 400 {object} response.ErrorResponse "failed to decode request body"
// @Error 400 {object} response.ErrorResponse "invalid value for page"
// @Error 400 {object} response.ErrorResponse "invalid value for count"
// @Error 500 {object} response.ErrorResponse "failed to get exercises"
// @Router /exercises [get]
func (h *ExerciseHandler) GetExercises(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
page, count, err := request.ParsePaginatedQueryParams(r)
if err != nil {
response.Error(ctx, w, http.StatusBadRequest, err.Error())
return
}
exercises, err := h.ExerciseService.GetExercises(ctx, page, count)
if err != nil {
slog.ErrorContext(ctx, "failed to get exercises", slog.Any("error", err))
response.Error(ctx, w, http.StatusInternalServerError, "failed to get exercises")
return
}
total, err := h.ExerciseService.CountExercises(ctx)
if err != nil {
slog.ErrorContext(ctx, "failed to count exercises", slog.Any("error", err))
response.Error(ctx, w, http.StatusInternalServerError, "failed to get exercises")
return
}
response.JSON(ctx, w, http.StatusOK, response.Paginated[domain.Exercise]{
Count: len(exercises),
Total: total,
Data: exercises,
})
}
+58
View File
@@ -0,0 +1,58 @@
package request
import (
"errors"
"log/slog"
"net/http"
"strconv"
)
var (
ErrFailedToDecodeRequestQuery = errors.New("failed to decode request query")
ErrInvalidPageValue = errors.New("invalid value for page")
ErrInvalidCountValue = errors.New("invalid value for count")
)
func ParsePaginatedQueryParams(r *http.Request) (int, int, error) {
ctx := r.Context()
pageStr := r.URL.Query().Get("page")
if pageStr == "" {
pageStr = "1"
}
countStr := r.URL.Query().Get("count")
if countStr == "" {
countStr = "10"
}
page, err := strconv.Atoi(pageStr)
if err != nil {
slog.ErrorContext(ctx,
"failed to read page from query",
slog.String("page", pageStr))
return 0, 0, ErrFailedToDecodeRequestQuery
}
count, err := strconv.Atoi(countStr)
if err != nil {
slog.ErrorContext(ctx,
"failed to read count from query",
slog.String("count", countStr))
return 0, 0, ErrFailedToDecodeRequestQuery
}
if page < 1 {
slog.ErrorContext(ctx,
"page parameter is invalid",
slog.Int("page", page))
return 0, 0, ErrInvalidPageValue
}
if count <= 0 {
slog.ErrorContext(ctx,
"count parameter is invalid",
slog.Int("count", count))
return 0, 0, ErrInvalidCountValue
}
return page, count, nil
}
+5
View File
@@ -26,11 +26,13 @@ func NewMux(cfg Config) http.Handler {
userService := domain.NewUserService(store.User)
registrationService := domain.NewRegistrationService(store, userService, inviteService)
listService := domain.NewListService(store, store.List)
exerciseService := domain.NewExerciseService(store.Exercise)
authHandler := handler.NewAuthHandler(authService)
inviteHandler := handler.NewInviteHandler(inviteService)
userHandler := handler.NewUserHandler(userService, authService, registrationService)
listHandler := handler.NewListHandler(listService, userService)
exerciseHandler := handler.NewExerciseHandler(exerciseService)
protected := middleware.WithJWT(authService)
@@ -70,6 +72,9 @@ func NewMux(cfg Config) http.Handler {
apiMux.Handle("POST /lists/items/{id}/complete", protected(listHandler.SetListItemCompleted))
apiMux.Handle("GET /lists/{id}", protected(listHandler.GetListItems))
// Exercises
apiMux.Handle("GET /exercises", protected(exerciseHandler.GetExercises))
mux := http.NewServeMux()
mux.Handle("/api/v1/", http.StripPrefix("/api/v1", apiMux))