multiple provider support

This commit is contained in:
Kacper Donat 2018-09-09 13:05:01 +02:00
parent 432d90fa1b
commit 3e89c654ec
25 changed files with 183 additions and 97 deletions

View File

@ -1,3 +1,4 @@
#index:
# path: /
# controller: App\Controller\DefaultController::index
api_v1:
resource: ../src/Controller/Api/v1
type: annotation
prefix: /{provider}/api/v1

View File

@ -4,7 +4,7 @@
# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
parameters:
locale: 'en'
locale: 'pl'
services:
# default configuration for services in *this* file
@ -15,6 +15,10 @@ services:
# fetching services directly from the container via $container->get() won't work.
# The best practice is to be explicit about your dependencies anyway.
_instanceof:
App\Provider\Provider:
tags: [ app.provider ]
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
@ -46,8 +50,8 @@ services:
proxy.config:
class: 'ProxyManager\Configuration'
calls:
- ['setGeneratorStrategy', ['@proxy.strategy']]
- ['setProxiesTargetDir', ['%kernel.cache_dir%/proxy']]
- ['setGeneratorStrategy', ['@proxy.strategy']]
- ['setProxiesTargetDir', ['%kernel.cache_dir%/proxy']]
ProxyManager\Configuration: '@proxy.config'
@ -60,3 +64,7 @@ services:
App\Service\Normalizer\:
resource: '../src/Service/Normalizer'
tags: [serializer.normalizer]
# other servces
App\Service\ProviderResolver:
arguments: [!tagged app.provider]

View File

@ -4,8 +4,6 @@
"author": "Kacper Donat <kadet1090@gmail.com>",
"license": "MIT",
"devDependencies": {
"webpack": "^4.17.0",
"webpack-cli": "^3.1.0",
"@fortawesome/fontawesome-svg-core": "^1.2.4",
"@fortawesome/pro-light-svg-icons": "^5.3.1",
"@fortawesome/pro-regular-svg-icons": "^5.3.1",
@ -30,7 +28,12 @@
"vue": "^2.5.17",
"vue-class-component": "^6.2.0",
"vue-property-decorator": "^7.0.0",
"webpack": "^4.17.0",
"webpack-cli": "^3.1.0",
"xmldom": "^0.1.27",
"xpath": "^0.0.27"
},
"dependencies": {
"mini-css-extract-plugin": "^0.4.2"
}
}

View File

@ -24,6 +24,10 @@
{{ departure.line.symbol }}
</div>
<div class="line__display">{{ departure.display }}</div>
<div class="line__perks flex-space-left">
<fa :icon="['fas', 'walking']" fixed-width v-if="departure.line.fast"/>
<fa :icon="['fal', 'moon']" fixed-width v-if="departure.line.night"/>
</div>
</div>
<div class="departure__time">

View File

@ -34,9 +34,11 @@
</div>
</div>
<div class="alert alert-warning" v-else-if="filter.length > 2">
<fa :icon="['far', 'exclamation-triangle']"></fa>
Nie znaleziono więcej przystanków, spełniających te kryteria.
</div>
<div class="alert alert-info" v-else>
<fa :icon="['far', 'search']"></fa>
Wprowadź zapytanie powyżej, aby wyszukać przystanek.
</div>
</div>

View File

@ -13,3 +13,19 @@
.flex-space-right {
margin-right: auto;
}
.alert {
border-width: 0;
background: transparent;
@each $color, $value in $theme-colors {
&.alert-#{$color} {
border-bottom: 2px solid theme-color-level($color, $alert-color-level);
transition: background-color ease 200ms;
&:hover {
background-color: rgba(theme-color-level($color, $alert-bg-level), .5);
}
}
}
}

View File

@ -1,4 +1,4 @@
$border-radius: 2px;
$border-radius: 0;
$border-radius-lg: $border-radius;
$border-radius-sm: $border-radius;

View File

