Compare commits

..

No commits in common. "5879fbd146ae5076fb9f46bca502aa6f373d5ebe" and "edd4a185da15c2a36241dabc1e76cb88d64f9d6b" have entirely different histories.

21 changed files with 26 additions and 384 deletions

View File

@ -5,14 +5,12 @@
"prefer-stable": true, "prefer-stable": true,
"require": { "require": {
"php": ">=8.4", "php": ">=8.4",
"ext-apcu": "*",
"ext-ctype": "*", "ext-ctype": "*",
"ext-iconv": "*", "ext-iconv": "*",
"doctrine/doctrine-bundle": "^3.1", "doctrine/doctrine-bundle": "^3.1",
"doctrine/doctrine-migrations-bundle": "^4.0", "doctrine/doctrine-migrations-bundle": "^4.0",
"doctrine/orm": "^3.6", "doctrine/orm": "^3.6",
"ramsey/uuid-doctrine": "^2.1", "ramsey/uuid-doctrine": "^2.1",
"spatie/icalendar-generator": "^3.2",
"symfony/console": "8.0.*", "symfony/console": "8.0.*",
"symfony/dotenv": "8.0.*", "symfony/dotenv": "8.0.*",
"symfony/flex": "^2", "symfony/flex": "^2",

62
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "72529ff122c6d2ca2b568e195ae17b67", "content-hash": "02657b010196b6bde0f0c61b3c41708c",
"packages": [ "packages": [
{ {
"name": "brick/math", "name": "brick/math",
@ -1724,65 +1724,6 @@
], ],
"time": "2024-05-27T00:00:21+00:00" "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", "name": "symfony/cache",
"version": "v8.0.1", "version": "v8.0.1",
@ -6326,7 +6267,6 @@
"prefer-lowest": false, "prefer-lowest": false,
"platform": { "platform": {
"php": ">=8.4", "php": ">=8.4",
"ext-apcu": "*",
"ext-ctype": "*", "ext-ctype": "*",
"ext-iconv": "*" "ext-iconv": "*"
}, },

View File

@ -2,7 +2,7 @@ framework:
router: router:
# Configure how to generate URLs in non-HTTP contexts, such as CLI commands. # 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 # See https://symfony.com/doc/current/routing.html#generating-urls-in-commands
# default_uri: '%env(DEFAULT_URI)%' default_uri: '%env(DEFAULT_URI)%'
when@prod: when@prod:
framework: framework:

View File

@ -1,6 +1,11 @@
# yaml-language-server: $schema=../vendor/symfony/routing/Loader/schema/routing.schema.json # yaml-language-server: $schema=../vendor/symfony/routing/Loader/schema/routing.schema.json
get_calendar: # This file is the entry point to configure the routes of your app.
path: /calendar # Methods with the #[Route] attribute are automatically imported.
controller: App\Infrastructure\Controller\Calendar::getCalendar # See also https://symfony.com/doc/current/routing.html
methods: GET
# To list all registered routes, run the following command:
# bin/console debug:router
controllers:
resource: routing.controllers

View File

@ -1,30 +1,4 @@
services: 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: nhl-schedule:
image: nhl-schedule:dev image: nhl-schedule:dev
user: 1000:1000 user: 1000:1000
@ -34,6 +8,8 @@ services:
- ./:/var/www:rw,cached - ./:/var/www:rw,cached
- ./docker/php-fpm/ini/xdebug.ini:/usr/local/etc/php/conf.d/xdebug.ini - ./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 - ./docker/php-fpm/ini/local.ini:/usr/local/etc/php/conf.d/local.ini
# extra_hosts:
# - host.docker.internal:host-gateway
postgres: postgres:
image: postgres:18.1 image: postgres:18.1

View File

@ -1,28 +0,0 @@
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;
}
}

View File

@ -1,19 +0,0 @@
<?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,
) {
}
}

View File

@ -2,11 +2,11 @@
namespace App\Application\ReadModel; namespace App\Application\ReadModel;
readonly class Provider class Provider
{ {
public function __construct( public function __construct(
public string $id, public readonly string $id,
public string $name, public readonly string $name,
) { ) {
} }
} }

View File

@ -1,16 +0,0 @@
<?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,
) {
}
}

View File

@ -22,14 +22,10 @@ class FetchNHLMatch
public function __invoke(FetchNHLMatchRequest $request): void public function __invoke(FetchNHLMatchRequest $request): void
{ {
$provider = $provider = $this->entityManager->getRepository(Provider::class)->findOneBy(['id' => '885fe581-c4c3-45e7-a06c-29ece7d47fad']); // id should not be here
$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)); $matchs = $this->sportRadarEngine->getNHLSchedule($request->year, ENHLSeasonType::from($request->type));
$season = $season = $this->entityManager->getRepository(Season::class)->findOneBy(['providerSeasonId' => $matchs['season']['providerId']]);
$this->entityManager->getRepository(Season::class)->findOneBy(['providerSeasonId' => $matchs['season']['providerId']])
?? throw new \Exception('Season not found');
/** @var array<string, Team> $teams */ /** @var array<string, Team> $teams */
$teams = []; $teams = [];

View File

@ -4,11 +4,11 @@ declare(strict_types=1);
namespace App\Application\UseCase; namespace App\Application\UseCase;
readonly class FetchNHLMatchRequest class FetchNHLMatchRequest
{ {
public function __construct( public function __construct(
public int $year, public readonly int $year,
public string $type, public readonly string $type,
) { ) {
} }
} }

View File

@ -1,70 +0,0 @@
<?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()
;
}
}

View File

@ -1,12 +0,0 @@
<?php
namespace App\Application\UseCase;
readonly class GetGamesCalendarRequest
{
public function __construct(
/** @var string[] $teams */
public array $teams,
) {
}
}

View File

@ -1,27 +0,0 @@
<?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"',
]);
}
}

View File

@ -1,63 +0,0 @@
<?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();

View File

@ -1,43 +0,0 @@
<?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();

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
$builder = new Doctrine\ORM\Mapping\Builder\ClassMetadataBuilder($metadata); $builder = new Doctrine\ORM\Mapping\Builder\ClassMetadataBuilder($metadata);
$builder $builder
->setReadOnly()
->setTable('game') ->setTable('game')
; ;

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
$builder = new Doctrine\ORM\Mapping\Builder\ClassMetadataBuilder($metadata); $builder = new Doctrine\ORM\Mapping\Builder\ClassMetadataBuilder($metadata);
$builder $builder
->setReadOnly()
->setTable('league') ->setTable('league')
; ;

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
$builder = new Doctrine\ORM\Mapping\Builder\ClassMetadataBuilder($metadata); $builder = new Doctrine\ORM\Mapping\Builder\ClassMetadataBuilder($metadata);
$builder $builder
->setReadOnly()
->setTable('provider') ->setTable('provider')
; ;

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
$builder = new Doctrine\ORM\Mapping\Builder\ClassMetadataBuilder($metadata); $builder = new Doctrine\ORM\Mapping\Builder\ClassMetadataBuilder($metadata);
$builder $builder
->setReadOnly()
->setTable('season') ->setTable('season')
; ;

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
$builder = new Doctrine\ORM\Mapping\Builder\ClassMetadataBuilder($metadata); $builder = new Doctrine\ORM\Mapping\Builder\ClassMetadataBuilder($metadata);
$builder $builder
->setReadOnly()
->setTable('team') ->setTable('team')
; ;