diff --git a/.env.dist b/.env.dist index d72dd897..1a220f52 100644 --- a/.env.dist +++ b/.env.dist @@ -9,4 +9,5 @@ CACHE.ENABLE=false DISPLAY_ERRORS=true GITHUB_CLIENT_ID= GITHUB_CLIENT_SECRET= +GITHUB_TOKEN= DEV_MODE=true \ No newline at end of file diff --git a/app/config.php b/app/config.php index 27558bab..10054382 100644 --- a/app/config.php +++ b/app/config.php @@ -11,6 +11,7 @@ use Doctrine\ORM\ORMSetup; use Doctrine\ORM\Tools\Console\ConsoleRunner; use Doctrine\ORM\Tools\Console\EntityManagerProvider\SingleManagerProvider; +use Github\AuthMethod; use Github\Client; use League\CommonMark\Extension\CommonMarkCoreExtension; use League\CommonMark\Extension\ExternalLink\ExternalLinkExtension; @@ -50,6 +51,7 @@ use PhpSchool\Website\Action\SubmitWorkshop; use PhpSchool\Website\Action\TrackDownloads; use PhpSchool\Website\Blog\Generator; +use PhpSchool\Website\Command\SyncContributors; use PhpSchool\Website\Online\CloudWorkshopRepository; use PhpSchool\Website\Online\Command\DownloadComposerPackageList; use PhpSchool\Website\Online\Middleware\ExerciseRunnerRateLimiter; @@ -112,6 +114,7 @@ $app->command('create-admin-user name email password', CreateAdminUser::class); $app->command('generate-blog', GenerateBlog::class); $app->command('download-composer-packages', DownloadComposerPackageList::class); + $app->command('sync-contributors', SyncContributors::class); ConsoleRunner::addCommands($app, new SingleManagerProvider($c->get(EntityManagerInterface::class))); @@ -189,6 +192,9 @@ DownloadComposerPackageList::class => function (ContainerInterface $c): DownloadComposerPackageList { return new DownloadComposerPackageList($c->get('guzzle.packagist'), $c->get(LoggerInterface::class)); }, + SyncContributors::class => function (ContainerInterface $c): SyncContributors { + return new SyncContributors($c->get(Client::class), $c->get(LoggerInterface::class)); + }, TrackDownloads::class => function (ContainerInterface $c): TrackDownloads { return new TrackDownloads($c->get(WorkshopRepository::class), $c->get(WorkshopInstallRepository::class)); @@ -212,6 +218,13 @@ ); }, + Client::class => function (ContainerInterface $c): Client { + $client = new Client(); + $client->authenticate($c->get('config')['github']['token'], AuthMethod::ACCESS_TOKEN); + + return $client; + }, + Github::class => function (ContainerInterface $c): Github { return new Github([ 'clientId' => $c->get('config')['github']['clientId'], @@ -582,6 +595,7 @@ public function parse($markdown): string 'github' => [ 'clientId' => $_ENV['GITHUB_CLIENT_ID'], 'clientSecret' => $_ENV['GITHUB_CLIENT_SECRET'], + 'token' => $_ENV['GITHUB_TOKEN'], ], 'jwtSecret' => $_ENV['JWT_SECRET'], diff --git a/assets/components/Website/ProjectContributors.vue b/assets/components/Website/ProjectContributors.vue new file mode 100644 index 00000000..c5436455 --- /dev/null +++ b/assets/components/Website/ProjectContributors.vue @@ -0,0 +1,58 @@ + + + diff --git a/assets/components/Website/SiteFooter.vue b/assets/components/Website/SiteFooter.vue index e5e90a85..f40d1a77 100644 --- a/assets/components/Website/SiteFooter.vue +++ b/assets/components/Website/SiteFooter.vue @@ -3,10 +3,12 @@ import GitHubIcon from "../Icons/GitHubIcon.vue"; import Logo from "./SiteLogo.vue"; import { ref } from "vue"; import JoinSlack from "./JoinSlack.vue"; +import Contributors from "./ProjectContributors.vue"; const currentYear = new Date().getFullYear(); const slackModalOpen = ref(false); +const contributorModalOpen = ref(false); diff --git a/public/index.php b/public/index.php index 056d2df5..894d7dc0 100644 --- a/public/index.php +++ b/public/index.php @@ -16,6 +16,7 @@ use PhpSchool\Website\Action\Admin\Workshop\View; use PhpSchool\Website\Action\BlogPost; use PhpSchool\Website\Action\BlogPosts; +use PhpSchool\Website\Action\Contributors; use PhpSchool\Website\Action\Events; use PhpSchool\Website\Action\Online\ComposerPackageAdd; use PhpSchool\Website\Action\Online\ComposerPackageSearch; @@ -69,6 +70,7 @@ $app->get('/api/events', Events::class); $app->get('/api/posts', BlogPosts::class); $app->post('/api/slack-invite', SlackInvite::class); +$app->get('/api/contributors', Contributors::class); $app ->group('/api/admin', function (RouteCollectorProxy $group) { diff --git a/src/Action/Contributors.php b/src/Action/Contributors.php new file mode 100644 index 00000000..472f064b --- /dev/null +++ b/src/Action/Contributors.php @@ -0,0 +1,19 @@ + $data */ + $data = json_decode((string) file_get_contents(__DIR__ . '/../../var/contributors.json')); + return $this->withJson($data, $response); + } +} diff --git a/src/Action/JsonUtils.php b/src/Action/JsonUtils.php index 212b9c74..c19cc5d9 100644 --- a/src/Action/JsonUtils.php +++ b/src/Action/JsonUtils.php @@ -20,7 +20,7 @@ private function jsonSuccess(Response $response): Response } /** - * @param array $json + * @param array $json */ private function withJson(array $json, Response $response, int $status = 200): Response { diff --git a/src/Command/SyncContributors.php b/src/Command/SyncContributors.php new file mode 100644 index 00000000..6e09b7c6 --- /dev/null +++ b/src/Command/SyncContributors.php @@ -0,0 +1,69 @@ +downloadContributors(); + } catch (\Exception $e) { + $this->logger->error('Could not download contributors. Error: ' . $e->getMessage()); + return; + } + + file_put_contents(self::CONTRIBUTORS_FILE_LOCATION, json_encode($contributors, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR)); + + $output->writeln('Contributors synced'); + } + + /** + * @return array + */ + private function downloadContributors(): array + { + $repositories = $this->client->user()->repositories('php-school'); + + $contributors = []; + + foreach ($repositories as $repository) { + $result = $this->client->repositories()->contributors('php-school', $repository['name']); + + if (!is_array($result)) { + continue; + } + + foreach ($result as $contributor) { + if (!isset($contributors[$contributor['login']])) { + $contributors[$contributor['login']] = [ + 'username' => $contributor['login'], + 'contributions' => (int) $contributor['contributions'], + 'profilePic' => $contributor['avatar_url'], + 'profile' => $contributor['html_url'], + ]; + } else { + $contributors[$contributor['login']]['contributions'] += (int) $contributor['contributions']; + } + } + } + + usort($contributors, fn(array $a, array $b) => $b['contributions'] <=> $a['contributions']); + + return $contributors; + } +}