feat: add nginx + export ics cal
This commit is contained in:
parent
7870058af6
commit
5879fbd146
|
|
@ -5,12 +5,14 @@
|
|||
"prefer-stable": true,
|
||||
"require": {
|
||||
"php": ">=8.4",
|
||||
"ext-apcu": "*",
|
||||
"ext-ctype": "*",
|
||||
"ext-iconv": "*",
|
||||
"doctrine/doctrine-bundle": "^3.1",
|
||||
"doctrine/doctrine-migrations-bundle": "^4.0",
|
||||
"doctrine/orm": "^3.6",
|
||||
"ramsey/uuid-doctrine": "^2.1",
|
||||
"spatie/icalendar-generator": "^3.2",
|
||||
"symfony/console": "8.0.*",
|
||||
"symfony/dotenv": "8.0.*",
|
||||
"symfony/flex": "^2",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "02657b010196b6bde0f0c61b3c41708c",
|
||||
"content-hash": "72529ff122c6d2ca2b568e195ae17b67",
|
||||
"packages": [
|
||||
{
|
||||
"name": "brick/math",
|
||||
|
|
@ -1724,6 +1724,65 @@
|
|||
],
|
||||
"time": "2024-05-27T00:00:21+00:00"
|
||||
},
|
||||
{
|
||||
"name": "spatie/icalendar-generator",
|
||||
"version": "3.2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/spatie/icalendar-generator.git",
|
||||
"reference": "410885abfd26d8653234cead2ae1da78e7558cdb"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/spatie/icalendar-generator/zipball/410885abfd26d8653234cead2ae1da78e7558cdb",
|
||||
"reference": "410885abfd26d8653234cead2ae1da78e7558cdb",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-mbstring": "*",
|
||||
"php": "^8.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-json": "*",
|
||||
"larapack/dd": "^1.1",
|
||||
"nesbot/carbon": "^3.5",
|
||||
"pestphp/pest": "^2.34 || ^3.0 || ^4.0",
|
||||
"phpstan/phpstan": "^2.0",
|
||||
"spatie/pest-plugin-snapshots": "^2.1"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Spatie\\IcalendarGenerator\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Ruben Van Assche",
|
||||
"email": "ruben@spatie.be",
|
||||
"homepage": "https://spatie.be",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "Build calendars in the iCalendar format",
|
||||
"homepage": "https://github.com/spatie/icalendar-generator",
|
||||
"keywords": [
|
||||
"calendar",
|
||||
"iCalendar",
|
||||
"ical",
|
||||
"ics",
|
||||
"spatie"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/spatie/icalendar-generator/issues",
|
||||
"source": "https://github.com/spatie/icalendar-generator/tree/3.2.0"
|
||||
},
|
||||
"time": "2025-12-03T11:07:27+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/cache",
|
||||
"version": "v8.0.1",
|
||||
|
|
@ -6267,6 +6326,7 @@
|
|||
"prefer-lowest": false,
|
||||
"platform": {
|
||||
"php": ">=8.4",
|
||||
"ext-apcu": "*",
|
||||
"ext-ctype": "*",
|
||||
"ext-iconv": "*"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ framework:
|
|||
router:
|
||||
# Configure how to generate URLs in non-HTTP contexts, such as CLI commands.
|
||||
# See https://symfony.com/doc/current/routing.html#generating-urls-in-commands
|
||||
default_uri: '%env(DEFAULT_URI)%'
|
||||
# default_uri: '%env(DEFAULT_URI)%'
|
||||
|
||||
when@prod:
|
||||
framework:
|
||||
|
|
|
|||
|
|
@ -1,11 +1,6 @@
|
|||
# yaml-language-server: $schema=../vendor/symfony/routing/Loader/schema/routing.schema.json
|
||||
|
||||
# This file is the entry point to configure the routes of your app.
|
||||
# Methods with the #[Route] attribute are automatically imported.
|
||||
# See also https://symfony.com/doc/current/routing.html
|
||||
|
||||
# To list all registered routes, run the following command:
|
||||
# bin/console debug:router
|
||||
|
||||
controllers:
|
||||
resource: routing.controllers
|
||||
get_calendar:
|
||||
path: /calendar
|
||||
controller: App\Infrastructure\Controller\Calendar::getCalendar
|
||||
methods: GET
|
||||
|
|
|
|||
|
|
@ -1,4 +1,30 @@
|
|||
services:
|
||||
traefik:
|
||||
image: traefik:3.6.5
|
||||
command:
|
||||
- "--api.insecure=true" # Dashboard Traefik (optionnel)
|
||||
- "--providers.docker=true" # Labels Docker
|
||||
- "--entrypoints.web.address=:80" # HTTP
|
||||
ports:
|
||||
- "80:80"
|
||||
- "8080:8080" # Dashboard (facultatif)
|
||||
volumes:
|
||||
- "/var/run/docker.sock:/var/run/docker.sock:ro"
|
||||
|
||||
nhl-schedule-nginx:
|
||||
image: nginx:1.29.4
|
||||
volumes:
|
||||
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf:rw,cached
|
||||
- ./public:/var/www/public:rw,cached
|
||||
labels:
|
||||
- traefik.http.routers.nhl-schedule.rule=Host(`local.match-schedule.home`)
|
||||
- traefik.http.routers.nhl-schedule.middlewares=nhl-schedule
|
||||
- traefik.http.middlewares.nhl-schedule.headers.customresponseheaders.Access-Control-Allow-Methods=POST, PATCH, GET, PUT, OPTIONS, DELETE
|
||||
- traefik.http.middlewares.nhl-schedule.headers.customresponseheaders.Access-Control-Allow-Origin=*
|
||||
- traefik.http.middlewares.nhl-schedule.headers.customresponseheaders.Access-Control-Allow-Headers=x-requested-with, Content-Type,Authorization,Location
|
||||
- traefik.http.middlewares.nhl-schedule.headers.customresponseheaders.Access-Control-Expose-Headers=link, Location
|
||||
- traefik.port=80
|
||||
|
||||
nhl-schedule:
|
||||
image: nhl-schedule:dev
|
||||
user: 1000:1000
|
||||
|
|
@ -8,8 +34,6 @@ services:
|
|||
- ./:/var/www:rw,cached
|
||||
- ./docker/php-fpm/ini/xdebug.ini:/usr/local/etc/php/conf.d/xdebug.ini
|
||||
- ./docker/php-fpm/ini/local.ini:/usr/local/etc/php/conf.d/local.ini
|
||||
# extra_hosts:
|
||||
# - host.docker.internal:host-gateway
|
||||
|
||||
postgres:
|
||||
image: postgres:18.1
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
server {
|
||||
listen 80;
|
||||
server_name local.match-schedule.home;
|
||||
client_max_body_size 100M;
|
||||
|
||||
index index.php index.html;
|
||||
|
||||
error_log /var/log/nginx/error.log;
|
||||
access_log /var/log/nginx/access.log;
|
||||
root /var/www/public;
|
||||
|
||||
location ~ \.php$ {
|
||||
try_files $uri =404;
|
||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||
fastcgi_pass nhl-schedule:9000;
|
||||
fastcgi_index index.php;
|
||||
include fastcgi_params;
|
||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||
fastcgi_param PATH_INFO $fastcgi_path_info;
|
||||
fastcgi_buffers 16 16k;
|
||||
fastcgi_buffer_size 32k;
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.php?$query_string;
|
||||
gzip_static on;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace App\Application\ReadModel;
|
||||
|
||||
readonly class Game
|
||||
{
|
||||
public function __construct(
|
||||
public string $id,
|
||||
public string $providerId,
|
||||
public string $providerGameId,
|
||||
public \DateTimeImmutable $startTimeScheduled,
|
||||
public ?\DateTimeImmutable $endTimeScheduled,
|
||||
public string $seasonId,
|
||||
public string $homeTeamId,
|
||||
public string $awayTeamId,
|
||||
public string $venue,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
namespace App\Application\ReadModel;
|
||||
|
||||
class Provider
|
||||
readonly class Provider
|
||||
{
|
||||
public function __construct(
|
||||
public readonly string $id,
|
||||
public readonly string $name,
|
||||
public string $id,
|
||||
public string $name,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace App\Application\ReadModel;
|
||||
|
||||
readonly class Team
|
||||
{
|
||||
public function __construct(
|
||||
public string $id,
|
||||
public string $providerId,
|
||||
public string $providerTeamId,
|
||||
public string $name,
|
||||
public string $alias,
|
||||
public bool $active,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
|
@ -22,10 +22,14 @@ class FetchNHLMatch
|
|||
|
||||
public function __invoke(FetchNHLMatchRequest $request): void
|
||||
{
|
||||
$provider = $this->entityManager->getRepository(Provider::class)->findOneBy(['id' => '885fe581-c4c3-45e7-a06c-29ece7d47fad']); // id should not be here
|
||||
$provider =
|
||||
$this->entityManager->getRepository(Provider::class)->findOneBy(['id' => '885fe581-c4c3-45e7-a06c-29ece7d47fad']) // id should not be here
|
||||
?? throw new \Exception('Provider not found');
|
||||
$matchs = $this->sportRadarEngine->getNHLSchedule($request->year, ENHLSeasonType::from($request->type));
|
||||
|
||||
$season = $this->entityManager->getRepository(Season::class)->findOneBy(['providerSeasonId' => $matchs['season']['providerId']]);
|
||||
$season =
|
||||
$this->entityManager->getRepository(Season::class)->findOneBy(['providerSeasonId' => $matchs['season']['providerId']])
|
||||
?? throw new \Exception('Season not found');
|
||||
/** @var array<string, Team> $teams */
|
||||
$teams = [];
|
||||
|
||||
|
|
|
|||
|
|
@ -4,11 +4,11 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Application\UseCase;
|
||||
|
||||
class FetchNHLMatchRequest
|
||||
readonly class FetchNHLMatchRequest
|
||||
{
|
||||
public function __construct(
|
||||
public readonly int $year,
|
||||
public readonly string $type,
|
||||
public int $year,
|
||||
public string $type,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
namespace App\Application\UseCase;
|
||||
|
||||
use App\Application\ReadModel\Game;
|
||||
use App\Application\ReadModel\Team;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Spatie\IcalendarGenerator\Components\Calendar;
|
||||
use Spatie\IcalendarGenerator\Components\Event;
|
||||
|
||||
class GetGamesCalendar
|
||||
{
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
) {
|
||||
}
|
||||
|
||||
public function __invoke(GetGamesCalendarRequest $request): string
|
||||
{
|
||||
$calendar = Calendar::create()
|
||||
->name('Game')
|
||||
->description('List game schedule');
|
||||
|
||||
if (empty($request->teams)) {
|
||||
return $calendar->get();
|
||||
}
|
||||
|
||||
$teams = [];
|
||||
foreach ($request->teams as $team) {
|
||||
$teams[$team] = $this->entityManager->getRepository(Team::class)->find($team);
|
||||
}
|
||||
|
||||
// $criteria = Criteria::create()
|
||||
// ->where(Criteria::expr()->in('homeTeamId', $request->teams))
|
||||
// ->orWhere(Criteria::expr()->in('awayTeamId', $request->teams))
|
||||
// ->orderBy(['startTimeScheduled' => 'ASC']);
|
||||
//
|
||||
// /** @var Game[] $games */
|
||||
// $games = $this->entityManager->getRepository(Game::class)->matching($criteria);
|
||||
|
||||
/** @var Game[] $homeGames */
|
||||
$homeGames = $this->entityManager->getRepository(Game::class)->findBy(['homeTeamId' => $request->teams]);
|
||||
/** @var Game[] $awayGames */
|
||||
$awayGames = $this->entityManager->getRepository(Game::class)->findBy(['awayTeamId' => $request->teams]);
|
||||
|
||||
$gamesById = [];
|
||||
foreach (array_merge($homeGames, $awayGames) as $game) {
|
||||
$gamesById[$game->id] = $game;
|
||||
}
|
||||
/** @var Game[] $games */
|
||||
$games = array_values($gamesById);
|
||||
|
||||
$events = [];
|
||||
foreach ($games as $game) {
|
||||
$home = $this->entityManager->getRepository(Team::class)->find($game->homeTeamId) ?? throw new \Exception('Team not found');
|
||||
$away = $this->entityManager->getRepository(Team::class)->find($game->awayTeamId) ?? throw new \Exception('Team not found');
|
||||
|
||||
$events[] = Event::create($home->name . ' vs ' . $away->name)
|
||||
->startsAt($game->startTimeScheduled)
|
||||
->endsAt($game->endTimeScheduled ?? $game->startTimeScheduled->modify('+3 hours')) // should be a parameter of the league
|
||||
->address($game->venue);
|
||||
}
|
||||
|
||||
return $calendar
|
||||
->event($events)
|
||||
->get()
|
||||
;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
namespace App\Application\UseCase;
|
||||
|
||||
readonly class GetGamesCalendarRequest
|
||||
{
|
||||
public function __construct(
|
||||
/** @var string[] $teams */
|
||||
public array $teams,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace App\Infrastructure\Controller;
|
||||
|
||||
use App\Application\CommandBus\UseCaseCommandBus;
|
||||
use App\Application\UseCase\GetGamesCalendarRequest;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class Calendar extends AbstractController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly UseCaseCommandBus $commandBus,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getCalendar(Request $request): Response
|
||||
{
|
||||
$calendar = $this->commandBus->ask(new GetGamesCalendarRequest($request->query->all('teams')));
|
||||
|
||||
return new Response($calendar, 200, [
|
||||
'Content-Type' => 'text/calendar; charset=utf-8',
|
||||
'Content-Disposition' => 'attachment; filename="calendar.ics"',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
$builder = new Doctrine\ORM\Mapping\Builder\ClassMetadataBuilder($metadata);
|
||||
|
||||
$builder
|
||||
->setTable('game')
|
||||
->setReadOnly()
|
||||
;
|
||||
|
||||
$builder
|
||||
->createField('id', 'uuid')
|
||||
->nullable(false)
|
||||
->makePrimaryKey()
|
||||
->build();
|
||||
|
||||
$builder
|
||||
->createField('providerId', 'uuid')
|
||||
->columnName('provider_id')
|
||||
->nullable(false)
|
||||
->build();
|
||||
|
||||
$builder
|
||||
->createField('providerGameId', 'uuid')
|
||||
->columnName('provider_game_id')
|
||||
->nullable(false)
|
||||
->build();
|
||||
|
||||
$builder
|
||||
->createField('startTimeScheduled', 'datetimetz_immutable')
|
||||
->columnName('start_time_scheduled')
|
||||
->nullable(false)
|
||||
->build();
|
||||
|
||||
$builder
|
||||
->createField('endTimeScheduled', 'datetimetz_immutable')
|
||||
->columnName('end_time_scheduled')
|
||||
->nullable()
|
||||
->build();
|
||||
|
||||
$builder
|
||||
->createField('seasonId', 'uuid')
|
||||
->columnName('season_id')
|
||||
->nullable(false)
|
||||
->build();
|
||||
|
||||
$builder
|
||||
->createField('homeTeamId', 'uuid')
|
||||
->columnName('home_team_id')
|
||||
->nullable(false)
|
||||
->build();
|
||||
|
||||
$builder
|
||||
->createField('awayTeamId', 'uuid')
|
||||
->columnName('away_team_id')
|
||||
->nullable(false)
|
||||
->build();
|
||||
|
||||
$builder
|
||||
->createField('venue', 'string')
|
||||
->nullable(false)
|
||||
->build();
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
$builder = new Doctrine\ORM\Mapping\Builder\ClassMetadataBuilder($metadata);
|
||||
|
||||
$builder
|
||||
->setReadOnly()
|
||||
->setTable('team')
|
||||
;
|
||||
|
||||
$builder
|
||||
->createField('id', 'uuid')
|
||||
->nullable(false)
|
||||
->makePrimaryKey()
|
||||
->build();
|
||||
|
||||
$builder
|
||||
->createField('providerId', 'uuid')
|
||||
->columnName('provider_id')
|
||||
->nullable(false)
|
||||
->build();
|
||||
|
||||
$builder
|
||||
->createField('providerTeamId', 'string')
|
||||
->columnName('provider_team_id')
|
||||
->nullable(false)
|
||||
->build();
|
||||
|
||||
$builder
|
||||
->createField('name', 'string')
|
||||
->nullable(false)
|
||||
->build();
|
||||
|
||||
$builder
|
||||
->createField('alias', 'string')
|
||||
->nullable(false)
|
||||
->build();
|
||||
|
||||
$builder
|
||||
->createField('active', 'boolean')
|
||||
->nullable(false)
|
||||
->build();
|
||||
Loading…
Reference in New Issue