src/Entity/StravaActivity.php line 39

Open in your IDE?
  1. <?php
  2. // src/Entity/StravaActivity.php
  3. namespace App\Entity;
  4. use ApiPlatform\Core\Annotation\ApiFilter;
  5. use ApiPlatform\Core\Annotation\ApiResource;
  6. use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\DateFilter;
  7. use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\OrderFilter;
  8. use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\SearchFilter;
  9. use App\Repository\StravaActivityRepository;
  10. use Doctrine\ORM\Mapping as ORM;
  11. use Gedmo\Mapping\Annotation as Gedmo;
  12. use Symfony\Component\Serializer\Annotation\Groups;
  13. /**
  14.  * @ORM\Entity(repositoryClass=StravaActivityRepository::class)
  15.  * @ORM\Table(name="strava_activity", uniqueConstraints={
  16.  *     @ORM\UniqueConstraint(name="unique_strava_activity", columns={"strava_activity_id"})
  17.  * })
  18.  * @ApiResource(
  19.  *     normalizationContext={"groups"={"strava_activity:read"}},
  20.  *     denormalizationContext={"groups"={"strava_activity:write"}},
  21.  *     itemOperations={
  22.  *         "get"={
  23.  *             "security"="is_granted('ROLE_ADMIN') or object.getStravaProfile().getUser() == user"
  24.  *         }
  25.  *     },
  26.  *     collectionOperations={
  27.  *         "get"={
  28.  *             "security"="is_granted('ROLE_USER')"
  29.  *         }
  30.  *     }
  31.  * )
  32.  * @ApiFilter(SearchFilter::class, properties={"category": "exact", "stravaProfile.user.id": "exact"})
  33.  * @ApiFilter(OrderFilter::class, properties={"startDateLocal"})
  34.  * @ApiFilter(DateFilter::class, properties={"startDateLocal"})
  35.  */
  36. class StravaActivity
  37. {
  38.     // Types Strava acceptés
  39.     const TYPE_RUN = 'Run';
  40.     const TYPE_RIDE = 'Ride';
  41.     const TYPE_VIRTUAL_RUN = 'VirtualRun';
  42.     const TYPE_VIRTUAL_RIDE = 'VirtualRide';
  43.     const TYPE_TRAIL_RUN = 'TrailRun';
  44.     const TYPE_GRAVEL_RIDE = 'GravelRide';
  45.     const TYPE_MOUNTAIN_BIKE_RIDE = 'MountainBikeRide';
  46.     const TYPE_E_BIKE_RIDE = 'EBikeRide';
  47.     const ALLOWED_TYPES = [
  48.         self::TYPE_RUN,
  49.         self::TYPE_RIDE,
  50.         self::TYPE_VIRTUAL_RUN,
  51.         self::TYPE_VIRTUAL_RIDE,
  52.         self::TYPE_TRAIL_RUN,
  53.         self::TYPE_GRAVEL_RIDE,
  54.         self::TYPE_MOUNTAIN_BIKE_RIDE,
  55.         self::TYPE_E_BIKE_RIDE,
  56.     ];
  57.     // Catégories simplifiées
  58.     const CATEGORY_RUNNING = 'running';
  59.     const CATEGORY_CYCLING = 'cycling';
  60.     const TYPE_TO_CATEGORY = [
  61.         self::TYPE_RUN => self::CATEGORY_RUNNING,
  62.         self::TYPE_VIRTUAL_RUN => self::CATEGORY_RUNNING,
  63.         self::TYPE_TRAIL_RUN => self::CATEGORY_RUNNING,
  64.         self::TYPE_RIDE => self::CATEGORY_CYCLING,
  65.         self::TYPE_VIRTUAL_RIDE => self::CATEGORY_CYCLING,
  66.         self::TYPE_GRAVEL_RIDE => self::CATEGORY_CYCLING,
  67.         self::TYPE_MOUNTAIN_BIKE_RIDE => self::CATEGORY_CYCLING,
  68.         self::TYPE_E_BIKE_RIDE => self::CATEGORY_CYCLING,
  69.     ];
  70.     /**
  71.      * @ORM\Id
  72.      * @ORM\GeneratedValue
  73.      * @ORM\Column(type="integer")
  74.      * @Groups({"strava_activity:read", "strava_profile:read"})
  75.      */
  76.     private $id;
  77.     /**
  78.      * @ORM\ManyToOne(targetEntity=StravaProfile::class, inversedBy="activities")
  79.      * @ORM\JoinColumn(nullable=false, onDelete="CASCADE")
  80.      * @Groups({"strava_activity:read"})
  81.      */
  82.     private $stravaProfile;
  83.     /**
  84.      * @ORM\Column(type="bigint", unique=true)
  85.      * @Groups({"strava_activity:read", "strava_profile:read"})
  86.      */
  87.     private $stravaActivityId;
  88.     /**
  89.      * Catégorie : "running" ou "cycling"
  90.      * @ORM\Column(type="string", length=20)
  91.      * @Groups({"strava_activity:read", "strava_profile:read"})
  92.      */
  93.     private $category;
  94.     /**
  95.      * Distance en mètres
  96.      * @ORM\Column(type="float")
  97.      * @Groups({"strava_activity:read", "strava_profile:read"})
  98.      */
  99.     private $distance;
  100.     /**
  101.      * Temps de déplacement en secondes
  102.      * @ORM\Column(type="integer")
  103.      * @Groups({"strava_activity:read", "strava_profile:read"})
  104.      */
  105.     private $movingTime;
  106.     /**
  107.      * Temps total écoulé en secondes
  108.      * @ORM\Column(type="integer")
  109.      * @Groups({"strava_activity:read", "strava_profile:read"})
  110.      */
  111.     private $elapsedTime;
  112.     /**
  113.      * Date/heure de début (UTC)
  114.      * @ORM\Column(type="datetime")
  115.      * @Groups({"strava_activity:read", "strava_profile:read"})
  116.      */
  117.     private $startDate;
  118.     /**
  119.      * Date/heure de début (locale à l'athlète)
  120.      * @ORM\Column(type="datetime")
  121.      * @Groups({"strava_activity:read", "strava_profile:read"})
  122.      */
  123.     private $startDateLocal;
  124.     /**
  125.      * Nombre de pas (course à pied uniquement, calculé via la cadence Strava)
  126.      * Strava ne fournit pas directement un champ "steps", on le calcule :
  127.      * steps = average_cadence * 2 * (moving_time / 60)
  128.      * (cadence Strava = pas par minute d'un seul pied, x2 pour les deux pieds)
  129.      *
  130.      * @ORM\Column(type="integer", nullable=true)
  131.      * @Groups({"strava_activity:read", "strava_profile:read"})
  132.      */
  133.     private $steps;
  134.     /**
  135.      * @ORM\Column(type="datetime")
  136.      * @Gedmo\Timestampable(on="create")
  137.      */
  138.     private $createdAt;
  139.     /**
  140.      * @ORM\Column(type="datetime", nullable=true)
  141.      * @Gedmo\Timestampable(on="update")
  142.      */
  143.     private $updatedAt;
  144.     // ─── Getters / Setters ───────────────────────────────────────────
  145.     public function getId(): ?int
  146.     {
  147.         return $this->id;
  148.     }
  149.     public function getStravaProfile(): ?StravaProfile
  150.     {
  151.         return $this->stravaProfile;
  152.     }
  153.     public function setStravaProfile(?StravaProfile $stravaProfile): self
  154.     {
  155.         $this->stravaProfile = $stravaProfile;
  156.         return $this;
  157.     }
  158.     public function getStravaActivityId(): ?int
  159.     {
  160.         return $this->stravaActivityId;
  161.     }
  162.     public function setStravaActivityId(int $stravaActivityId): self
  163.     {
  164.         $this->stravaActivityId = $stravaActivityId;
  165.         return $this;
  166.     }
  167.     public function getCategory(): ?string
  168.     {
  169.         return $this->category;
  170.     }
  171.     public function setCategory(string $category): self
  172.     {
  173.         $this->category = $category;
  174.         return $this;
  175.     }
  176.     /**
  177.      * Définit la catégorie à partir du type Strava brut
  178.      */
  179.     public function setCategoryFromType(string $stravaType): self
  180.     {
  181.         $this->category = self::TYPE_TO_CATEGORY[$stravaType] ?? 'other';
  182.         return $this;
  183.     }
  184.     public function getDistance(): ?float
  185.     {
  186.         return $this->distance;
  187.     }
  188.     public function setDistance(float $distance): self
  189.     {
  190.         $this->distance = $distance;
  191.         return $this;
  192.     }
  193.     /**
  194.      * Distance en kilomètres
  195.      * @Groups({"strava_activity:read", "strava_profile:read"})
  196.      */
  197.     public function getDistanceKm(): ?float
  198.     {
  199.         return $this->distance !== null ? round($this->distance / 1000, 2) : null;
  200.     }
  201.     public function getMovingTime(): ?int
  202.     {
  203.         return $this->movingTime;
  204.     }
  205.     public function setMovingTime(int $movingTime): self
  206.     {
  207.         $this->movingTime = $movingTime;
  208.         return $this;
  209.     }
  210.     /**
  211.      * Temps de déplacement formaté HH:MM:SS
  212.      * @Groups({"strava_activity:read", "strava_profile:read"})
  213.      */
  214.     public function getMovingTimeFormatted(): ?string
  215.     {
  216.         if ($this->movingTime === null) {
  217.             return null;
  218.         }
  219.         $h = floor($this->movingTime / 3600);
  220.         $m = floor(($this->movingTime % 3600) / 60);
  221.         $s = $this->movingTime % 60;
  222.         return sprintf('%02d:%02d:%02d', $h, $m, $s);
  223.     }
  224.     public function getElapsedTime(): ?int
  225.     {
  226.         return $this->elapsedTime;
  227.     }
  228.     public function setElapsedTime(int $elapsedTime): self
  229.     {
  230.         $this->elapsedTime = $elapsedTime;
  231.         return $this;
  232.     }
  233.     /**
  234.      * Temps total formaté HH:MM:SS
  235.      * @Groups({"strava_activity:read", "strava_profile:read"})
  236.      */
  237.     public function getElapsedTimeFormatted(): ?string
  238.     {
  239.         if ($this->elapsedTime === null) {
  240.             return null;
  241.         }
  242.         $h = floor($this->elapsedTime / 3600);
  243.         $m = floor(($this->elapsedTime % 3600) / 60);
  244.         $s = $this->elapsedTime % 60;
  245.         return sprintf('%02d:%02d:%02d', $h, $m, $s);
  246.     }
  247.     public function getStartDate(): ?\DateTimeInterface
  248.     {
  249.         return $this->startDate;
  250.     }
  251.     public function setStartDate(\DateTimeInterface $startDate): self
  252.     {
  253.         $this->startDate = $startDate;
  254.         return $this;
  255.     }
  256.     public function getStartDateLocal(): ?\DateTimeInterface
  257.     {
  258.         return $this->startDateLocal;
  259.     }
  260.     public function setStartDateLocal(\DateTimeInterface $startDateLocal): self
  261.     {
  262.         $this->startDateLocal = $startDateLocal;
  263.         return $this;
  264.     }
  265.     public function getSteps(): ?int
  266.     {
  267.         return $this->steps;
  268.     }
  269.     public function setSteps(?int $steps): self
  270.     {
  271.         $this->steps = $steps;
  272.         return $this;
  273.     }
  274.     public function getCreatedAt(): ?\DateTimeInterface
  275.     {
  276.         return $this->createdAt;
  277.     }
  278.     public function setCreatedAt(\DateTimeInterface $createdAt): self
  279.     {
  280.         $this->createdAt = $createdAt;
  281.         return $this;
  282.     }
  283.     public function getUpdatedAt(): ?\DateTimeInterface
  284.     {
  285.         return $this->updatedAt;
  286.     }
  287.     public function setUpdatedAt(?\DateTimeInterface $updatedAt): self
  288.     {
  289.         $this->updatedAt = $updatedAt;
  290.         return $this;
  291.     }
  292.     public static function isAllowedType(string $type): bool
  293.     {
  294.         return in_array($type, self::ALLOWED_TYPES);
  295.     }
  296.     /**
  297.      * Calcule le nombre de pas à partir de la cadence Strava.
  298.      * Strava average_cadence pour la course = foulées par minute (un seul pied).
  299.      * Nombre total de pas = cadence * 2 * durée en minutes.
  300.      */
  301.     public static function calculateSteps(?float $averageCadence, int $movingTimeSeconds): ?int
  302.     {
  303.         if ($averageCadence === null || $averageCadence <= 0) {
  304.             return null;
  305.         }
  306.         $movingTimeMinutes = $movingTimeSeconds / 60;
  307.         return (int) round($averageCadence * 2 * $movingTimeMinutes);
  308.     }
  309. }