Skip to content

Commit

Permalink
feat: workout start time (#78)
Browse files Browse the repository at this point in the history
  • Loading branch information
crlssn authored Nov 24, 2024
1 parent 4ff8ae6 commit 70f89ce
Show file tree
Hide file tree
Showing 8 changed files with 176 additions and 120 deletions.
3 changes: 3 additions & 0 deletions database/migrations/009_workout_started_at.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
ALTER TABLE getstronger.workouts ADD COLUMN started_at TIMESTAMP WITHOUT TIME ZONE;
UPDATE getstronger.workouts SET started_at = finished_at;
ALTER TABLE getstronger.workouts ALTER COLUMN started_at SET NOT NULL;
3 changes: 2 additions & 1 deletion protobufs/api/v1/workouts.proto
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ message GetLatestExerciseSetsResponse {
message CreateWorkoutRequest {
string routine_id = 1 [(buf.validate.field).string.uuid = true];
repeated ExerciseSets exercise_sets = 2 [(buf.validate.field).repeated.min_items = 1];
google.protobuf.Timestamp finished_at = 3 [(buf.validate.field).required = true];
google.protobuf.Timestamp started_at = 3 [(buf.validate.field).required = true];
google.protobuf.Timestamp finished_at = 4 [(buf.validate.field).required = true];
}
message CreateWorkoutResponse {
string workout_id = 1;
Expand Down
11 changes: 9 additions & 2 deletions server/pkg/orm/workouts.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

216 changes: 115 additions & 101 deletions server/pkg/pb/api/v1/workouts.pb.go

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions server/pkg/repo/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,7 @@ type CreateWorkoutParams struct {
Name string
UserID string
ExerciseSets []ExerciseSet
StartedAt time.Time
FinishedAt time.Time
}

Expand All @@ -610,6 +611,7 @@ func (r *Repo) CreateWorkout(ctx context.Context, p CreateWorkoutParams) (*orm.W
workout := &orm.Workout{
Name: p.Name,
UserID: p.UserID,
StartedAt: p.StartedAt.Truncate(time.Minute).UTC(),
FinishedAt: p.FinishedAt.Truncate(time.Minute).UTC(),
}

Expand Down
6 changes: 6 additions & 0 deletions server/rpc/v1/workout.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ func (h *workoutHandler) Create(ctx context.Context, req *connect.Request[v1.Cre
log := xcontext.MustExtractLogger(ctx)
userID := xcontext.MustExtractUserID(ctx)

if req.Msg.GetStartedAt().AsTime().After(req.Msg.GetFinishedAt().AsTime()) {
log.Warn("workout cannot start after it finishes")
return nil, connect.NewError(connect.CodeInvalidArgument, nil)
}

routine, err := h.repo.GetRoutine(ctx, repo.GetRoutineWithID(req.Msg.GetRoutineId()))
if err != nil {
log.Error("failed to get routine", zap.Error(err))
Expand All @@ -44,6 +49,7 @@ func (h *workoutHandler) Create(ctx context.Context, req *connect.Request[v1.Cre
workout, err := h.repo.CreateWorkout(ctx, repo.CreateWorkoutParams{
Name: routine.Title,
UserID: userID,
StartedAt: req.Msg.GetStartedAt().AsTime(),
FinishedAt: req.Msg.GetFinishedAt().AsTime(),
ExerciseSets: parseExerciseSetsFromPB(req.Msg.GetExerciseSets()),
})
Expand Down
10 changes: 8 additions & 2 deletions web/src/pb/api/v1/workouts_pb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,12 @@ export class CreateWorkoutRequest extends Message<CreateWorkoutRequest> {
exerciseSets: ExerciseSets[] = [];

/**
* @generated from field: google.protobuf.Timestamp finished_at = 3;
* @generated from field: google.protobuf.Timestamp started_at = 3;
*/
startedAt?: Timestamp;

/**
* @generated from field: google.protobuf.Timestamp finished_at = 4;
*/
finishedAt?: Timestamp;

Expand All @@ -110,7 +115,8 @@ export class CreateWorkoutRequest extends Message<CreateWorkoutRequest> {
static readonly fields: FieldList = proto3.util.newFieldList(() => [
{ no: 1, name: "routine_id", kind: "scalar", T: 9 /* ScalarType.STRING */ },
{ no: 2, name: "exercise_sets", kind: "message", T: ExerciseSets, repeated: true },
{ no: 3, name: "finished_at", kind: "message", T: Timestamp },
{ no: 3, name: "started_at", kind: "message", T: Timestamp },
{ no: 4, name: "finished_at", kind: "message", T: Timestamp },
]);

static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): CreateWorkoutRequest {
Expand Down
45 changes: 31 additions & 14 deletions web/src/views/Workouts/WorkoutRoutine.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ const routine = ref<Routine | undefined>(undefined)
const routineID = route.params.routine_id as string
const workoutStore = useWorkoutStore()
const pageTitleStore = usePageTitleStore()
const dateTime = ref(DateTime.now().toFormat("yyyy-MM-dd'T'HH:mm"))
const startDateTime = ref(DateTime.now().toFormat("yyyy-MM-dd'T'HH:mm"))
const endDateTime = ref(DateTime.now().toFormat("yyyy-MM-dd'T'HH:mm"))
let dateTimeInterval: ReturnType<typeof setInterval>
const reqError = ref('')
const prevExerciseSets = ref<ExerciseSets[]>([])
Expand Down Expand Up @@ -48,7 +49,7 @@ const fetchLatestExerciseSets = async () => {
}
const updateDateTime = () => {
dateTime.value = DateTime.now().toFormat("yyyy-MM-dd'T'HH:mm")
endDateTime.value = DateTime.now().toFormat("yyyy-MM-dd'T'HH:mm")
}
const clearDateTimeInterval = () => {
Expand Down Expand Up @@ -95,8 +96,11 @@ const finishWorkout = async () => {
new CreateWorkoutRequest({
routineId: routineID,
exerciseSets: eSetsList,
startedAt: new Timestamp({
seconds: BigInt(DateTime.fromISO(startDateTime.value).toSeconds()),
}),
finishedAt: new Timestamp({
seconds: BigInt(DateTime.fromISO(dateTime.value).toSeconds()),
seconds: BigInt(DateTime.fromISO(endDateTime.value).toSeconds()),
}),
}),
)
Expand Down Expand Up @@ -195,19 +199,32 @@ const isNumber = (value: number | undefined | string) => {
</li>
</ul>
</div>
<div>
<label class="block text-xs font-semibold text-gray-900 uppercase">Date</label>
<div class="mt-2">
<input
v-model="dateTime"
type="datetime-local"
@input="clearDateTimeInterval"
required
class="block w-full rounded-md border-0 bg-white px-3 py-3 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm/6"
/>
<div class="flex gap-x-6">
<div class="w-full">
<label class="block text-xs font-semibold text-gray-900 uppercase">Start Date</label>
<div>
<input
v-model="startDateTime"
type="datetime-local"
required
class="block w-full rounded-md border-0 bg-white px-3 py-3 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm/6"
/>
</div>
</div>
<div class="w-full">
<label class="block text-xs font-semibold text-gray-900 uppercase">End Date</label>
<div>
<input
v-model="endDateTime"
type="datetime-local"
@input="clearDateTimeInterval"
required
class="block w-full rounded-md border-0 bg-white px-3 py-3 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm/6"
/>
</div>
</div>
</div>
<AppButton type="submit" colour="primary" class="mt-6">Finish Workout</AppButton>
<AppButton type="submit" colour="primary" class="mt-6">Save Workout</AppButton>
<AppButton type="button" colour="gray" class="mt-6" @click="cancelWorkout">
Cancel Workout
</AppButton>
Expand Down

0 comments on commit 70f89ce

Please sign in to comment.