Merge pull request 'Added workouts database schema' (#25) from dev into main
This commit was merged in pull request #25.
This commit is contained in:
+1
-1
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.26-alpine AS build
|
||||
FROM golang:1.27-alpine AS build
|
||||
|
||||
RUN apk add --no-cache make git
|
||||
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
### POST login
|
||||
POST http://localhost:8080/api/v1/login
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"email": "admin@example.com",
|
||||
"password": "12345"
|
||||
}
|
||||
|
||||
> {% client.global.set("access_token", response.body.access_token); %}
|
||||
|
||||
###
|
||||
|
||||
POST http://localhost:8080/api/v1/lists
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer {{access_token}}
|
||||
|
||||
{
|
||||
"name": "test_list"
|
||||
}
|
||||
|
||||
###
|
||||
|
||||
GET http://localhost:8080/api/v1/lists
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer {{access_token}}
|
||||
|
||||
{
|
||||
}
|
||||
|
||||
###
|
||||
|
||||
GET http://localhost:8080/api/v1/lists/e817465a-d75d-420b-9d4a-610496ed3bc7
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer {{access_token}}
|
||||
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
###
|
||||
|
||||
POST http://localhost:8080/api/v1/lists/item
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer {{access_token}}
|
||||
|
||||
{
|
||||
"list_id": "cd2c280a-05fa-4ea0-b350-0d3f972b9777",
|
||||
"title": "hello"
|
||||
}
|
||||
|
||||
###
|
||||
|
||||
PUT http://localhost:8080/api/v1/lists/item
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer {{access_token}}
|
||||
|
||||
{
|
||||
"list_item_id": "3b0666b0-bfc5-41e2-8c22-e3437da40298",
|
||||
"title": "hello 2",
|
||||
"is_completed": true
|
||||
}
|
||||
|
||||
###
|
||||
|
||||
POST http://localhost:8080/api/v1/lists/items/3b0666b0-bfc5-41e2-8c22-e3437da40298
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer {{access_token}}
|
||||
|
||||
{
|
||||
"is_completed": false
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/robindittmar/dttmr-api/internal/config"
|
||||
"github.com/robindittmar/dttmr-api/internal/database"
|
||||
"github.com/robindittmar/dttmr-api/internal/database/migrations"
|
||||
"github.com/robindittmar/dttmr-api/internal/domain"
|
||||
"github.com/robindittmar/dttmr-api/internal/repository"
|
||||
)
|
||||
@@ -51,7 +52,7 @@ func main() {
|
||||
}
|
||||
}()
|
||||
|
||||
if err := database.RunMigrations(db); err != nil {
|
||||
if err := database.RunMigrations(db, migrations.MigrationFS); err != nil {
|
||||
slog.Error("failed to run migrations", slog.Any("error", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module github.com/robindittmar/dttmr-api
|
||||
|
||||
go 1.26
|
||||
go 1.27
|
||||
|
||||
require (
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2
|
||||
@@ -8,21 +8,20 @@ require (
|
||||
github.com/golang-migrate/migrate/v4 v4.19.1
|
||||
github.com/jackc/pgx/v5 v5.10.0
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/stretchr/testify v1.11.1
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.70.0
|
||||
go.opentelemetry.io/otel v1.45.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.45.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.45.0
|
||||
go.opentelemetry.io/otel/sdk v1.45.0
|
||||
go.opentelemetry.io/otel/sdk/metric v1.45.0
|
||||
go.opentelemetry.io/otel/trace v1.45.0
|
||||
github.com/stretchr/testify v1.12.1
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.71.0
|
||||
go.opentelemetry.io/otel v1.46.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.46.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.46.0
|
||||
go.opentelemetry.io/otel/sdk v1.46.0
|
||||
go.opentelemetry.io/otel/sdk/metric v1.46.0
|
||||
go.opentelemetry.io/otel/trace v1.46.0
|
||||
golang.org/x/crypto v0.55.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/felixge/httpsnoop v1.1.0 // indirect
|
||||
github.com/go-logr/logr v1.4.4 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
@@ -32,19 +31,18 @@ require (
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/lib/pq v1.12.3 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/stretchr/objx v0.5.3 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.45.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.45.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.46.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.46.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.11.0 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.5 // indirect
|
||||
golang.org/x/net v0.58.0 // indirect
|
||||
golang.org/x/sync v0.22.0 // indirect
|
||||
golang.org/x/sys v0.47.0 // indirect
|
||||
golang.org/x/text v0.41.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect
|
||||
google.golang.org/grpc v1.83.1 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260825221802-da73d73af1c5 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260825221802-da73d73af1c5 // indirect
|
||||
google.golang.org/grpc v1.83.2 // indirect
|
||||
google.golang.org/protobuf v1.36.12 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
@@ -2,9 +2,9 @@ package database
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"embed"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"log/slog"
|
||||
|
||||
"github.com/golang-migrate/migrate/v4"
|
||||
@@ -12,11 +12,8 @@ import (
|
||||
"github.com/golang-migrate/migrate/v4/source/iofs"
|
||||
)
|
||||
|
||||
//go:embed migrations/*.sql
|
||||
var migrationFS embed.FS
|
||||
|
||||
func RunMigrations(db *sql.DB) error {
|
||||
sourceDriver, err := iofs.New(migrationFS, "migrations")
|
||||
func RunMigrations(db *sql.DB, migrationFS fs.FS) error {
|
||||
sourceDriver, err := iofs.New(migrationFS, ".")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load embedded migrations: %w", err)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
DROP TABLE IF EXISTS workout_sets;
|
||||
DROP TABLE IF EXISTS workouts;
|
||||
DROP TABLE IF EXISTS template_exercises;
|
||||
DROP TABLE IF EXISTS templates;
|
||||
DROP TABLE IF EXISTS exercises;
|
||||
@@ -0,0 +1,101 @@
|
||||
CREATE TABLE exercises (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name TEXT NOT NULL,
|
||||
equipment TEXT[] NOT NULL DEFAULT '{}'
|
||||
CHECK (
|
||||
equipment <@ ARRAY [
|
||||
'floor',
|
||||
'rings',
|
||||
'pull_up_bar',
|
||||
'parallel_bars',
|
||||
'low_bar',
|
||||
'parallettes',
|
||||
'resistance_band'
|
||||
]::TEXT[]),
|
||||
metric TEXT NOT NULL DEFAULT 'reps'
|
||||
CHECK (metric IN ('reps', 'seconds')),
|
||||
load TEXT NOT NULL DEFAULT 'bodyweight'
|
||||
CHECK (load IN ('bodyweight', 'absolute')),
|
||||
tags TEXT[] NOT NULL DEFAULT '{}', -- push/pull/legs/core/skill/shoulders/...
|
||||
notes TEXT,
|
||||
hidden BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
modified_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX exercises_name_key ON exercises (lower(name));
|
||||
CREATE INDEX exercises_equipment_idx ON exercises USING gin (equipment);
|
||||
CREATE INDEX exercises_tags_idx ON exercises USING gin (tags);
|
||||
|
||||
|
||||
CREATE TABLE templates (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name TEXT NOT NULL,
|
||||
notes TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE template_exercises (
|
||||
template_id UUID NOT NULL REFERENCES templates(id) ON DELETE CASCADE,
|
||||
exercise_id UUID NOT NULL REFERENCES exercises(id) ON DELETE CASCADE,
|
||||
position SMALLINT NOT NULL,
|
||||
target TEXT,
|
||||
|
||||
PRIMARY KEY (template_id, exercise_id)
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE workouts (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name TEXT NOT NULL,
|
||||
template_id UUID REFERENCES templates(id) ON DELETE SET NULL,
|
||||
started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
ended_at TIMESTAMPTZ,
|
||||
rpe smallint CHECK (rpe BETWEEN 1 AND 10),
|
||||
bodyweight_kg NUMERIC(5,2),
|
||||
notes TEXT
|
||||
);
|
||||
|
||||
CREATE INDEX workouts_time_idx ON workouts (started_at DESC);
|
||||
|
||||
|
||||
CREATE TABLE workout_sets (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
workout_id UUID NOT NULL REFERENCES workouts(id) ON DELETE CASCADE,
|
||||
exercise_id UUID NOT NULL REFERENCES exercises(id) ON DELETE RESTRICT,
|
||||
logged_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
reps SMALLINT,
|
||||
seconds SMALLINT,
|
||||
weight_kg NUMERIC(5,2),
|
||||
note TEXT,
|
||||
|
||||
CONSTRAINT set_has_number CHECK (reps IS NOT NULL OR seconds IS NOT NULL)
|
||||
);
|
||||
|
||||
CREATE INDEX sets_workout_idx ON workout_sets (workout_id, logged_at);
|
||||
CREATE INDEX sets_exercise_ifx ON workout_sets (exercise_id, logged_at DESC);
|
||||
|
||||
INSERT INTO exercises (name, metric, load, tags, equipment) VALUES
|
||||
('Push-up', 'reps', 'bodyweight', '{push,chest,triceps}', '{floor,rings,parallettes}'),
|
||||
('Diamond push-up', 'reps', 'bodyweight', '{push,triceps}', '{floor,rings,parallettes}'),
|
||||
('Pseudo planche push-up','reps', 'bodyweight', '{push,shoulders,skill}', '{floor,rings,parallettes}'),
|
||||
('Pike push-up', 'reps', 'bodyweight', '{push,shoulders}', '{floor,rings,parallettes}'),
|
||||
('Dip', 'reps', 'bodyweight', '{push,chest,triceps}', '{rings,parallel_bars}'),
|
||||
('Handstand hold', 'seconds', 'bodyweight', '{push,shoulders,skill}', '{floor,rings,parallettes}'),
|
||||
('Handstand Push-up', 'reps', 'bodyweight', '{push,shoulders,skill}', '{floor,rings,parallettes}'),
|
||||
('Pull-up', 'reps', 'bodyweight', '{pull,back,biceps}', '{rings,pull_up_bar}'),
|
||||
('Chin-up', 'reps', 'bodyweight', '{pull,back,biceps}', '{rings,pull_up_bar}'),
|
||||
('Inverted row', 'reps', 'bodyweight', '{pull,back}', '{rings,parallel_bars,low_bar}'),
|
||||
('Muscle-up', 'reps', 'bodyweight', '{pull,push,skill}', '{rings,pull_up_bar}'),
|
||||
('Dead hang', 'seconds', 'bodyweight', '{pull,grip}', '{rings,pull_up_bar}'),
|
||||
('Front lever hold', 'seconds', 'bodyweight', '{pull,core,skill}', '{rings,pull_up_bar,low_bar}'),
|
||||
('Pistol squat', 'reps', 'bodyweight', '{legs}', '{floor}'),
|
||||
('Bulgarian split squat','reps', 'bodyweight', '{legs}', '{floor}'),
|
||||
('Nordic curl', 'reps', 'bodyweight', '{legs,hamstrings}', '{floor}'),
|
||||
('Calf raise', 'reps', 'bodyweight', '{legs}', '{floor}'),
|
||||
('Plank', 'seconds', 'bodyweight', '{core}', '{floor}'),
|
||||
('Hollow body hold', 'seconds', 'bodyweight', '{core}', '{floor}'),
|
||||
('L-sit', 'seconds', 'bodyweight', '{core,skill}', '{floor,parallettes,parallel_bars}'),
|
||||
('Hanging leg raise', 'reps', 'bodyweight', '{core}', '{rings,pull_up_bar}'),
|
||||
('Face pull', 'reps', 'bodyweight', '{pull,shoulders}', '{rings,resistance_band}'),
|
||||
('Lateral raise', 'reps', 'absolute', '{shoulders,isolation}', '{floor}');
|
||||
@@ -0,0 +1,6 @@
|
||||
package migrations
|
||||
|
||||
import "embed"
|
||||
|
||||
//go:embed *.sql
|
||||
var MigrationFS embed.FS
|
||||
@@ -149,11 +149,11 @@ func (s *AuthService) issueTokens(ctx context.Context, authUser *AuthUser) (Toke
|
||||
|
||||
func (s *AuthService) GenerateAccessToken(authUser *AuthUser) (string, error) {
|
||||
claims := JWTClaims{
|
||||
UserID: authUser.ID,
|
||||
Email: authUser.Email,
|
||||
Name: authUser.Name,
|
||||
UserID: authUser.ID,
|
||||
Email: authUser.Email,
|
||||
Name: authUser.Name,
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Minute * 15)),
|
||||
}
|
||||
claims.ExpiresAt = jwt.NewNumericDate(time.Now().Add(time.Minute * 15))
|
||||
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user