src/Controller/API/WorkoutTemplateAPIController.php line 470

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.     ): JsonResponse
  89.     {
  90.          // check jwt-authorization
  91.          $token $this->authenticationService->verifyToken($request);
  92.          if (!$token) {
  93.              return $this->json([
  94.                  'error' => 'Unauthorized access.'
  95.              ], 401);
  96.          }
  97.  
  98.          ini_set('memory_limit''-1');
  99.          try {
  100.             $jsonData json_decode($request->getContent(), true);
  101.             $workoutData = new WorkoutData();
  102.             $workoutData->setJsonData($jsonData);
  103.             $em->persist($workoutData);
  104.             $em->flush();
  105.             return $this->json([
  106.                 'success' => true,
  107.             ]);
  108.          } catch (Exception $e) {
  109.             if (!$token) {
  110.                 return $this->json([
  111.                     'error' => $e->getMessage()
  112.                 ], 422);
  113.             }
  114.          }
  115.          
  116.     }
  117.     #[Route('/api/workout-templates/{language}'defaults: ['language' => 'en'], name'app_workout_get_templates'methods: ["GET"])]
  118.     public function index(
  119.         Request $request,
  120.         WorkoutTemplateRepository $workoutTemplateRepository,
  121.         ExerciseRepository $exerciseRepository,
  122.         ClassifierParamsRepository $paramsRepo,
  123.         EntityManagerInterface $em
  124.     ): JsonResponse
  125.     {
  126.         // check jwt-authorization
  127.         $token $this->authenticationService->verifyToken($request);
  128.         if (!$token) {
  129.             return $this->json([
  130.                 'error' => 'Unauthorized access.'
  131.             ], 401);
  132.         }
  133.         ini_set('memory_limit''-1');
  134.         $language $request->attributes->get("language");
  135.         // WORKOUT SECTION
  136.         $workouts $workoutTemplateRepository->findAll();
  137.         $workoutObjects = [];
  138.         foreach ($workouts as $workout) {
  139.             $workoutObjects[] = [
  140.                 'id' => $workout->getId(),
  141.                 'deleted' => false,
  142.                 'updated' => false,
  143.                 'added' => true,
  144.                 'version' => 0,
  145.                 'data' => [
  146.                     'name' => $workout->getName(),
  147.             'moduleType' => $workout->getModuleType(),
  148.                     'description' => $workout->getDescription(),
  149.             'equipment' => array_map('strval'array_values((array) $workout->getEquipment())),
  150.                     'targetArea' => $workout->getTargetArea(),
  151.                     'level' => $workout->getLevel(),
  152.                     'calories' => 0,
  153.                     'image' => $this->getWorkoutImage($workout),
  154.                     'exercises' => json_decode($workout->getExercises()),
  155.                 ],
  156.             ];
  157.         }
  158.         // EXERCISE SECTION
  159.         $exercises $exerciseRepository->findAll();
  160.         $exerciseObjects = [];
  161.         foreach ($exercises as $exercise) {
  162.                 // was added
  163.                 $exerciseObjects[] = [
  164.                     'id' => $exercise->getId(),
  165.                     'deleted' => false,
  166.                     'updated' => false,
  167.                     'added' => true,
  168.                     'version' => 0,
  169.                     'data' => $this->aggregateExerciseData($exercise$language)
  170.                 ];
  171.             }
  172.         // PARAMETERS SECTION
  173.         $paramsList = [];
  174.         $params $paramsRepo->findAll();
  175.         foreach ($params as $param) {
  176.             $paramsList[] = [
  177.                 'romSmootherBufferSize' => $param->getRomSmootherBufferSize(),
  178.                 'repetitionCountRange' => $param->getRepetitionCountRange(),
  179.                 'versionHash' => $param->getVersionHash(),
  180.             ];
  181.         }
  182.         
  183.         return $this->json([
  184.             'workoutDeltas' => $workoutObjects,
  185.             'exerciseDeltas' => $exerciseObjects,
  186.             'parameters' => $paramsList,
  187.             'audioBaseFiles' => $this->getLocalizedBaseAudioFiles($language),
  188.             'dataVersion' => $em->getRepository(DataVersion::class)->findOneBy([], ['id' => 'ASC'])->getVersionCounter()
  189.         ]);
  190.     }
  191.     #[Route('/api/workout-assets/{language}'defaults: ["language" => "en"], name'app_workout_assets'methods: ["POST"])]
  192. public function exerciseAssets(
  193.     Request $request,
  194.     ExerciseRepository $exerciseRepository,
  195.     FormCorrectionRepository $formCorrectionRepository
  196. ): JsonResponse {
  197.     // Check JWT authorization
  198.     $token $this->authenticationService->verifyToken($request);
  199.     if (!$token) {
  200.         return $this->json([
  201.             'error' => 'Unauthorized access.'
  202.         ], 401);
  203.     }
  204.     ini_set('memory_limit''-1');
  205.     $language $request->attributes->get("language");
  206.     $data json_decode($request->getContent(), true);
  207.     $idList = [];
  208.     if (is_array($data)) {
  209.         $idList $data;
  210.     }
  211.     // EXERCISE SECTION
  212.     $exercises $exerciseRepository->findBy(array('id' => $idList));
  213.     $exerciseObjects = [];
  214.     foreach ($exercises as $exercise) {
  215.     // form correction objects
  216.     $formCorrections $formCorrectionRepository->findBy(["exerciseId" => $exercise->getId()]);
  217.         // Was added
  218.         $exerciseObjects[] = [
  219.             'id' => $exercise->getId(),
  220.             'deleted' => false,
  221.             'updated' => false,
  222.             'added' => true,
  223.             'version' => 0,
  224.             'data' => $this->aggregateExerciseAssets($exercise$formCorrections$language)
  225.         ];
  226.     }
  227.     return $this->json([
  228.         'exerciseAssets' => $exerciseObjects,
  229.     ]);
  230. }
  231.     private function getWorkoutImage(WorkoutTemplate $workout)
  232.     {
  233.         $image $workout->getImage();
  234.         if ($image != null$image file_get_contents($this->getParameter('target_directory') . '/' $workout->getImage());
  235.         $image base64_encode($image);
  236.         return $image;
  237.     }
  238.     private function aggregateExerciseData(Exercise $exercise$language)
  239.     {
  240.         $gifFile $exercise->getGif();
  241.         if ($gifFile != null) {
  242.             $gifFile file_get_contents($this->getParameter('target_directory') . '/' $exercise->getGif());
  243.         }
  244.  
  245.         $base64GifFile base64_encode($gifFile);
  246.   
  247.         return [
  248.             'id' => $exercise->getId(),
  249.         'rotationRange' => $exercise->getRotationRange(),
  250.             'name' => $exercise->getLocalizedName('en'), // will always return the name of the exercise in english
  251.         "howTo" => $exercise->getLocalizedDescription($language),
  252.             'landmarkInputs' => $exercise->getLandmarkInputs(),
  253.         'formCorrection' => $exercise->isFormCorrection(),
  254.             'type' => $exercise->getType(),
  255.             'gif' => $base64GifFile,
  256.         ];
  257.     }
  258.     private function getLocalizedBaseAudioFiles($language) {
  259.         $get_into_starting_position base64_encode(file_get_contents($this->getParameter("target_directory") . "/" "get_into_starting_position_" $language ".mp3"));
  260.         $great_follow_the_curve base64_encode(file_get_contents($this->getParameter("target_directory") . "/" "great_follow_the_curve_" $language ".mp3"));
  261.         $follow_the_curve base64_encode(file_get_contents($this->getParameter("target_directory") . "/" "follow_the_curve_" $language ".mp3"));
  262.         return [
  263.             "get_into_starting_position" => $get_into_starting_position,
  264.             "great_follow_the_curve" => $great_follow_the_curve,
  265.             "follow_the_curve" => $follow_the_curve,
  266.         ];
  267.     }
  268.     private function aggregateExerciseAssets(Exercise $exercise$formCorrections$language)
  269.     {
  270.         $regModel $exercise->getRegressorModelFileName();
  271.         $binModel $exercise->getClassifierModelFileName();
  272.        // $errorModel = $exercise->getErrorModelFileName();
  273.     $exerciseDetector $exercise->getExerciseDetector();    
  274.         $audio0 null;
  275.         $audio1 null;
  276.         $audio2 null;
  277.         $audio3 null;
  278.         $audio4 null;
  279.         $outlineFile $exercise->getStartPoseOutlineImage();
  280.         if ($regModel != null) {
  281.             $regModel file_get_contents($this->getParameter('target_directory') . '/' $exercise->getRegressorModelFileName());
  282.         }
  283.         if ($binModel != null) {
  284.             $binModel file_get_contents($this->getParameter('target_directory') . '/' $exercise->getClassifierModelFileName());
  285.         }
  286.        // if ($errorModel != null) {
  287.          //   $errorModel = file_get_contents($this->getParameter('target_directory') . '/' . $exercise->getErrorModelFileName());
  288.         //}
  289.     if ($exerciseDetector != null) {
  290.         $exerciseDetector file_get_contents($this->getParameter('target_directory') . '/' $exercise->getExerciseDetector());
  291.     }
  292.         $nextExerciseAudio null;
  293.         if (file_exists($this->getParameter("target_directory") . "/" $exercise->getNameEn() . "_" $language ".mp3")) {
  294.             $nextExerciseAudio file_get_contents($this->getParameter("target_directory") . "/" $exercise->getNameEn() . "_" $language ".mp3");
  295.         }
  296.         $audio0 null;
  297.     try {
  298.         $audio0Path $this->getParameter('target_directory') . '/' $exercise->getNameEn() . "_form0_" $language ".mp3";
  299.         
  300.             if (file_exists($audio0Path)) {
  301.                 $audio0 file_get_contents($audio0Path);
  302.             }
  303.         } catch (Exception $e) {
  304.             // no op
  305.         }
  306.         $audio1 null;
  307.     try {
  308.         $audio1Path $this->getParameter('target_directory') . '/' $exercise->getNameEn() . "_form1_" $language ".mp3";
  309.        
  310.             if (file_exists($audio1Path)) {
  311.                 $audio1 file_get_contents($audio1Path);
  312.             }
  313.         } catch (Exception $e) {
  314.             // no op
  315.         }
  316.         $audio2 null;
  317.     try {
  318.         $audio2Path $this->getParameter('target_directory') . '/' $exercise->getNameEn() . "_form2_" $language ".mp3";
  319.         
  320.             if (file_exists($audio2Path)) {
  321.                 $audio2 file_get_contents($audio2Path);
  322.             }
  323.         } catch (Exception $e) {
  324.             // no op
  325.         }
  326.         $audio3 null;
  327.     try {
  328.         $audio3Path $this->getParameter('target_directory') . '/' $exercise->getNameEn() . "_form3_" $language ".mp3";
  329.         
  330.             if (file_exists($audio3Path)) {
  331.                 $audio3 file_get_contents($audio3Path);
  332.             }
  333.         } catch (Exception $e) {
  334.             // no op
  335.         }
  336.         $audio4 null;
  337.     try {
  338.         $audio4Path $this->getParameter('target_directory') . '/' $exercise->getNameEn() . "_form4_" $language ".mp3";
  339.        
  340.             if (file_exists($audio4Path)) {
  341.                 $audio4 file_get_contents($audio4Path);
  342.             }
  343.         } catch (Exception $e) {
  344.             // no op
  345.         }
  346.         if ($outlineFile != null) {
  347.             $outlineFile file_get_contents($this->getParameter('target_directory') . '/' $exercise->getStartPoseOutlineImage());
  348.         }
  349.         $base64RegModel base64_encode($regModel);
  350.         $base64BinModel base64_encode($binModel);
  351.         //$base64ErrorModel = base64_encode($errorModel);
  352.     $base64ExerciseDetectorModel base64_encode($exerciseDetector);
  353.         $baseAudio0 base64_encode($audio0);
  354.         $baseAudio1 base64_encode($audio1);
  355.         $baseAudio2 base64_encode($audio2);
  356.         $baseAudio3 base64_encode($audio3);
  357.         $baseAudio4 base64_encode($audio4);
  358.         $base64OutlineImageFile base64_encode($outlineFile);
  359.         $nextExerciseAudio base64_encode($nextExerciseAudio);
  360.     // aggregate error correction objects
  361.     $formCorrectionObjects = [];
  362.     foreach ($formCorrections as $formCorrection) {
  363.         $image $formCorrection->getImage();
  364.         $imageFile null;
  365.                 if ($image != null) {
  366.                 $imageFile file_get_contents($this->getParameter('target_directory') . '/' $image);
  367.             }
  368.  
  369.             $base64GifFile base64_encode($imageFile);
  370.         
  371.         $errorModelFilePath $formCorrection->getErrorModelFilePath();
  372.         $errorModel null;
  373.         if ($errorModelFilePath != null) {
  374.             $errorModel file_get_contents($this->getParameter('target_directory') . '/' $errorModelFilePath);
  375.         }
  376.         $base64ErrorModel base64_encode($errorModel);
  377.         $formCorrectionObjects[] = [
  378.             "exerciseId" => $formCorrection->getExerciseId(),
  379.             "exerciseName" => $formCorrection->getExerciseName(),
  380.             "errorCode" => $formCorrection->getErrorCode(),
  381.             "description" => $formCorrection->getDescription(),
  382.             "image" => $base64GifFile,
  383.             "title" => $formCorrection->getTitle(),
  384.             "minRom" => $formCorrection->getMinRom(),
  385.             "maxRom" => $formCorrection->getMaxRom(),
  386.             "errorModel" => $base64ErrorModel
  387.         ];
  388.     }
  389.     
  390.         return [
  391.         "formCorrections" => $formCorrectionObjects,
  392.             'models' => [
  393.                 'regressor' => $base64RegModel,
  394.                 'classifier' => $base64BinModel,
  395.                 //'errorCoder' => $base64ErrorModel,
  396.         'exerciseDetector' => $base64ExerciseDetectorModel,
  397.             ],
  398.         'formCorrectionText1' => $exercise->getFormCorrectionText1(),
  399.         'formCorrectionText2' => $exercise->getFormCorrectionText2(),
  400.         'formCorrectionText3' => $exercise->getFormCorrectionText3(),
  401.         'formCorrectionText4' => $exercise->getFormCorrectionText4(),
  402.         'audio' => [
  403.             'a0' => $baseAudio0,
  404.             'a1' => $baseAudio1,
  405.             'a2' => $baseAudio2,
  406.             'a3' => $baseAudio3,
  407.             'a4' => $baseAudio4
  408.         ],
  409.         'startPoseOutlineImage' => $base64OutlineImageFile,
  410.         "next_exercise_audio" => $nextExerciseAudio,
  411.         ];
  412.     }
  413.     
  414. #[Route('/api/heartbeat'name'app_heartbeat'methods: ["POST"])]    
  415.     public function updateHeartbeat(Request $requestEntityManagerInterface $entityManager): JsonResponse
  416.     {
  417.         // Decode JSON payload
  418.         $data json_decode($request->getContent(), true);
  419.         // 1. Ensure "gym" key exists
  420.         if (!isset($data['gym'])) {
  421.             return new JsonResponse(['error' => 'Missing "gym" in payload'], 400);
  422.         }
  423.         // 2. Look up the Device by "gym"
  424.         $gymValue $data['gym'];
  425.         $device $entityManager->getRepository(Device::class)->findOneBy(['gym' => $gymValue]);
  426.         // 3. If no Device found, return an error
  427.         if (!$device) {
  428.             return new JsonResponse(['error' => 'No device found for gym: ' $gymValue], 404);
  429.         }
  430.         // 4. Update lastHeartBeat to "now"
  431.         $device->setLastHeartBeat(
  432.     new \DateTime('now', new \DateTimeZone('Europe/Zurich'))
  433. );
  434.         // 5. Persist changes
  435.         $entityManager->flush();
  436.         // Return success
  437.         return new JsonResponse(['status' => 'ok'], 200);
  438.     }
  439. }