StreakFit — API Laravel 13 OSDD + Octane/FrankenPHP
Build & Deploy / build (push) Successful in 18s

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-19 03:00:17 +02:00
co-authored by Claude Opus 4.8
commit af32669931
300 changed files with 21235 additions and 0 deletions
@@ -0,0 +1,72 @@
<?php
namespace Functional\Tracking\Services;
use Carbon\Carbon;
use Functional\Tracking\Enums\WorkoutSessionStatus;
use Functional\Tracking\Exceptions\WorkoutSessionAlreadyClosedException;
use Functional\Tracking\Models\SetLog;
use Functional\Tracking\Models\WorkoutSession;
use Functional\Users\Models\User;
/**
* Cycle de vie d'une séance d'entraînement : démarrage, log de sets, complétion.
* La complétion sauvegarde le statut "completed", ce qui déclenche WorkoutCompleted
* (écouté par la couche streaks pour recalculer le streak de l'utilisateur).
*/
class WorkoutSessionService
{
public function start(User $user, array $data): WorkoutSession
{
return WorkoutSession::create([
'user_id' => $user->id,
'routine_id' => $data['routine_id'] ?? null,
'title' => $data['title'] ?? null,
'started_at' => now(),
'status' => WorkoutSessionStatus::Active,
]);
}
public function logSet(WorkoutSession $session, array $data): SetLog
{
if ($session->status->isTerminal()) {
throw new WorkoutSessionAlreadyClosedException($session->id);
}
return $session->setLogs()->create([
'exercise_id' => $data['exercise_id'],
'set_number' => $data['set_number'],
'reps' => $data['reps'] ?? null,
'weight' => $data['weight'] ?? null,
'rpe' => $data['rpe'] ?? null,
'duration_seconds' => $data['duration_seconds'] ?? null,
'distance_meters' => $data['distance_meters'] ?? null,
'is_warmup' => $data['is_warmup'] ?? false,
'is_completed' => true,
'logged_at' => now(),
]);
}
public function complete(WorkoutSession $session, array $data): WorkoutSession
{
if ($session->status->isTerminal()) {
throw new WorkoutSessionAlreadyClosedException($session->id);
}
$session->ended_at = isset($data['ended_at']) ? Carbon::parse($data['ended_at']) : now();
$session->total_volume = $this->calculateTotalVolume($session);
$session->status = WorkoutSessionStatus::Completed;
$session->save(); // déclenche WorkoutCompleted (recalcul du streak, couche streaks)
return $session->fresh('setLogs');
}
/** Volume total = somme(reps * poids) des séries effectives (hors échauffement). */
private function calculateTotalVolume(WorkoutSession $session): float
{
return (float) $session->setLogs()
->where('is_warmup', false)
->get()
->sum(fn (SetLog $setLog) => (float) ($setLog->reps ?? 0) * (float) ($setLog->weight ?? 0));
}
}