src/Controller/API/WorkoutTemplateAPIController.php line 501

Open in your IDE?
  1. <?php
  2. namespace App\Controller\API;
  3. use App\Controller\API\AbstractAPIController;
  4. use App\Entity\Device;
  5. use App\Entity\Exercise;
  6. use App\Entity\DataVersion;
  7. use App\Entity\WorkoutTemplate;
  8. use App\Entity\FormCorrection;
  9. use App\Entity\WorkoutData;
  10. use App\Repository\WorkoutDataRepository;
  11. use App\Repository\ExerciseRepository;
  12. use App\ErrorLogRepository;
  13. use App\Entity\ErrorLog;
  14. use App\Repository\WorkoutTemplateRepository;
  15. use App\Repository\FormCorrectionRepository;
  16. use App\Repository\ClassifierParamsRepository;
  17. use Symfony\Component\HttpFoundation\JsonResponse;
  18. use Symfony\Component\HttpFoundation\Request;
  19. use Doctrine\ORM\EntityManagerInterface;
  20. use Symfony\Component\Routing\Annotation\Route;
  21. class WorkoutTemplateAPIController extends AbstractAPIController
  22. {
  23.     #[Route("/api/error-log",methods: ["POST"])]
  24.         public function postErrorLog(Request $requestEntityManagerInterface $em): JsonResponse {
  25.         $data json_decode($request->getContent(), true);
  26.         for ($i 0$i count($data); $i++) {
  27.             $entry $data[$i];
  28.             $errorLog = new ErrorLog();
  29.                     $errorLog->setTimestamp($entry["timestamp"]);
  30.                     $errorLog->setGymName($entry["gymName"]);
  31.                     $errorLog->setLog(strval($entry["log"]));
  32.                     $em->persist($errorLog);
  33.                     $em->flush();
  34.         }
  35.                 return $this->json(true);
  36.         }
  37.     #[Route("/api/leaderboard-list/{id}",methods: ["GET"])]
  38.         public function getWorkoutLeaderboardList(Request $requestWorkoutTemplate $workoutWorkoutDataRepository $repo): JsonResponse {
  39.                 $sum 0;
  40.                 $max 0;
  41.                 $data $repo->findAll();
  42.                 $count 0;
  43.         $entries = [];
  44.                 foreach ($data as $entry) {
  45.                         if ($entry == null || $entry->getJsonData() == null) continue;
  46.                         $jsonData json_decode($entry->getJsonData(), true);
  47.                         // todo: only count entries that were not exited for the leaderboard
  48.                         if ($jsonData["workoutCloudId"] != $workout->getId()) continue;
  49.                         $count++;
  50.             $entries[] = intval($jsonData["btScore"]);
  51.                         $sum $sum $jsonData["btScore"];
  52.                         if ($jsonData["btScore"] > $max$max $jsonData["btScore"];
  53.                 }
  54.                 if ($sum == 0) return $this->json(["mean" => 0"max" => 0"count" => 0"entries" => []]);
  55.                 return $this->json([
  56.                                         "mean" => $sum $count,
  57.                                         "max" => $max,
  58.                                         "count" => $count,
  59.                     "entries" => $entries
  60.                                 ]);
  61.         } 
  62.     #[Route("/api/leaderboard/{id}",methods: ["GET"])]
  63.     public function getWorkoutLeaderboard(Request $requestWorkoutTemplate $workoutWorkoutDataRepository $repo): JsonResponse {
  64.         $sum 0;
  65.         $max 0;
  66.         $data $repo->findAll();
  67.         $count 0;
  68.         foreach ($data as $entry) {
  69.             if ($entry == null || $entry->getJsonData() == null) continue;
  70.             $jsonData json_decode($entry->getJsonData(), true);
  71.             // todo: only count entries that were not exited for the leaderboard
  72.             if ($jsonData["workoutCloudId"] != $workout->getId()) continue;
  73.             $count++;
  74.             $sum $sum $jsonData["btScore"];
  75.             if ($jsonData["btScore"] > $max$max $jsonData["btScore"];
  76.         }
  77.         if ($sum == 0) return $this->json(["mean" => 0"max" => 0"count" => 0]);
  78.         return $this->json([
  79.                     "mean" => $sum $count,
  80.                     "max" => $max,
  81.                     "count" => $count
  82.                 ]);
  83.     } 
  84.     #[Route("/api/workout-data"methods: ["POST"])]
  85.     public function postWorkoutData(
  86.         Request $request,
  87.         EntityManagerInterface $em,
  88.     WorkoutDataRepository $repo
  89.     ): JsonResponse
  90.     {
  91.          // check jwt-authorization
  92.          $token $this->authenticationService->verifyToken($request);
  93.          if (!$token) {
  94.              return $this->json([
  95.                  'error' => 'Unauthorized access.'
  96.              ], 401);
  97.          }
  98.          ini_set('memory_limit''-1');
  99.          try {
  100.             $jsonData json_decode($request->getContent(), true);
  101.         
  102.         $duplicate $repo->findBy(['startTimestamp' => $jsonData['startTimestamp'], 'endTimestamp' => $jsonData['endTimestamp']]);
  103.         if (count($duplicate) > 0) {
  104.             return $this->json(['success' => true]);
  105.         }
  106.         $workoutData = new WorkoutData();    
  107.         $workoutData->setWorkoutName($jsonData['workoutName']);
  108.         $workoutData->setModuleType($jsonData['moduleType']);
  109.         $workoutData->setExited($jsonData['exited']);
  110.         $workoutData->setWouldReuse($jsonData['would_reuse']);
  111.         $workoutData->setGymName($jsonData['gymName']);
  112.         $workoutData->setBtScore($jsonData['btScore']);
  113.         $workoutData->setOverallFeedback($jsonData['feedback_overall_score']);
  114.         if (array_key_exists('actualTrail'$jsonData)) {
  115.             $workoutData->setActualTrail($jsonData['actualTrail']);
  116.         } else {
  117.             $workoutData->setActualTrail([]);
  118.         }
  119.         if (array_key_exists('targetTrail'$jsonData)) {
  120.             $workoutData->setTargetTrail($jsonData['targetTrail']);
  121.         } else {
  122.             $workoutData->setTargetTrail([]);
  123. }
  124.         $workoutData->setStartTimestamp($jsonData['startTimestamp']);
  125.         $workoutData->setEndTimestamp($jsonData['endTimestamp']);
  126.            
  127.             $workoutData->setJsonData($jsonData);
  128.             $em->persist($workoutData);
  129.             $em->flush();
  130.             return $this->json([
  131.                 'success' => true,
  132.             ]);
  133.          } catch (Exception $e) {
  134.             if (!$token) {
  135.                 return $this->json([
  136.                     'error' => $e->getMessage()
  137.                 ], 422);
  138.             }
  139.          }
  140.          
  141.     }
  142.     #[Route('/api/workout-templates/{language}'defaults: ['language' => 'en'], name'app_workout_get_templates'methods: ["GET"])]
  143.     public function index(
  144.         Request $request,
  145.         WorkoutTemplateRepository $workoutTemplateRepository,
  146.         ExerciseRepository $exerciseRepository,
  147.         ClassifierParamsRepository $paramsRepo,
  148.         EntityManagerInterface $em
  149.     ): JsonResponse
  150.     {
  151.         // check jwt-authorization
  152.         $token $this->authenticationService->verifyToken($request);
  153.         if (!$token) {
  154.             return $this->json([
  155.                 'error' => 'Unauthorized access.'
  156.             ], 401);
  157.         }
  158.         ini_set('memory_limit''-1');
  159.         $language $request->attributes->get("language");
  160.         // WORKOUT SECTION
  161.         $workouts $workoutTemplateRepository->findAll();
  162.         $workoutObjects = [];
  163.         foreach ($workouts as $workout) {
  164.             $workoutObjects[] = [
  165.                 'id' => $workout->getId(),
  166.                 'deleted' => false,
  167.                 'updated' => false,
  168.                 'added' => true,
  169.                 'version' => 0,
  170.                 'data' => [
  171.             'favorite' => $workout->isFavorite(),
  172.                     'name' => $workout->getName(),
  173.             'moduleType' => $workout->getModuleType(),
  174.                     'description' => $workout->getDescription(),
  175.             'equipment' => array_map('strval'array_values((array) $workout->getEquipment())),
  176.                     'targetArea' => $workout->getTargetArea(),
  177.                     'level' => $workout->getLevel(),
  178.                     'calories' => 0,
  179.                     'image' => $this->getWorkoutImage($workout),
  180.                     'exercises' => json_decode($workout->getExercises()),
  181.                 ],
  182.             ];
  183.         }
  184.         // EXERCISE SECTION
  185.         $exercises $exerciseRepository->findAll();
  186.         $exerciseObjects = [];
  187.         foreach ($exercises as $exercise) {
  188.                 // was added
  189.                 $exerciseObjects[] = [
  190.                     'id' => $exercise->getId(),
  191.                     'deleted' => false,
  192.                     'updated' => false,
  193.                     'added' => true,
  194.                     'version' => 0,
  195.                     'data' => $this->aggregateExerciseData($exercise$language)
  196.                 ];
  197.             }
  198.         // PARAMETERS SECTION
  199.         $paramsList = [];
  200.         $params $paramsRepo->findAll();
  201.         foreach ($params as $param) {
  202.             $paramsList[] = [
  203.                 'romSmootherBufferSize' => $param->getRomSmootherBufferSize(),
  204.                 'repetitionCountRange' => $param->getRepetitionCountRange(),
  205.                 'versionHash' => $param->getVersionHash(),
  206.             ];
  207.         }
  208.         
  209.         return $this->json([
  210.             'workoutDeltas' => $workoutObjects,
  211.             'exerciseDeltas' => $exerciseObjects,
  212.             'parameters' => $paramsList,
  213.             'audioBaseFiles' => $this->getLocalizedBaseAudioFiles($language),
  214.             'dataVersion' => $em->getRepository(DataVersion::class)->findOneBy([], ['id' => 'ASC'])->getVersionCounter()
  215.         ]);
  216.     }
  217.     #[Route('/api/workout-assets/{language}'defaults: ["language" => "en"], name'app_workout_assets'methods: ["POST"])]
  218. public function exerciseAssets(
  219.     Request $request,
  220.     ExerciseRepository $exerciseRepository,
  221.     FormCorrectionRepository $formCorrectionRepository
  222. ): JsonResponse {
  223.     // Check JWT authorization
  224.     $token $this->authenticationService->verifyToken($request);
  225.     if (!$token) {
  226.         return $this->json([
  227.             'error' => 'Unauthorized access.'
  228.         ], 401);
  229.     }
  230.     ini_set('memory_limit''-1');
  231.     $language $request->attributes->get("language");
  232.     $data json_decode($request->getContent(), true);
  233.     $idList = [];
  234.     if (is_array($data)) {
  235.         $idList $data;
  236.     }
  237.     // EXERCISE SECTION
  238.     $exercises $exerciseRepository->findBy(array('id' => $idList));
  239.     $exerciseObjects = [];
  240.     foreach ($exercises as $exercise) {
  241.     // form correction objects
  242.     $formCorrections $formCorrectionRepository->findBy(["exerciseId" => $exercise->getId()]);
  243.         // Was added
  244.         $exerciseObjects[] = [
  245.             'id' => $exercise->getId(),
  246.             'deleted' => false,
  247.             'updated' => false,
  248.             'added' => true,
  249.             'version' => 0,
  250.             'data' => $this->aggregateExerciseAssets($exercise$formCorrections$language)
  251.         ];
  252.     }
  253.     return $this->json([
  254.         'exerciseAssets' => $exerciseObjects,
  255.     ]);
  256. }
  257.     private function getWorkoutImage(WorkoutTemplate $workout)
  258.     {
  259.         $image $workout->getImage();
  260.         if ($image != null$image file_get_contents($this->getParameter('target_directory') . '/' $workout->getImage());
  261.         $image base64_encode($image);
  262.         return $image;
  263.     }
  264.     private function aggregateExerciseData(Exercise $exercise$language)
  265.     {
  266.         $gifFile $exercise->getGif();
  267.         if ($gifFile != null) {
  268.             $gifFile file_get_contents($this->getParameter('target_directory') . '/' $exercise->getGif());
  269.         }
  270.  
  271.         $base64GifFile base64_encode($gifFile);
  272.   
  273.         return [
  274.             'id' => $exercise->getId(),
  275.         'rotationRange' => $exercise->getRotationRange(),
  276.             'name' => $exercise->getLocalizedName('en'), // will always return the name of the exercise in english
  277.         "howTo" => $exercise->getLocalizedDescription($language),
  278.             'landmarkInputs' => $exercise->getLandmarkInputs(),
  279.         'formCorrection' => $exercise->isFormCorrection(),
  280.             'type' => $exercise->getType(),
  281.             'gif' => $base64GifFile,
  282.         ];
  283.     }
  284.     private function getLocalizedBaseAudioFiles($language) {
  285.         $get_into_starting_position base64_encode(file_get_contents($this->getParameter("target_directory") . "/" "get_into_starting_position_" $language ".mp3"));
  286.         $great_follow_the_curve base64_encode(file_get_contents($this->getParameter("target_directory") . "/" "great_follow_the_curve_" $language ".mp3"));
  287.         $follow_the_curve base64_encode(file_get_contents($this->getParameter("target_directory") . "/" "follow_the_curve_" $language ".mp3"));
  288.         return [
  289.             "get_into_starting_position" => $get_into_starting_position,
  290.             "great_follow_the_curve" => $great_follow_the_curve,
  291.             "follow_the_curve" => $follow_the_curve,
  292.         ];
  293.     }
  294.     private function aggregateExerciseAssets(Exercise $exercise$formCorrections$language)
  295.     {
  296.         $regModel $exercise->getRegressorModelFileName();
  297.         $binModel $exercise->getClassifierModelFileName();
  298.        // $errorModel = $exercise->getErrorModelFileName();
  299.     $exerciseDetector $exercise->getExerciseDetector();    
  300.         $audio0 null;
  301.         $audio1 null;
  302.         $audio2 null;
  303.         $audio3 null;
  304.         $audio4 null;
  305.         $outlineFile $exercise->getStartPoseOutlineImage();
  306.         if ($regModel != null) {
  307.             $regModel file_get_contents($this->getParameter('target_directory') . '/' $exercise->getRegressorModelFileName());
  308.         }
  309.         if ($binModel != null) {
  310.             $binModel file_get_contents($this->getParameter('target_directory') . '/' $exercise->getClassifierModelFileName());
  311.         }
  312.        // if ($errorModel != null) {
  313.          //   $errorModel = file_get_contents($this->getParameter('target_directory') . '/' . $exercise->getErrorModelFileName());
  314.         //}
  315.     if ($exerciseDetector != null) {
  316.         $exerciseDetector file_get_contents($this->getParameter('target_directory') . '/' $exercise->getExerciseDetector());
  317.     }
  318.         $nextExerciseAudio null;
  319.         if (file_exists($this->getParameter("target_directory") . "/" $exercise->getNameEn() . "_" $language ".mp3")) {
  320.             $nextExerciseAudio file_get_contents($this->getParameter("target_directory") . "/" $exercise->getNameEn() . "_" $language ".mp3");
  321.         }
  322.         $audio0 null;
  323.     try {
  324.         $audio0Path $this->getParameter('target_directory') . '/' $exercise->getNameEn() . "_form0_" $language ".mp3";
  325.         
  326.             if (file_exists($audio0Path)) {
  327.                 $audio0 file_get_contents($audio0Path);
  328.             }
  329.         } catch (Exception $e) {
  330.             // no op
  331.         }
  332.         $audio1 null;
  333.     try {
  334.         $audio1Path $this->getParameter('target_directory') . '/' $exercise->getNameEn() . "_form1_" $language ".mp3";
  335.        
  336.             if (file_exists($audio1Path)) {
  337.                 $audio1 file_get_contents($audio1Path);
  338.             }
  339.         } catch (Exception $e) {
  340.             // no op
  341.         }
  342.         $audio2 null;
  343.     try {
  344.         $audio2Path $this->getParameter('target_directory') . '/' $exercise->getNameEn() . "_form2_" $language ".mp3";
  345.         
  346.             if (file_exists($audio2Path)) {
  347.                 $audio2 file_get_contents($audio2Path);
  348.             }
  349.         } catch (Exception $e) {
  350.             // no op
  351.         }
  352.         $audio3 null;
  353.     try {
  354.         $audio3Path $this->getParameter('target_directory') . '/' $exercise->getNameEn() . "_form3_" $language ".mp3";
  355.         
  356.             if (file_exists($audio3Path)) {
  357.                 $audio3 file_get_contents($audio3Path);
  358.             }
  359.         } catch (Exception $e) {
  360.             // no op
  361.         }
  362.         $audio4 null;
  363.     try {
  364.         $audio4Path $this->getParameter('target_directory') . '/' $exercise->getNameEn() . "_form4_" $language ".mp3";
  365.        
  366.             if (file_exists($audio4Path)) {
  367.                 $audio4 file_get_contents($audio4Path);
  368.             }
  369.         } catch (Exception $e) {
  370.             // no op
  371.         }
  372.         if ($outlineFile != null) {
  373.             $outlineFile file_get_contents($this->getParameter('target_directory') . '/' $exercise->getStartPoseOutlineImage());
  374.         }
  375.         $base64RegModel base64_encode($regModel);
  376.         $base64BinModel base64_encode($binModel);
  377.         //$base64ErrorModel = base64_encode($errorModel);
  378.     $base64ExerciseDetectorModel base64_encode($exerciseDetector);
  379.         $baseAudio0 base64_encode($audio0);
  380.         $baseAudio1 base64_encode($audio1);
  381.         $baseAudio2 base64_encode($audio2);
  382.         $baseAudio3 base64_encode($audio3);
  383.         $baseAudio4 base64_encode($audio4);
  384.         $base64OutlineImageFile base64_encode($outlineFile);
  385.         $nextExerciseAudio base64_encode($nextExerciseAudio);
  386.     // aggregate error correction objects
  387.     $formCorrectionObjects = [];
  388.     foreach ($formCorrections as $formCorrection) {
  389.         $image $formCorrection->getImage();
  390.         $imageFile null;
  391.                 if ($image != null) {
  392.                 $imageFile file_get_contents($this->getParameter('target_directory') . '/' $image);
  393.             }
  394.  
  395.             $base64GifFile base64_encode($imageFile);
  396.         
  397.         $errorModelFilePath $formCorrection->getErrorModelFilePath();
  398.         $errorModel null;
  399.         if ($errorModelFilePath != null) {
  400.             $errorModel file_get_contents($this->getParameter('target_directory') . '/' $errorModelFilePath);
  401.         }
  402.         $base64ErrorModel base64_encode($errorModel);
  403.         $formCorrectionObjects[] = [
  404.             "exerciseId" => $formCorrection->getExerciseId(),
  405.             "exerciseName" => $formCorrection->getExerciseName(),
  406.             "errorCode" => $formCorrection->getErrorCode(),
  407.             "description" => $formCorrection->getDescription(),
  408.             "image" => $base64GifFile,
  409.             "title" => $formCorrection->getTitle(),
  410.             "minRom" => $formCorrection->getMinRom(),
  411.             "maxRom" => $formCorrection->getMaxRom(),
  412.             "lms" => $formCorrection->getLandmarks(),
  413.             "errorModel" => $base64ErrorModel
  414.         ];
  415.     }
  416.     
  417.         return [
  418.         "formCorrections" => $formCorrectionObjects,
  419.             'models' => [
  420.                 'regressor' => $base64RegModel,
  421.                 'classifier' => $base64BinModel,
  422.                 //'errorCoder' => $base64ErrorModel,
  423.         'exerciseDetector' => $base64ExerciseDetectorModel,
  424.             ],
  425.         'formCorrectionText1' => $exercise->getFormCorrectionText1(),
  426.         'formCorrectionText2' => $exercise->getFormCorrectionText2(),
  427.         'formCorrectionText3' => $exercise->getFormCorrectionText3(),
  428.         'formCorrectionText4' => $exercise->getFormCorrectionText4(),
  429.         'audio' => [
  430.             'a0' => $baseAudio0,
  431.             'a1' => $baseAudio1,
  432.             'a2' => $baseAudio2,
  433.             'a3' => $baseAudio3,
  434.             'a4' => $baseAudio4
  435.         ],
  436.         'startPoseOutlineImage' => $base64OutlineImageFile,
  437.         "next_exercise_audio" => $nextExerciseAudio,
  438.         ];
  439.     }
  440.     
  441. #[Route('/api/heartbeat'name'app_heartbeat'methods: ["POST"])]    
  442.     public function updateHeartbeat(Request $requestEntityManagerInterface $entityManager): JsonResponse
  443.     {
  444.         // Decode JSON payload
  445.         $data json_decode($request->getContent(), true);
  446.         // 1. Ensure "gym" key exists
  447.         if (!isset($data['gym'])) {
  448.             return new JsonResponse(['error' => 'Missing "gym" in payload'], 400);
  449.         }
  450.         // 2. Look up the Device by "gym"
  451.         $gymValue $data['gym'];
  452.         $device $entityManager->getRepository(Device::class)->findOneBy(['gym' => $gymValue]);
  453.         // 3. If no Device found, return an error
  454.         if (!$device) {
  455.             return new JsonResponse(['error' => 'No device found for gym: ' $gymValue], 404);
  456.         }
  457.         // 4. Update lastHeartBeat to "now"
  458.         $device->setLastHeartBeat(
  459.     new \DateTime('now', new \DateTimeZone('Europe/Zurich'))
  460. );
  461.         // 5. Persist changes
  462.         $entityManager->flush();
  463.         // Return success
  464.         return new JsonResponse(['status' => 'ok'], 200);
  465.     }
  466. }