Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
37.70% covered (danger)
37.70%
23 / 61
50.00% covered (danger)
50.00%
3 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
SyncStravaCommand
37.70% covered (danger)
37.70%
23 / 61
50.00% covered (danger)
50.00%
3 / 6
106.27
0.00% covered (danger)
0.00%
0 / 1
 getAllowedAppEnvs
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 configure
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 handle
46.67% covered (danger)
46.67%
7 / 15
0.00% covered (danger)
0.00%
0 / 1
17.71
 syncStravaForYear
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 syncStravaForUserForYear
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 syncStravaLinks
25.71% covered (danger)
25.71%
9 / 35
0.00% covered (danger)
0.00%
0 / 1
27.09
1<?php
2
3namespace Olz\Command;
4
5use Olz\Command\Common\OlzCommand;
6use Olz\Entity\Anniversary\RunRecord;
7use Olz\Entity\StravaLink;
8use Symfony\Component\Console\Attribute\AsCommand;
9use Symfony\Component\Console\Command\Command;
10use Symfony\Component\Console\Input\InputArgument;
11use Symfony\Component\Console\Input\InputInterface;
12use Symfony\Component\Console\Output\OutputInterface;
13
14#[AsCommand(name: 'olz:sync-strava')]
15class SyncStravaCommand extends OlzCommand {
16    /** @return array<string> */
17    protected function getAllowedAppEnvs(): array {
18        return ['dev', 'test', 'staging', 'prod'];
19    }
20
21    protected function configure(): void {
22        $this->addArgument('year', InputArgument::REQUIRED, 'Year (YYYY)');
23        $this->addArgument('user', InputArgument::OPTIONAL, 'User ID');
24    }
25
26    protected function handle(InputInterface $input, OutputInterface $output): int {
27        $year = $input->getArgument('year');
28        if (!preg_match('/^[0-9]{4}$/', $year) || intval($year) < 1996) {
29            $this->logAndOutput("Invalid year: {$year}. Must be in format YYYY and 1996 or later.", level: 'notice');
30            return Command::INVALID;
31        }
32        $year = intval($year);
33        $user_id = $input->getArgument('user');
34        if ($user_id === null) {
35            $this->syncStravaForYear($year);
36            return Command::SUCCESS;
37        }
38        $int_user_id = $user_id ? intval($user_id) : null;
39        if (!preg_match('/^[0-9]+$/', $user_id) || intval($user_id) < 1 || !$int_user_id) {
40            $this->logAndOutput("Invalid user: {$user_id}. Must be a positive integer.", level: 'notice');
41            return Command::INVALID;
42        }
43        $this->syncStravaForUserForYear($int_user_id, $year);
44        return Command::SUCCESS;
45    }
46
47    protected function syncStravaForYear(int $year): void {
48        $this->logAndOutput("Syncing Strava for {$year}...");
49
50        $strava_link_repo = $this->entityManager()->getRepository(StravaLink::class);
51        $strava_links = $strava_link_repo->findAll();
52
53        $this->syncStravaLinks($strava_links);
54    }
55
56    protected function syncStravaForUserForYear(?int $user_id, int $year): void {
57        $this->logAndOutput("Syncing Strava for user {$user_id} for {$year}...");
58
59        $strava_link_repo = $this->entityManager()->getRepository(StravaLink::class);
60        $strava_links = $strava_link_repo->findBy(['user' => $user_id]);
61
62        $this->syncStravaLinks($strava_links);
63    }
64
65    /** @param array<StravaLink> $strava_links */
66    protected function syncStravaLinks(array $strava_links): void {
67        $now = new \DateTime($this->dateUtils()->getIsoNow());
68        $runs_repo = $this->entityManager()->getRepository(RunRecord::class);
69        foreach ($strava_links as $strava_link) {
70            $this->logAndOutput("Syncing {$strava_link}...");
71            $user = $strava_link->getUser();
72            $access_token = $this->stravaUtils()->getAccessToken($strava_link);
73            if (!$access_token) {
74                $this->logAndOutput("{$strava_link} has no access token...", level: 'debug');
75                continue;
76            }
77            // $activities = $this->stravaUtils()->callStravaApi('GET', '/athlete/activities', [], $access_token);
78            $activities = $this->stravaUtils()->callStravaApi('GET', '/clubs/158910/activities', [], $access_token);
79            foreach ($activities as $activity) {
80                $firstname = $activity['athlete']['firstname'] ?? '';
81                $lastname = $activity['athlete']['lastname'] ?? '';
82                $distance = $activity['distance'];
83                $moving_time = $activity['moving_time'];
84                $elapsed_time = $activity['elapsed_time'];
85                $total_elevation_gain = $activity['total_elevation_gain'];
86                $sport_type = $activity['sport_type'];
87                $is_run = $sport_type === 'Run' || $sport_type === 'TrailRun';
88                $id = md5("{$firstname}-{$lastname}-{$distance}-{$total_elevation_gain}-{$moving_time}-{$elapsed_time}");
89                $source = "strava-{$id}";
90                $existing = $runs_repo->findOneBy(['source' => $source]);
91                if ($existing !== null) {
92                    continue;
93                }
94                $this->logAndOutput("New activity: {$source} by {$firstname} {$lastname}");
95                $run = new RunRecord();
96                $this->entityUtils()->createOlzEntity($run, ['onOff' => true]);
97                $run->setUser(null);
98                $run->setRunAt($now);
99                $run->setDistanceMeters(intval($distance));
100                $run->setElevationMeters(intval($total_elevation_gain));
101                $run->setSource($source);
102                $run->setInfo(json_encode($activity) ?: null);
103                $this->entityManager()->persist($run);
104                $this->entityManager()->flush();
105            }
106        }
107    }
108}