Skip to content

Commit 18992cb

Browse files
committed
added dk packages page
Signed-off-by: bidi <bidi@apidemia.com>
1 parent 6ddb395 commit 18992cb

16 files changed

Lines changed: 898 additions & 651 deletions

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,7 @@ package-lock.json
5252
**/.DS_Store
5353

5454
public/uploads/
55+
56+
# Generated by bin/generate-packages (daily cron); not tracked so it never conflicts on pull
57+
/data/packages.json
58+
/data/packages.json.tmp

bin/generate-packages

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#!/usr/bin/env php
2+
<?php
3+
4+
/**
5+
* Rebuilds the Dotkernel packages listing from the GitHub organisation.
6+
*
7+
* Intended to run from cron, e.g. daily:
8+
* 0 4 * * * cd /path/to/dotkernel.com && /usr/bin/php bin/generate-packages >> log/generate-packages.log 2>&1
9+
*
10+
* Exits non-zero without touching the data file when the run cannot be trusted, so the
11+
* previously generated listing keeps serving.
12+
*/
13+
14+
declare(strict_types=1);
15+
16+
use Light\App\Service\PackageGenerator;
17+
18+
chdir(__DIR__ . '/../');
19+
20+
require 'vendor/autoload.php';
21+
22+
$container = require 'config/container.php';
23+
$packageGenerator = $container->get(PackageGenerator::class);
24+
25+
try {
26+
$report = $packageGenerator->write();
27+
} catch (Throwable $exception) {
28+
fwrite(STDERR, sprintf(
29+
'[%s] generate-packages failed: %s%s',
30+
date('c'),
31+
$exception->getMessage(),
32+
PHP_EOL
33+
));
34+
exit(1);
35+
}
36+
37+
printf(
38+
'[%s] Done. %d package%s written to %s%s',
39+
date('c'),
40+
$report['written'],
41+
$report['written'] === 1 ? '' : 's',
42+
$packageGenerator->getDataFile(),
43+
PHP_EOL
44+
);
45+
46+
if ($report['skipped'] !== []) {
47+
printf('Skipped by ignoreRepos: %s%s', implode(', ', $report['skipped']), PHP_EOL);
48+
}
49+
50+
if ($report['ignoredMisses'] !== []) {
51+
printf(
52+
'WARNING: ignoreRepos entries matched nothing (renamed or deleted?): %s%s',
53+
implode(', ', $report['ignoredMisses']),
54+
PHP_EOL
55+
);
56+
}
57+
58+
foreach ($report['warnings'] as $warning) {
59+
printf('WARNING: %s%s', $warning, PHP_EOL);
60+
}

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
},
2828
"require": {
2929
"php": "~8.5.0",
30+
"ext-curl": "*",
3031
"ext-dom": "*",
3132
"doctrine/data-fixtures": "^2.2",
3233
"doctrine/doctrine-fixtures-bundle": "^4.3",

config/autoload/local.php.dist

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,21 @@ return [
5959
'feed' => [
6060
'path' => realpath(__DIR__ . '/../../public/feed.xml'),
6161
],
62+
/**
63+
* Credentials for `bin/generate-packages`. Reading public repositories needs no token
64+
* scopes at all; without a token the generator still runs, at a lower rate limit.
65+
* Non-credential settings (ignore list, output path) live in `packages.global.php`.
66+
*/
67+
'github' => [
68+
'userAgent' => 'dotkernel.com',
69+
'authBearer' => '',
70+
'org' => 'dotkernel',
71+
],
72+
// `dotkernel-packages-oss-lifecycle` is intentionally absent: it has a dedicated handler
73+
// and is routed in Light\App\RoutesDelegator. Re-adding it here registers the path twice.
6274
'routes' => [
6375
'page' => [
64-
'contact' => 'contact',
65-
'dotkernel-packages-oss-lifecycle' => 'dotkernel-packages-oss-lifecycle',
76+
'contact' => 'contact',
6677
],
6778
],
6879
'twig' => [
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
/**
4+
* Dotkernel packages listing.
5+
*
6+
* Consumed by `bin/generate-packages`, which queries the GitHub organisation and writes the
7+
* result to `dataFile`. Credentials are intentionally NOT stored here - the GitHub token lives
8+
* in `config/autoload/local.php` under the `github` key, which is ignored by git.
9+
*
10+
* `local.php` is merged after every `*.global.php`, so any value below can be overridden locally.
11+
*/
12+
13+
declare(strict_types=1);
14+
15+
return [
16+
'packages' => [
17+
/**
18+
* Repositories that must never appear on the packages page, matched on the bare
19+
* repository name (case-insensitive). The generator reports entries that matched
20+
* nothing, so a renamed repository does not silently reappear on the site.
21+
*/
22+
'ignoreRepos' => [
23+
'dotkernel.com',
24+
],
25+
'dataFile' => __DIR__ . '/../../data/packages.json',
26+
'includeArchived' => true,
27+
'timeout' => 10,
28+
'connectTimeout' => 5,
29+
],
30+
];

src/App/src/ConfigProvider.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,18 @@
1616
use Light\App\Factory\GetFeedViewHandlerFactory;
1717
use Light\App\Factory\GetIndexViewHandlerFactory;
1818
use Light\App\Factory\GetMarkdownArticleHandlerFactory;
19+
use Light\App\Factory\GetPackagesViewHandlerFactory;
20+
use Light\App\Factory\GitHubClientFactory;
21+
use Light\App\Factory\PackageGeneratorFactory;
1922
use Light\App\Handler\GetFeedViewHandler;
2023
use Light\App\Handler\GetIndexViewHandler;
2124
use Light\App\Handler\GetMarkdownArticleHandler;
25+
use Light\App\Handler\GetPackagesViewHandler;
2226
use Light\App\Resolver\EntityListenerResolver;
2327
use Light\App\Service\FeedGenerator;
28+
use Light\App\Service\GitHubClient;
29+
use Light\App\Service\GitHubClientInterface;
30+
use Light\App\Service\PackageGenerator;
2431
use Mezzio\Application;
2532
use Roave\PsrContainerDoctrine\EntityManagerFactory;
2633
use Symfony\Component\Cache\Adapter\AdapterInterface;
@@ -121,11 +128,15 @@ public function getDependencies(): array
121128
GetIndexViewHandler::class => GetIndexViewHandlerFactory::class,
122129
GetFeedViewHandler::class => GetFeedViewHandlerFactory::class,
123130
GetMarkdownArticleHandler::class => GetMarkdownArticleHandlerFactory::class,
131+
GetPackagesViewHandler::class => GetPackagesViewHandlerFactory::class,
124132
FeedGenerator::class => FeedGeneratorFactory::class,
133+
GitHubClient::class => GitHubClientFactory::class,
134+
PackageGenerator::class => PackageGeneratorFactory::class,
125135
],
126136
'aliases' => [
127137
EntityManager::class => 'doctrine.entity_manager.orm_default',
128138
EntityManagerInterface::class => 'doctrine.entity_manager.orm_default',
139+
GitHubClientInterface::class => GitHubClient::class,
129140
],
130141
];
131142
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Light\App\Factory;
6+
7+
use Light\App\Handler\GetPackagesViewHandler;
8+
use Light\App\Service\PackageGenerator;
9+
use Light\Blog\Repository\CategoryRepository;
10+
use Light\Blog\Repository\PostRepository;
11+
use Mezzio\Template\TemplateRendererInterface;
12+
use Psr\Container\ContainerInterface;
13+
14+
use function assert;
15+
16+
class GetPackagesViewHandlerFactory
17+
{
18+
public function __invoke(ContainerInterface $container): GetPackagesViewHandler
19+
{
20+
$template = $container->get(TemplateRendererInterface::class);
21+
assert($template instanceof TemplateRendererInterface);
22+
23+
$categoryRepository = $container->get(CategoryRepository::class);
24+
assert($categoryRepository instanceof CategoryRepository);
25+
26+
$postRepository = $container->get(PostRepository::class);
27+
assert($postRepository instanceof PostRepository);
28+
29+
$packageGenerator = $container->get(PackageGenerator::class);
30+
assert($packageGenerator instanceof PackageGenerator);
31+
32+
return new GetPackagesViewHandler($template, $categoryRepository, $postRepository, $packageGenerator);
33+
}
34+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Light\App\Factory;
6+
7+
use Light\App\Service\GitHubClient;
8+
use Psr\Container\ContainerInterface;
9+
10+
use function is_array;
11+
12+
class GitHubClientFactory
13+
{
14+
public function __invoke(ContainerInterface $container): GitHubClient
15+
{
16+
$config = $container->get('config');
17+
18+
// the token is in `config/autoload/local.php`
19+
// if unauthenticated, the client has a lower call limit
20+
$github = is_array($config) && isset($config['github']) && is_array($config['github'])
21+
? $config['github']
22+
: [];
23+
$packages = is_array($config) && isset($config['packages']) && is_array($config['packages'])
24+
? $config['packages']
25+
: [];
26+
27+
return new GitHubClient(
28+
(string) ($github['authBearer'] ?? ''),
29+
(string) ($github['userAgent'] ?? 'dotkernel.com'),
30+
(int) ($packages['timeout'] ?? 10),
31+
(int) ($packages['connectTimeout'] ?? 5),
32+
);
33+
}
34+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Light\App\Factory;
6+
7+
use Light\App\Service\GitHubClientInterface;
8+
use Light\App\Service\PackageGenerator;
9+
use Psr\Container\ContainerInterface;
10+
11+
use function assert;
12+
use function is_array;
13+
use function is_scalar;
14+
15+
class PackageGeneratorFactory
16+
{
17+
public function __invoke(ContainerInterface $container): PackageGenerator
18+
{
19+
$client = $container->get(GitHubClientInterface::class);
20+
assert($client instanceof GitHubClientInterface);
21+
22+
$config = $container->get('config');
23+
24+
$github = is_array($config) && isset($config['github']) && is_array($config['github'])
25+
? $config['github']
26+
: [];
27+
$packages = is_array($config) && isset($config['packages']) && is_array($config['packages'])
28+
? $config['packages']
29+
: [];
30+
31+
$ignoreRepos = [];
32+
if (isset($packages['ignoreRepos']) && is_array($packages['ignoreRepos'])) {
33+
foreach ($packages['ignoreRepos'] as $repository) {
34+
if (is_scalar($repository)) {
35+
$ignoreRepos[] = (string) $repository;
36+
}
37+
}
38+
}
39+
40+
return new PackageGenerator(
41+
$client,
42+
(string) ($packages['dataFile'] ?? 'data/packages.json'),
43+
(string) ($github['org'] ?? 'dotkernel'),
44+
$ignoreRepos,
45+
(bool) ($packages['includeArchived'] ?? true),
46+
);
47+
}
48+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Light\App\Handler;
6+
7+
use Laminas\Diactoros\Response\HtmlResponse;
8+
use Light\App\Service\PackageGenerator;
9+
use Light\Blog\Repository\CategoryRepository;
10+
use Light\Blog\Repository\PostRepository;
11+
use Mezzio\Template\TemplateRendererInterface;
12+
use Psr\Http\Message\ResponseInterface;
13+
use Psr\Http\Message\ServerRequestInterface;
14+
use Psr\Http\Server\RequestHandlerInterface;
15+
16+
use function is_array;
17+
use function is_string;
18+
19+
class GetPackagesViewHandler implements RequestHandlerInterface
20+
{
21+
public const string TEMPLATE = 'page::dotkernel-packages-oss-lifecycle';
22+
23+
public function __construct(
24+
private readonly TemplateRendererInterface $template,
25+
private readonly CategoryRepository $categoryRepository,
26+
private readonly PostRepository $postRepository,
27+
private readonly PackageGenerator $packageGenerator,
28+
) {
29+
}
30+
31+
public function handle(ServerRequestInterface $request): ResponseInterface
32+
{
33+
// no inline regeneration
34+
// data is cached in `data/packages.json` by `bin/generate-packages`
35+
$data = $this->packageGenerator->read();
36+
37+
$packages = is_array($data) && isset($data['packages']) && is_array($data['packages'])
38+
? $data['packages']
39+
: [];
40+
41+
$generatedAt = is_array($data) && isset($data['generated_at']) && is_string($data['generated_at'])
42+
? $data['generated_at']
43+
: null;
44+
45+
return new HtmlResponse(
46+
$this->template->render(self::TEMPLATE, [
47+
'posts' => $this->postRepository->getRecentPosts(3),
48+
'categories' => $this->categoryRepository->getCategories(),
49+
'packages' => $packages,
50+
'generatedAt' => $generatedAt,
51+
])
52+
);
53+
}
54+
}

0 commit comments

Comments
 (0)