@ -23,7 +23,6 @@ export class Departures extends Vue {
async update() {
const response = await fetch(urls.prepare(urls.departures, {
stop: this.stops.map(stop => stop.id),
provider: 'gdansk'
}));
if (response.ok) {

View File

@ -9,26 +9,11 @@ import stop = require('../../components/stop.html');
import { Prop, Watch } from "vue-property-decorator";
import { filter, map } from "../utils";
import { debounce, throttle } from "../decorators";
import { Departures } from "./departures";
import { debounce } from "../decorators";
@Component({ template: picker })
export class PickerComponent extends Vue {
protected stops?: Stop[] = [{
"id": 2001,
"name": "Dworzec Główny",
"description": null,
"location": [54.35544, 18.64565],
"variant": "01",
"onDemand": false
}, {
"id": 2002,
"name": "Dworzec Główny",
"description": null,
"location": [54.35541, 18.64548],
"variant": "02",
"onDemand": false
}];
protected stops?: Stop[] = [];
private remove(stop: Stop) {
this.stops = this.stops.filter(s => s != stop);
@ -70,10 +55,7 @@ export class FinderComponent extends Vue {
this.state = 'fetching';
const response = await fetch(urls.prepare(urls.stops.search, {
name: this.filter,
provider: 'gdansk'
}));
const response = await fetch(urls.prepare(urls.stops.search, { name: this.filter }));
if (response.ok) {
this.found = await response.json();

View File

@ -45,12 +45,14 @@ export function prepare(url: string, params: UrlParams = { }) {
return Object.keys(params).length > 0 ? `${url}?${query(params)}` : url;
}
const base = '/{provider}/api/v1';
export default {
departures: '/{provider}/departures',
departures: `${base}/departures`,
stops: {
all: '/{provider}/stops',
search: '/{provider}/stops/search',
get: '/{provider}/stop/{id}'
all: `${base}/stops`,
search: `${base}/stops/search`,
get: `${base}/stops/{id}`
},
prepare
prepare: (url: string, params: UrlParams = { }) => prepare(url, Object.assign({}, { provider: window['app'].provider }, params))
}

View File

@ -1,8 +1,9 @@
<?php
namespace App\Controller;
namespace App\Controller\Api\v1;
use App\Controller\Controller;
use App\Model\Departure;
use App\Provider\DepartureRepository;
use App\Provider\StopRepository;
@ -12,7 +13,7 @@ use Symfony\Component\Routing\Annotation\Route;
/**
* Class DeparturesController
*
* @Route("/{provider}/departures")
* @Route("/departures")
*/
class DeparturesController extends Controller
{

View File

@ -1,13 +1,14 @@
<?php
namespace App\Controller;
namespace App\Controller\Api\v1;
use App\Controller\Controller;
use App\Provider\MessageRepository;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
/**
* @Route("/{provider}/messages")
* @Route("/messages")
*/
class MessagesController extends Controller
{

View File

@ -1,8 +1,9 @@
<?php
namespace App\Controller;
namespace App\Controller\Api\v1;
use App\Controller\Controller;
use App\Provider\StopRepository;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
@ -11,7 +12,7 @@ use Symfony\Component\Routing\Annotation\Route;
* Class StopsController
*
* @package App\Controller
* @Route("/{provider}/stops")
* @Route("/stops")
*/
class StopsController extends Controller
{

View File

@ -1,17 +0,0 @@
<?php
namespace App\Controller;
use Symfony\Component\Routing\Annotation\Route;
class HomepageController extends Controller
{
/**
* @Route("/", name="home")
*/
public function homepage()
{
return $this->render('base.html.twig');
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace App\Controller;
use App\Provider\Provider;
use App\Service\ProviderResolver;
use Symfony\Component\Routing\Annotation\Route;
class MainController extends Controller
{
/**
* @Route("/", name="choose")
*/
public function choose(ProviderResolver $resolver)
{
return $this->render('choose.html.twig', ['providers' => $resolver->all()]);
}
/**
* @Route("/{provider}", name="app")
*/
public function app(Provider $provider)
{
return $this->render('app.html.twig', ['provider' => $provider]);
}
}

View File

@ -8,4 +8,7 @@ interface Provider
public function getLineRepository(): LineRepository;
public function getStopRepository(): StopRepository;
public function getMessageRepository(): MessageRepository;
public function getName();
public function getIdentifier();
}

View File

@ -27,6 +27,7 @@ class ZtmGdanskDataUpdateSubscriber implements EventSubscriberInterface
private $em;
private $ids;
private $logger;
private $provider;
/**
* ZtmGdanskDataUpdateSubscriber constructor.
@ -34,19 +35,24 @@ class ZtmGdanskDataUpdateSubscriber implements EventSubscriberInterface
* @param $provider
* @param $em
*/
public function __construct(EntityManagerInterface $em, IdUtils $ids, LoggerInterface $logger)
{
$this->em = $em;
$this->ids = $ids;
$this->logger = $logger;
public function __construct(
EntityManagerInterface $em,
IdUtils $ids,
LoggerInterface $logger,
ZtmGdanskProvider $provider
) {
$this->em = $em;
$this->ids = $ids;
$this->logger = $logger;
$this->provider = $provider;
}
public function update()
{
$provider = ProviderEntity::createFromArray([
'name' => 'ZTM Gdańsk',
'name' => $this->provider->getName(),
'class' => ZtmGdanskProvider::class,
'id' => 'ztm-gdansk',
'id' => $this->provider->getIdentifier(),
]);
$this->em->persist($provider);
@ -94,7 +100,7 @@ class ZtmGdanskDataUpdateSubscriber implements EventSubscriberInterface
OperatorEntity::class,
$this->ids->generate($provider, $line['agencyId'])
);
$type = [
$type = [
2 => LineModel::TYPE_TRAM,
5 => LineModel::TYPE_TROLLEYBUS,
];

View File

@ -16,13 +16,23 @@ class ZtmGdanskProvider implements Provider
private $stops;
private $messages;
public function getName()
{
return 'MZKZG Trójmiasto';
}
public function getIdentifier()
{
return 'trojmiasto';
}
public function __construct(
EntityManagerInterface $em,
GenericLineRepository $lines,
GenericStopRepository $stops,
ZtmGdanskMessageRepository $messages
) {
$provider = $em->getReference(ProviderEntity::class, 'ztm-gdansk');
$provider = $em->getReference(ProviderEntity::class, $this->getIdentifier());
$lines = $lines->withProvider($provider);
$stops = $stops->withProvider($provider);

View File

@ -64,6 +64,8 @@ final class EntityConverter
'symbol' => $entity->getSymbol(),
'type' => $entity->getType(),
'operator' => $convert($entity->getOperator()),
'night' => $entity->isNight(),
'fast' => $entity->isFast(),
'tracks' => $this->collection($entity->getTracks(), $convert),
]);
break;

View File

@ -3,30 +3,18 @@
namespace App\Service;
use App\Exception\NonExistentServiceException;
use App\Provider\Provider;
use App\Provider\ZtmGdanskProvider;
use Kadet\Functional\Transforms as t;
use Tightenco\Collect\Support\Collection;
class ProviderResolver
{
private const PROVIDER = [
'gdansk' => ZtmGdanskProvider::class
];
private $providers;
/**
* @var \Symfony\Component\DependencyInjection\ContainerInterface
*/
private $container;
/**
* ProviderResolver constructor.
*
* @param \Symfony\Component\DependencyInjection\ContainerInterface $container
*/
public function __construct(\Symfony\Component\DependencyInjection\ContainerInterface $container)
public function __construct($providers)
{
$this->container = $container;
$this->providers = collect($providers)->keyBy(t\property('identifier'));
}
/**\
@ -37,10 +25,17 @@ class ProviderResolver
*/
public function resolve(string $name): Provider
{
if (!array_key_exists($name, static::PROVIDER)) {
throw new NonExistentServiceException();
if (!$this->providers->has($name)) {
$message = sprintf("Provider '%s' doesn't exist, you can choose from: %s", $name, $this->providers->keys()->implode(', '));
throw new NonExistentServiceException($message);
}
return $this->container->get(static::PROVIDER[$name]);
return $this->providers->get($name);
}
/** @return Provider[] */
public function all(): Collection
{
return clone $this->providers;
}
}

13
templates/app.html.twig Normal file
View File

@ -0,0 +1,13 @@
{% extends 'base.html.twig' %}
{% block title "#{parent()} - #{provider.name}" %}
{% block body %}
<stop-picker></stop-picker>
{% endblock %}
{% block javascripts %}
<script>
window.app = {
provider: {{ provider.identifier|json_encode|raw }}
}
</script>
{% endblock %}

View File

@ -1,16 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{% block title %}Welcome!{% endblock %}</title>
{% block stylesheets %}{% endblock %}
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="main.css" />
<title>{% block title %}Czy dojadę?{% endblock %}</title>
</head>
<body>
{% block body %}
<main id="app" class="container">
<stop-picker></stop-picker>
</main>
{% endblock %}
<main role="main" class="container" id="app">
{% block body %}{% endblock %}
</main>
<script src="bundle.js"></script>
{% block javascripts %}{% endblock %}
</body>

View File

@ -0,0 +1,11 @@
{% extends 'base.html.twig' %}
{% block body %}
<div class="alert alert-primary">
<fa :icon="['fal', 'info-circle']"></fa>
Wybierz źródło danych
</div>
{% for provider in providers %}
<a href="{{ path('app', { provider: provider.identifier }) }}">{{ provider.name }}</a>
{% endfor %}
{% endblock %}

View File

@ -1,4 +1,6 @@
const path = require('path');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const config = {
entry: {
main: ['./resources/ts/app.ts'],
@ -24,7 +26,9 @@ const config = {
}]
},{
test: /\.s[ac]ss$/,
use: ["style-loader", "css-loader?sourceMap", "sass-loader?sourceMap"]
use: [{
loader: MiniCssExtractPlugin.loader,
}, "css-loader?sourceMap", "sass-loader?sourceMap"]
}, {
test: /\.css$/,
use: ["style-loader", "css-loader"]
@ -46,6 +50,9 @@ const config = {
use: 'raw-loader'
}]
},
plugins: [
new MiniCssExtractPlugin({ filename: '[name].css' })
],
};
module.exports = (env, argv) => {

View File

@ -1967,6 +1967,14 @@ mimic-fn@^1.0.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022"
mini-css-extract-plugin@^0.4.2:
version "0.4.2"
resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.4.2.tgz#b3ecc0d6b1bbe5ff14add42b946a7b200cf78651"
dependencies:
loader-utils "^1.1.0"
schema-utils "^1.0.0"
webpack-sources "^1.1.0"
minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7"