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
+41
View File
@@ -0,0 +1,41 @@
package domain
import (
"context"
"time"
)
type ExerciseRepository interface {
CreateExercise(ctx context.Context) (*Exercise, error)
DeleteExercise(ctx context.Context, id string) error
GetExercises(ctx context.Context, offset int, count int) ([]Exercise, error)
CountExercises(ctx context.Context) (int, error)
}
type Exercise struct {
ID string `json:"id"`
Name string `json:"name"`
Equipment []Equipment `json:"equipment"`
Metric Metric `json:"metric"`
Load Load `json:"load"`
Tags []string `json:"tags"`
Notes *string `json:"notes"`
ModifiedAt time.Time `json:"modified_at"`
}
type ExerciseService struct {
repo ExerciseRepository
}
func NewExerciseService(r ExerciseRepository) *ExerciseService {
return &ExerciseService{repo: r}
}
func (s *ExerciseService) GetExercises(ctx context.Context, page int, count int) ([]Exercise, error) {
offset := (page - 1) * count
return s.repo.GetExercises(ctx, offset, count)
}
func (s *ExerciseService) CountExercises(ctx context.Context) (int, error) {
return s.repo.CountExercises(ctx)
}
+134
View File
@@ -0,0 +1,134 @@
package domain
import (
"database/sql/driver"
"fmt"
"strings"
)
type Equipment int
const (
EquipmentUnknown Equipment = iota
EquipmentFloor
EquipmentRings
EquipmentPullUpBar
EquipmentParallelBars
EquipmentLowBar
EquipmentParallettes
EquipmentResistanceBand
)
var equipmentNames = [...]string{
EquipmentUnknown: "",
EquipmentFloor: "floor",
EquipmentRings: "rings",
EquipmentPullUpBar: "pull_up_bar",
EquipmentParallelBars: "parallel_bars",
EquipmentLowBar: "low_bar",
EquipmentParallettes: "parallettes",
EquipmentResistanceBand: "resistance_band",
}
var equipmentValues = func() map[string]Equipment {
m := make(map[string]Equipment, len(equipmentNames))
for i, name := range equipmentNames {
m[name] = Equipment(i)
}
return m
}()
func (e Equipment) String() string {
if e < 0 || int(e) > len(equipmentNames) {
return ""
}
return equipmentNames[e]
}
func ParseEquipment(s string) (Equipment, error) {
if e, ok := equipmentValues[s]; ok {
return e, nil
}
return EquipmentUnknown, fmt.Errorf("equipment: unknown value %q", s)
}
//func (e Equipment) MarshalJSON() ([]byte, error) {
// s := e.String()
// if s == "" {
// return nil, fmt.Errorf("equipment: cannot marshal value %d", int(e))
// }
// return json.Marshal(s)
//}
//
//func (e *Equipment) UnmarshalJSON(data []byte) error {
// var s string
// if err := json.Unmarshal(data, &s); err != nil {
// return err
// }
//
// v, err := ParseEquipment(s)
// if err != nil {
// return err
// }
//
// *e = v
// return nil
//}
type EquipmentSet []Equipment
func (s *EquipmentSet) Scan(src any) error {
if src == nil {
*s = nil
return nil
}
var raw string
switch v := src.(type) {
case string:
raw = v
case []byte:
raw = string(v)
default:
return fmt.Errorf("equipment: cannot scan %T", src)
}
names, err := parseTextArray(raw)
if err != nil {
return err
}
out := make(EquipmentSet, len(names))
for i, name := range names {
e, err := ParseEquipment(name)
if err != nil {
return err
}
out[i] = e
}
*s = out
return nil
}
func (s EquipmentSet) Value() (driver.Value, error) {
names := make([]string, len(s))
for i, e := range s {
name := e.String()
if name == "" {
return nil, fmt.Errorf("equipment: cannot store value %d", int(e))
}
names[i] = name
}
return "{" + strings.Join(names, ",") + "}", nil
}
func parseTextArray(raw string) ([]string, error) {
raw = strings.TrimSpace(raw)
if len(raw) < 2 || raw[0] != '{' || raw[len(raw)-1] != '}' {
return nil, fmt.Errorf("equipment: malformed array %q", raw)
}
if inner := raw[1 : len(raw)-1]; inner != "" {
return strings.Split(inner, ","), nil
}
return nil, nil
}
+95
View File
@@ -0,0 +1,95 @@
package domain
import (
"database/sql/driver"
"fmt"
)
type Load int
const (
LoadUnknown Load = iota
LoadBodyweight
LoadExternal
)
var loadNames = [...]string{
LoadUnknown: "",
LoadBodyweight: "bodyweight",
LoadExternal: "external",
}
var loadValues = func() map[string]Load {
m := make(map[string]Load, len(loadNames))
for i, name := range loadNames {
m[name] = Load(i)
}
return m
}()
func (l Load) String() string {
if l < 0 || int(l) > len(loadNames) {
return ""
}
return loadNames[l]
}
func ParseLoad(s string) (Load, error) {
if l, ok := loadValues[s]; ok {
return l, nil
}
return LoadUnknown, fmt.Errorf("load: unknown value %q", s)
}
//func (l Load) MarshalJSON() ([]byte, error) {
// s := l.String()
// if s == "" {
// return nil, fmt.Errorf("load: cannot marshal value %d", int(l))
// }
// return json.Marshal(s)
//}
//
//func (l *Load) UnmarshalJSON(data []byte) error {
// var s string
// if err := json.Unmarshal(data, &s); err != nil {
// return err
// }
//
// v, err := ParseLoad(s)
// if err != nil {
// return err
// }
//
// *l = v
// return nil
//}
func (l *Load) Scan(src any) error {
var s string
switch v := src.(type) {
case nil:
return fmt.Errorf("load: unexpected NULL")
case string:
s = v
case []byte:
s = string(v)
default:
return fmt.Errorf("load: cannot scan %T", src)
}
parsed, err := ParseLoad(s)
if err != nil {
return err
}
*l = parsed
return nil
}
func (l Load) Value() (driver.Value, error) {
s := l.String()
if s == "" {
return nil, fmt.Errorf("load: cannot store value %d", int(l))
}
return s, nil
}
+95
View File
@@ -0,0 +1,95 @@
package domain
import (
"database/sql/driver"
"fmt"
)
type Metric int
const (
MetricUnknown Metric = iota
MetricReps
MetricSeconds
)
var metricNames = [...]string{
MetricUnknown: "",
MetricReps: "reps",
MetricSeconds: "seconds",
}
var metricValues = func() map[string]Metric {
m := make(map[string]Metric, len(metricNames))
for i, name := range metricNames {
m[name] = Metric(i)
}
return m
}()
func (m Metric) String() string {
if m < 0 || int(m) > len(metricNames) {
return ""
}
return metricNames[m]
}
func ParseMetric(s string) (Metric, error) {
if m, ok := metricValues[s]; ok {
return m, nil
}
return MetricUnknown, fmt.Errorf("metric: unknown value %q", s)
}
//func (m Metric) MarshalJSON() ([]byte, error) {
// s := m.String()
// if s == "" {
// return nil, fmt.Errorf("metric: cannot marshal value %d", int(m))
// }
// return json.Marshal(s)
//}
//
//func (m *Metric) UnmarshalJSON(data []byte) error {
// var s string
// if err := json.Unmarshal(data, &s); err != nil {
// return err
// }
//
// v, err := ParseMetric(s)
// if err != nil {
// return err
// }
//
// *m = v
// return nil
//}
func (m *Metric) Scan(src any) error {
var s string
switch v := src.(type) {
case nil:
return fmt.Errorf("metric: unexpected NULL")
case string:
s = v
case []byte:
s = string(v)
default:
return fmt.Errorf("metric: cannot scan %T", src)
}
parsed, err := ParseMetric(s)
if err != nil {
return err
}
*m = parsed
return nil
}
func (m Metric) Value() (driver.Value, error) {
s := m.String()
if s == "" {
return nil, fmt.Errorf("metric: cannot store value %d", int(m))
}
return s, nil
}