Compare commits

..

3 Commits

Author SHA1 Message Date
Kacper Donat
bcdc94384b add proxy support 2019-12-10 20:29:44 +01:00
Kacper Donat
2e2cf65a46 add test for one stop scenario 2019-12-10 17:51:15 +01:00
Kacper Donat
8cb1f9342c add some basic cypress test 2019-12-09 20:58:59 +01:00
421 changed files with 10558 additions and 20155 deletions

View File

@ -1 +0,0 @@
PHP_IDE_CONFIG=serverName=cojedzie

View File

@ -1,2 +0,0 @@
php_admin_flag[log_errors] = on
php_flag[display_errors] = off

View File

@ -1,31 +0,0 @@
server {
root /var/www/front/public/;
server_name cojedzie.localhost;
location /_profiler/ {
try_files $uri $uri/ @api;
}
location /bundles/ {
try_files $uri $uri/ @api;
}
location /api/ {
try_files $uri $uri/ @api;
}
location / {
try_files $uri $uri/ @frontend;
}
location @frontend {
proxy_pass http://frontend:3000;
proxy_intercept_errors on;
}
location @api {
proxy_pass http://api:8080;
proxy_intercept_errors on;
}
}

View File

@ -1,44 +0,0 @@
server {
root /var/www/front/public/;
server_name cojedzie.localhost;
location /api/ {
root /var/www/api/public/;
try_files $uri $uri/ index.php$is_args$args;
}
location /_profiler/ {
root /var/www/api/public/;
try_files $uri $uri/ index.php$is_args$args;
}
location /bundles/ {
root /var/www/api/public/;
try_files $uri $uri/;
}
location / {
try_files $uri $uri/ @frontend;
}
location @frontend {
proxy_pass http://frontend:3000;
proxy_intercept_errors on;
}
location ~ (.+).php(/|$) {
root /var/www/api/public/;
fastcgi_pass api:9000;
fastcgi_split_path_info ^(.+\.php)(/.*)$;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME /var/www/public/$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT /var/www/public/;
fastcgi_param APP_ENV "dev";
fastcgi_param DATABASE_URL "sqlite:///%kernel.project_dir%/var/app.db";
internal;
}
}

View File

@ -2,21 +2,16 @@
# Copy this file to .env file for development, create environment variables when deploying to production
# https://symfony.com/doc/current/best_practices/configuration.html#infrastructure-related-configuration
GOOGLE_ANALYTICS=
###> symfony/framework-bundle ###
APP_ENV=dev
APP_SECRET=494a9d1f8cc383f16075f4d5e54ae1a2
#TRUSTED_PROXIES=127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
#TRUSTED_HOSTS='^(localhost|example\.com)$'
APP_SECRET=1bdf86cdc78fba654e4f2c309c6bbdbd
###< symfony/framework-bundle ###
###> doctrine/doctrine-bundle ###
# Format described at https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url
# IMPORTANT: You MUST configure your server version, either here or in config/packages/doctrine.yaml
#
# Format described at http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url
# For an SQLite database, use: "sqlite:///%kernel.project_dir%/var/data.db"
# Configure your db driver and server_version in config/packages/doctrine.yaml
DATABASE_URL="sqlite:///%kernel.project_dir%/var/app.db"
# DATABASE_URL="mysql://db_user:db_password@127.0.0.1:3306/db_name?serverVersion=5.7"
###< doctrine/doctrine-bundle ###
###> symfony/messenger ###
APP_EVENT_QUEUE_DSN="doctrine://default"
###< symfony/messenger ###

16
.gitignore vendored
View File

@ -1 +1,17 @@
###> symfony/framework-bundle ###
/.env
/public/bundles/
/var/
/vendor/
###< symfony/framework-bundle ###
###> symfony/web-server-bundle ###
/.web-server-pid
###< symfony/web-server-bundle ###
/node_modules/
/.idea/
/public/*
!/public/index.php
!/public/manifest.json

71
CLA.md
View File

@ -1,71 +0,0 @@
# Co Jedzie Individual Contributor License Agreement
Adapted from http://www.apache.org/licenses/icla.txt © The Apache Software Foundation
Thank you for your interest in Co Jedzie (the **"Project"**) by Kacper Donat (the **"Author"**). In order to clarify the
intellectual property license granted with Contributions from any person or entity, the Author must have a Contributor
License Agreement ("CLA") on file that has been signed by each Contributor, indicating agreement to the license terms
below. This license is for your protection as a Contributor as well as the protection of the Author and its users; it
does not change your rights to use your own Contributions for any other purpose.
You accept and agree to the following terms and conditions for Your present and future Contributions submitted to the
Author. In return, the Author shall not use Your Contributions in a way that is contrary to the public benefit or
inconsistent with its bylaws in effect at the time of the Contribution. Except for the license granted herein to the
Author and recipients of software distributed by the Author, You reserve all right, title, and interest in and to Your
Contributions.
1. Definitions.
- **"You"** (or **"Your"**) shall mean the copyright owner or legal entity authorized by the copyright owner that is
making this Agreement with the Author. For legal entities, the entity making a Contribution and all other entities
that control, are controlled by, or are under common control with that entity are considered to be a single
Contributor. For the purposes of this definition, **"control"** means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or
more of the outstanding shares, or (iii) beneficial ownership of such entity.
- **"Contribution"** shall mean any original work of authorship, including any modifications or additions to an existing
work, that is intentionally submitted by You to the Author for inclusion in, or documentation of, any of the products
owned or managed by the Author (the **"Work"**). For the purposes of this definition, **"submitted"** means any form
of electronic, verbal, or written communication sent to the Author or its representatives, including but not limited
to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed
by, or on behalf of, the Author for the purpose of discussing and improving the Work, but excluding communication that
is conspicuously marked or otherwise designated in writing by You as **"Not a Contribution."**
2. Grant of Copyright License. Subject to the terms and conditions of this Agreement, You hereby grant to the Author and
to recipients of software distributed by the Author a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform,
sublicense, re-license, and distribute Your Contributions and such derivative works.
3. Grant of Patent License. Subject to the terms and conditions of this Agreement, You hereby grant to the Author and to
recipients of software distributed by the Author a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import,
and otherwise transfer the Work, where such license applies only to those patent claims licensable by You that are
necessarily infringed by Your Contribution(s) alone or by combination of Your Contribution(s) with the Work to which
such Contribution(s) was submitted. If any entity institutes patent litigation against You or any other entity (
including a cross-claim or counterclaim in a lawsuit) alleging that your Contribution, or the Work to which you have
contributed, constitutes direct or contributory patent infringement, then any patent licenses granted to that entity
under this Agreement for that Contribution or Work shall terminate as of the date such litigation is filed.
4. You represent that you are legally entitled to grant the above license. If your employer(s) has rights to
intellectual property that you create that includes your Contributions, you represent that you have received
permission to make Contributions on behalf of that employer, that your employer has waived such rights for your
Contributions to the Author, or that your employer has executed a separate Corporate CLA with the Author.
5. You represent that each of Your Contributions is Your original creation (see section 7 for submissions on behalf of
others). You represent that Your Contribution submissions include complete details of any third-party license or
other restriction (including, but not limited to, related patents and trademarks) of which you are personally aware
and which are associated with any part of Your Contributions.
6. You are not expected to provide support for Your Contributions, except to the extent You desire to provide support.
You may provide support for free, for a fee, or not at all. Unless required by applicable law or agreed to in
writing, You provide Your Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
express or implied, including, without limitation, any warranties or conditions of TITLE, NON- INFRINGEMENT,
MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE.
7. Should You wish to submit work that is not Your original creation, You may submit it to the Author separately from
any Contribution, identifying the complete details of its source and of any license or other restriction (including,
but not limited to, related patents, trademarks, and license agreements) of which you are personally aware, and
conspicuously marking the work as "Submitted on behalf of a third-party: [named here]".
8. You agree to notify the Author of any facts or circumstances of which you become aware that would make these
representations inaccurate in any respect.

View File

@ -1,38 +0,0 @@
# How to contribute?
Thanks for your interest in the project!
## I'd like to propose some feature / change...
Cool! Go ahead, [create an issue] and describe your proposal so anyone can see it. You can also vote on features that
you want the most.
## I've found a bug!
Well, less cool! Before creating an issue, please check if the bug remains after hard refreshing (usually `ctrl+F5`) the
application. If the answer is yes, or this is not the first encounter of it please [create an issue] and describe the
problem. If you can, please attach screenshots (especially if this is visual bug), and console logs (especially for
connection problems) - this will help to reproduce the problem.
## I've got some spare resources on my server...
Soon you will be able to help the project by hosting your own API node that will be available for clients to use.
More details to come soon.
## I want to contribute some code...
That's great! If you want to make changes to API (which is responsible for collecting and supplying applicaiton with
data) please check the [API contribution guidelines], if you are interested in UI side of the app please read the
[frontend contribution guidelines].
### Contributor License Agreement
Unfortunately due to this project nature and license I need you to sign [Contributor License Agreement] - the nice thing
is that it can be done with simple push of a button! **You still will have full copyright to your contribution** but
basically you consent that you are entitled to code you are submitting and also to allow me to license this project on
other terms if needed to, for example, local governments. If you don't want to sign - I understand - but I won't be able
to accept your contribution :(
[Contributor License Agreement]: ./CLA.md
[create an issue]: https://github.com/cojedzie/cojedzie/issues/new
[API contribution guidelines]: ./api/CONTRIBUTING.md
[frontend contribution guidelines]: ./front/CONTRIBUTING.md

View File

@ -1,41 +0,0 @@
“Commons Clause” License Condition v1.0
The Software is provided to you by the Licensor under the License, as defined
below, subject to the following condition.
Without limiting other conditions in the License, the grant of rights under the
License will not include, and the License does not grant to you, the right to
Sell the Software.
For purposes of the foregoing, “Sell” means practicing any or all of the rights
granted to you under the License to provide to third parties, for a fee or other
consideration (including without limitation fees for hosting or consulting/
support services related to the Software), a product or service whose value
derives, entirely or substantially, from the functionality of the Software. Any
license notice or attribution required by the License must also include this
Commons Clause License Condition notice.
Software: Co Jedzie
License: MIT
Licensor: Kacper Donat
Copyright 2020 Kacper Donat
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -1,32 +0,0 @@
# [Co Jedzie](https://cojedzie.pl)
![Co Jedzie logo](https://i.imgur.com/oMCMAxa.png)
![screenshot](https://i.imgur.com/kpcwgjb.png)
Co Jedzie is an app that allows you to quickly and easily check realtime departure times on public transport stops. It
aims to be the central hub for all public transport information you will need.
You can use the app at [cojedzie.pl](https://cojedzie.pl).
## Roadmap
Co Jedzie is in active development, roadmap of the project can be found on [trello]. This roadmap is regularly updated
and represents current state of the project. Feel free to take a look.
### Contributing to roadmap
If you have found a bug or want to propose some changes feel free to create an [issue] explaining your proposal or
problem. Issue management and discussion would be done on the github, but planning will be carried away to the [trello]
trello with linked issue.
## Contributing
Wan't to contribute? Nice! Please see [CONTRIBUTING.md]
## License
This project is [fair-code](https://faircode.io/) licensed under [MIT with Commons Clause](./LICENSE.md). Basically, Co
Jedzie is free and code is available to everyone, but it's not allowed to make money directly with it without
authors permission.
Note that data collected from available data sources is licensed by their respective owners, thus it may be
available under different terms than the project itself and may require additional permissions to use.
[trello]: https://trello.com/b/QXqDvmoG/co-jedzie
[issue]: https://github.com/cojedzie/cojedzie/issues/new
[CONTRIBUTING.md]: ./CONTRIBUTING.md

View File

@ -1,2 +0,0 @@
/vendor/
/var/

16
api/.gitignore vendored
View File

@ -1,16 +0,0 @@
###> symfony/framework-bundle ###
/.env.local
/.env.local.php
/.env.*.local
/config/secrets/prod/prod.decrypt.private.php
/public/bundles/
/var/
/vendor/
###< symfony/framework-bundle ###
/node_modules/
/public/*
!/public/index.php
###> baldinof/roadrunner-bundle ###
/bin/rr
###< baldinof/roadrunner-bundle ###

View File

@ -1,11 +0,0 @@
include:
- .rr.yaml
reload:
enabled: true
interval: 1s
patterns: [".php"]
services:
http:
dirs: ["."]
recursive: true

View File

@ -1,13 +0,0 @@
http:
address: "0.0.0.0:8080"
uploads:
forbid: [".php", ".exe", ".bat"]
workers:
command: "php bin/console baldinof:roadrunner:worker"
relay: "unix://var/roadrunner.sock"
static:
dir: "public"
forbid: [".php", ".htaccess"]

View File

@ -1,3 +0,0 @@
# Contributing guidelines
TBD

View File

@ -1,28 +0,0 @@
FROM php:7.4-fpm-alpine
COPY --from=mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/local/bin/
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
RUN install-php-extensions bcmath intl opcache zip sockets xdebug-^3.0;
RUN apk add git;
# XDebug
RUN echo "xdebug.mode=debug" >> $PHP_INI_DIR/conf.d/docker-php-ext-xdebug.ini && \
echo "xdebug.discover_client_host=On" >> $PHP_INI_DIR/conf.d/docker-php-ext-xdebug.ini;
# Blackfire
RUN version=$(php -r "echo PHP_MAJOR_VERSION.PHP_MINOR_VERSION;") \
&& curl -A "Docker" -o /tmp/blackfire-probe.tar.gz -D - -L -s https://blackfire.io/api/v1/releases/probe/php/linux/amd64/$version \
&& mkdir -p /tmp/blackfire \
&& tar zxpf /tmp/blackfire-probe.tar.gz -C /tmp/blackfire \
&& mv /tmp/blackfire/blackfire-*.so $(php -r "echo ini_get ('extension_dir');")/blackfire.so \
&& printf "extension=blackfire.so\nblackfire.agent_socket=tcp://blackfire:8707\n" > $PHP_INI_DIR/conf.d/blackfire.ini \
&& rm -rf /tmp/blackfire /tmp/blackfire-probe.tar.gz
# Timezone
RUN ln -snf /usr/share/zoneinfo/Europe/Warsaw /etc/localtime && \
echo "date.timezone = Europe/Warsaw" >> /usr/local/etc/php/conf.d/datetime.ini;
WORKDIR /var/www
EXPOSE 9001

View File

@ -1,3 +0,0 @@
#!/bin/sh
exec "$@"

View File

@ -1,5 +0,0 @@
#!/bin/sh
./bin/console doctrine:migrations:migrate --no-interaction
exec "$@"

7989
api/composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,23 +0,0 @@
<?php
use Symfony\Component\Dotenv\Dotenv;
require dirname(__DIR__).'/vendor/autoload.php';
if (!class_exists(Dotenv::class)) {
throw new LogicException('Please run "composer require symfony/dotenv" to load the ".env" files configuring the application.');
}
// Load cached env vars if the .env.local.php file exists
// Run "composer dump-env prod" to create it (requires symfony/flex >=1.2)
if (is_array($env = @include dirname(__DIR__).'/.env.local.php') && (!isset($env['APP_ENV']) || ($_SERVER['APP_ENV'] ?? $_ENV['APP_ENV'] ?? $env['APP_ENV']) === $env['APP_ENV'])) {
(new Dotenv(false))->populate($env);
} else {
// load all the .env files
(new Dotenv(false))->loadEnv(dirname(__DIR__).'/.env');
}
$_SERVER += $_ENV;
$_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = ($_SERVER['APP_ENV'] ?? $_ENV['APP_ENV'] ?? null) ?: 'dev';
$_SERVER['APP_DEBUG'] = $_SERVER['APP_DEBUG'] ?? $_ENV['APP_DEBUG'] ?? 'prod' !== $_SERVER['APP_ENV'];
$_SERVER['APP_DEBUG'] = $_ENV['APP_DEBUG'] = (int) $_SERVER['APP_DEBUG'] || filter_var($_SERVER['APP_DEBUG'], FILTER_VALIDATE_BOOLEAN) ? '1' : '0';

View File

@ -1,18 +0,0 @@
baldinof_road_runner:
# The kernel is preserved between requests. Change this to `true`
# if you want to reboot it, and use a fresh container on each request.
should_reboot_kernel: false
# Integrations are automatically detected, depending on installed bundle & current configuration
# See https://github.com/baldinof/roadrunner-bundle#integrations
default_integrations: true
# Allow to send prometheus metrics to the master RoadRunner process,
# via a `Spiral\RoadRunner\MetricsInterface` service.
# See https://github.com/baldinof/roadrunner-bundle#metrics
metrics_enabled: false
# You can use middlewares to manipulate PSR requests & responses.
# See https://github.com/baldinof/roadrunner-bundle#middlewares
# middlewares:
# - App\Middleware\YourMiddleware

View File

@ -1,19 +0,0 @@
framework:
cache:
# Unique name of your app: used to compute stable namespaces for cache keys.
#prefix_seed: your_vendor_name/app_name
# The "app" cache stores to the filesystem by default.
# The data in this cache should persist between deploys.
# Other options include:
# Redis
#app: cache.adapter.redis
#default_redis_provider: redis://localhost
# APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues)
#app: cache.adapter.apcu
# Namespaced pools use the above "app" backend by default
#pools:
#my.dedicated.cache: null

View File

@ -1,6 +0,0 @@
web_profiler:
toolbar: false
intercept_redirects: false
framework:
profiler: { only_exceptions: false }

View File

@ -1,3 +0,0 @@
doctrine_migrations:
migrations_paths:
'DoctrineMigrations': '%kernel.project_dir%/migrations'

View File

@ -1,6 +0,0 @@
framework:
secret: '%env(APP_SECRET)%'
csrf_protection: false
php_errors:
log: true

View File

@ -1,11 +0,0 @@
parameters:
env(APP_EVENT_QUEUE): "doctrine://default"
framework:
messenger:
transports:
main: '%env(resolve:APP_EVENT_QUEUE)%'
sync: 'sync://'
routing:
'App\Message\UpdateDataMessage': main

View File

@ -1,20 +0,0 @@
doctrine:
orm:
auto_generate_proxy_classes: false
metadata_cache_driver:
type: pool
pool: doctrine.system_cache_pool
query_cache_driver:
type: pool
pool: doctrine.system_cache_pool
result_cache_driver:
type: pool
pool: doctrine.result_cache_pool
framework:
cache:
pools:
doctrine.result_cache_pool:
adapter: cache.app
doctrine.system_cache_pool:
adapter: cache.system

View File

@ -1,2 +0,0 @@
twig:
strict_variables: true

View File

@ -1,6 +0,0 @@
web_profiler:
toolbar: false
intercept_redirects: false
framework:
profiler: { collect: false }

View File

@ -1,5 +0,0 @@
twig:
default_path: '%kernel.project_dir%/templates'
debug: '%kernel.debug%'
strict_variables: '%kernel.debug%'
exception_controller: null

View File

@ -1,9 +0,0 @@
<?php
if (file_exists(dirname(__DIR__).'/var/cache/prod/srcApp_KernelProdContainer.preload.php')) {
require dirname(__DIR__).'/var/cache/prod/srcApp_KernelProdContainer.preload.php';
}
if (file_exists(dirname(__DIR__).'/var/cache/prod/App_KernelProdContainer.preload.php')) {
require dirname(__DIR__).'/var/cache/prod/App_KernelProdContainer.preload.php';
}

View File

@ -1,16 +0,0 @@
api_v1:
resource: ../src/Controller/Api/v1
type: annotation
prefix: /api/v1/{provider}
api_v1_providers:
path: /api/v1/providers
methods: ["GET"]
defaults:
_controller: '\App\Controller\Api\v1\ProviderController::index'
api_v1_providers_one:
path: /api/v1/providers/{id}
methods: ["GET"]
defaults:
_controller: '\App\Controller\Api\v1\ProviderController::one'

View File

@ -1,3 +0,0 @@
_errors:
resource: '@FrameworkBundle/Resources/config/routing/errors.xml'
prefix: /_error

View File

@ -1,7 +0,0 @@
web_profiler_wdt:
resource: '@WebProfilerBundle/Resources/config/routing/wdt.xml'
prefix: /_wdt
web_profiler_profiler:
resource: '@WebProfilerBundle/Resources/config/routing/profiler.xml'
prefix: /_profiler

View File

@ -1,35 +0,0 @@
<?php declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20190111212909 extends AbstractMigration
{
public function up(Schema $schema) : void
{
// this up() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'sqlite', 'Migration can only be executed safely on \'sqlite\'.');
$this->addSql('CREATE TABLE trip_stop (stop_id VARCHAR(255) NOT NULL, trip_id VARCHAR(255) NOT NULL, sequence INTEGER NOT NULL, arrival DATETIME NOT NULL, departure DATETIME NOT NULL, PRIMARY KEY(stop_id, trip_id, sequence))');
$this->addSql('CREATE INDEX IDX_926E85DD3902063D ON trip_stop (stop_id)');
$this->addSql('CREATE INDEX IDX_926E85DDA5BC2E0E ON trip_stop (trip_id)');
$this->addSql('CREATE TABLE trip (id VARCHAR(255) NOT NULL, operator_id VARCHAR(255) DEFAULT NULL, track_id VARCHAR(255) DEFAULT NULL, provider_id VARCHAR(255) DEFAULT NULL, variant VARCHAR(255) DEFAULT NULL, note VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id))');
$this->addSql('CREATE INDEX IDX_7656F53B584598A3 ON trip (operator_id)');
$this->addSql('CREATE INDEX IDX_7656F53B5ED23C43 ON trip (track_id)');
$this->addSql('CREATE INDEX IDX_7656F53BA53A8AA ON trip (provider_id)');
}
public function down(Schema $schema) : void
{
// this down() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'sqlite', 'Migration can only be executed safely on \'sqlite\'.');
$this->addSql('DROP TABLE trip_stop');
$this->addSql('DROP TABLE trip');
}
}

View File

@ -1,109 +0,0 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20200103160747 extends AbstractMigration
{
public function getDescription() : string
{
return '';
}
public function up(Schema $schema) : void
{
// this up() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'sqlite', 'Migration can only be executed safely on \'sqlite\'.');
$this->addSql('DROP INDEX IDX_D6E3F8A6A53A8AA');
$this->addSql('DROP INDEX IDX_D6E3F8A64D7B7542');
$this->addSql('CREATE TEMPORARY TABLE __temp__track AS SELECT id, line_id, provider_id, variant, description FROM track');
$this->addSql('DROP TABLE track');
$this->addSql('CREATE TABLE track (id VARCHAR(255) NOT NULL COLLATE BINARY, line_id VARCHAR(255) DEFAULT NULL COLLATE BINARY, provider_id VARCHAR(255) DEFAULT NULL COLLATE BINARY, variant VARCHAR(16) DEFAULT NULL COLLATE BINARY, description VARCHAR(256) DEFAULT NULL COLLATE BINARY, PRIMARY KEY(id))');
$this->addSql('INSERT INTO track (id, line_id, provider_id, variant, description) SELECT id, line_id, provider_id, variant, description FROM __temp__track');
$this->addSql('DROP TABLE __temp__track');
$this->addSql('DROP INDEX IDX_D7A6A781A53A8AA');
$this->addSql('CREATE TEMPORARY TABLE __temp__operator AS SELECT id, provider_id, name, email, url, phone FROM operator');
$this->addSql('DROP TABLE operator');
$this->addSql('CREATE TABLE operator (id VARCHAR(255) NOT NULL COLLATE BINARY, provider_id VARCHAR(255) DEFAULT NULL COLLATE BINARY, name VARCHAR(255) NOT NULL COLLATE BINARY, email VARCHAR(255) DEFAULT NULL COLLATE BINARY, url VARCHAR(255) DEFAULT NULL COLLATE BINARY, phone VARCHAR(255) DEFAULT NULL COLLATE BINARY, PRIMARY KEY(id))');
$this->addSql('INSERT INTO operator (id, provider_id, name, email, url, phone) SELECT id, provider_id, name, email, url, phone FROM __temp__operator');
$this->addSql('DROP TABLE __temp__operator');
$this->addSql('DROP INDEX IDX_B95616B6A53A8AA');
$this->addSql('CREATE TEMPORARY TABLE __temp__stop AS SELECT id, provider_id, name, description, variant, latitude, longitude, on_demand FROM stop');
$this->addSql('DROP TABLE stop');
$this->addSql('CREATE TABLE stop (id VARCHAR(255) NOT NULL COLLATE BINARY, provider_id VARCHAR(255) DEFAULT NULL COLLATE BINARY, name VARCHAR(255) NOT NULL COLLATE BINARY, description VARCHAR(255) DEFAULT NULL COLLATE BINARY, variant VARCHAR(255) DEFAULT NULL COLLATE BINARY, latitude DOUBLE PRECISION DEFAULT NULL, longitude DOUBLE PRECISION DEFAULT NULL, on_demand BOOLEAN NOT NULL, PRIMARY KEY(id))');
$this->addSql('INSERT INTO stop (id, provider_id, name, description, variant, latitude, longitude, on_demand) SELECT id, provider_id, name, description, variant, latitude, longitude, on_demand FROM __temp__stop');
$this->addSql('DROP TABLE __temp__stop');
$this->addSql('DROP INDEX IDX_D114B4F6A53A8AA');
$this->addSql('DROP INDEX IDX_D114B4F6584598A3');
$this->addSql('CREATE TEMPORARY TABLE __temp__line AS SELECT id, operator_id, provider_id, symbol, type, fast, night FROM line');
$this->addSql('DROP TABLE line');
$this->addSql('CREATE TABLE line (id VARCHAR(255) NOT NULL COLLATE BINARY, operator_id VARCHAR(255) DEFAULT NULL COLLATE BINARY, provider_id VARCHAR(255) DEFAULT NULL COLLATE BINARY, symbol VARCHAR(16) NOT NULL COLLATE BINARY, type VARCHAR(20) NOT NULL COLLATE BINARY, fast BOOLEAN NOT NULL, night BOOLEAN NOT NULL, PRIMARY KEY(id))');
$this->addSql('INSERT INTO line (id, operator_id, provider_id, symbol, type, fast, night) SELECT id, operator_id, provider_id, symbol, type, fast, night FROM __temp__line');
$this->addSql('DROP TABLE __temp__line');
$this->addSql('CREATE TEMPORARY TABLE __temp__provider AS SELECT id, name, class, update_date FROM provider');
$this->addSql('DROP TABLE provider');
$this->addSql('CREATE TABLE provider (id VARCHAR(255) NOT NULL COLLATE BINARY, name VARCHAR(255) NOT NULL COLLATE BINARY, class VARCHAR(255) NOT NULL COLLATE BINARY, update_date DATETIME NOT NULL, PRIMARY KEY(id))');
$this->addSql('INSERT INTO provider (id, name, class, update_date) SELECT id, name, class, update_date FROM __temp__provider');
$this->addSql('DROP TABLE __temp__provider');
$this->addSql('DROP INDEX IDX_24003EB35ED23C43');
$this->addSql('DROP INDEX IDX_24003EB33902063D');
$this->addSql('CREATE TEMPORARY TABLE __temp__track_stop AS SELECT stop_id, track_id, sequence FROM track_stop');
$this->addSql('DROP TABLE track_stop');
$this->addSql('CREATE TABLE track_stop (stop_id VARCHAR(255) NOT NULL COLLATE BINARY, track_id VARCHAR(255) NOT NULL COLLATE BINARY, sequence INTEGER NOT NULL, PRIMARY KEY(stop_id, track_id, sequence))');
$this->addSql('INSERT INTO track_stop (stop_id, track_id, sequence) SELECT stop_id, track_id, sequence FROM __temp__track_stop');
$this->addSql('DROP TABLE __temp__track_stop');
}
public function down(Schema $schema) : void
{
// this down() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'sqlite', 'Migration can only be executed safely on \'sqlite\'.');
$this->addSql('CREATE TEMPORARY TABLE __temp__line AS SELECT id, symbol, type, fast, night, operator_id, provider_id FROM line');
$this->addSql('DROP TABLE line');
$this->addSql('CREATE TABLE line (id VARCHAR(255) NOT NULL, symbol VARCHAR(16) NOT NULL, type VARCHAR(20) NOT NULL, fast BOOLEAN NOT NULL, night BOOLEAN NOT NULL, operator_id VARCHAR(255) DEFAULT NULL, provider_id VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id))');
$this->addSql('INSERT INTO line (id, symbol, type, fast, night, operator_id, provider_id) SELECT id, symbol, type, fast, night, operator_id, provider_id FROM __temp__line');
$this->addSql('DROP TABLE __temp__line');
$this->addSql('CREATE INDEX IDX_D114B4F6A53A8AA ON line (provider_id)');
$this->addSql('CREATE INDEX IDX_D114B4F6584598A3 ON line (operator_id)');
$this->addSql('CREATE TEMPORARY TABLE __temp__operator AS SELECT id, name, email, url, phone, provider_id FROM operator');
$this->addSql('DROP TABLE operator');
$this->addSql('CREATE TABLE operator (id VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, email VARCHAR(255) DEFAULT NULL, url VARCHAR(255) DEFAULT NULL, phone VARCHAR(255) DEFAULT NULL, provider_id VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id))');
$this->addSql('INSERT INTO operator (id, name, email, url, phone, provider_id) SELECT id, name, email, url, phone, provider_id FROM __temp__operator');
$this->addSql('DROP TABLE __temp__operator');
$this->addSql('CREATE INDEX IDX_D7A6A781A53A8AA ON operator (provider_id)');
$this->addSql('CREATE TEMPORARY TABLE __temp__provider AS SELECT id, name, class, update_date FROM provider');
$this->addSql('DROP TABLE provider');
$this->addSql('CREATE TABLE provider (id VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, class VARCHAR(255) NOT NULL, update_date DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, PRIMARY KEY(id))');
$this->addSql('INSERT INTO provider (id, name, class, update_date) SELECT id, name, class, update_date FROM __temp__provider');
$this->addSql('DROP TABLE __temp__provider');
$this->addSql('CREATE TEMPORARY TABLE __temp__stop AS SELECT id, name, description, variant, latitude, longitude, on_demand, provider_id FROM stop');
$this->addSql('DROP TABLE stop');
$this->addSql('CREATE TABLE stop (id VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, description VARCHAR(255) DEFAULT NULL, variant VARCHAR(255) DEFAULT NULL, latitude DOUBLE PRECISION DEFAULT NULL, longitude DOUBLE PRECISION DEFAULT NULL, on_demand BOOLEAN NOT NULL, provider_id VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id))');
$this->addSql('INSERT INTO stop (id, name, description, variant, latitude, longitude, on_demand, provider_id) SELECT id, name, description, variant, latitude, longitude, on_demand, provider_id FROM __temp__stop');
$this->addSql('DROP TABLE __temp__stop');
$this->addSql('CREATE INDEX IDX_B95616B6A53A8AA ON stop (provider_id)');
$this->addSql('CREATE TEMPORARY TABLE __temp__track AS SELECT id, variant, description, line_id, provider_id FROM track');
$this->addSql('DROP TABLE track');
$this->addSql('CREATE TABLE track (id VARCHAR(255) NOT NULL, variant VARCHAR(16) DEFAULT NULL, description VARCHAR(256) DEFAULT NULL, line_id VARCHAR(255) DEFAULT NULL, provider_id VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id))');
$this->addSql('INSERT INTO track (id, variant, description, line_id, provider_id) SELECT id, variant, description, line_id, provider_id FROM __temp__track');
$this->addSql('DROP TABLE __temp__track');
$this->addSql('CREATE INDEX IDX_D6E3F8A6A53A8AA ON track (provider_id)');
$this->addSql('CREATE INDEX IDX_D6E3F8A64D7B7542 ON track (line_id)');
$this->addSql('CREATE TEMPORARY TABLE __temp__track_stop AS SELECT sequence, stop_id, track_id FROM track_stop');
$this->addSql('DROP TABLE track_stop');
$this->addSql('CREATE TABLE track_stop (stop_id VARCHAR(255) NOT NULL, track_id VARCHAR(255) NOT NULL, sequence INTEGER NOT NULL, PRIMARY KEY(stop_id, track_id))');
$this->addSql('INSERT INTO track_stop (sequence, stop_id, track_id) SELECT sequence, stop_id, track_id FROM __temp__track_stop');
$this->addSql('DROP TABLE __temp__track_stop');
$this->addSql('CREATE INDEX IDX_24003EB35ED23C43 ON track_stop (track_id)');
$this->addSql('CREATE INDEX IDX_24003EB33902063D ON track_stop (stop_id)');
}
}

View File

@ -1,63 +0,0 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20200103170517 extends AbstractMigration
{
public function getDescription() : string
{
return '';
}
public function up(Schema $schema) : void
{
// this up() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'sqlite', 'Migration can only be executed safely on \'sqlite\'.');
$this->addSql('DROP INDEX IDX_926E85DDA5BC2E0E');
$this->addSql('DROP INDEX IDX_926E85DD3902063D');
$this->addSql('CREATE TEMPORARY TABLE __temp__trip_stop AS SELECT stop_id, trip_id, sequence, arrival, departure FROM trip_stop');
$this->addSql('DROP TABLE trip_stop');
$this->addSql('CREATE TABLE trip_stop (stop_id VARCHAR(255) NOT NULL COLLATE BINARY, trip_id VARCHAR(255) NOT NULL COLLATE BINARY, sequence INTEGER NOT NULL, arrival DATETIME NOT NULL, departure DATETIME NOT NULL, PRIMARY KEY(stop_id, trip_id, sequence))');
$this->addSql('INSERT INTO trip_stop (stop_id, trip_id, sequence, arrival, departure) SELECT stop_id, trip_id, sequence, arrival, departure FROM __temp__trip_stop');
$this->addSql('DROP TABLE __temp__trip_stop');
$this->addSql('DROP INDEX IDX_7656F53BA53A8AA');
$this->addSql('DROP INDEX IDX_7656F53B5ED23C43');
$this->addSql('DROP INDEX IDX_7656F53B584598A3');
$this->addSql('CREATE TEMPORARY TABLE __temp__trip AS SELECT id, operator_id, track_id, provider_id, variant, note FROM trip');
$this->addSql('DROP TABLE trip');
$this->addSql('CREATE TABLE trip (id VARCHAR(255) NOT NULL COLLATE BINARY, operator_id VARCHAR(255) DEFAULT NULL COLLATE BINARY, track_id VARCHAR(255) DEFAULT NULL COLLATE BINARY, provider_id VARCHAR(255) DEFAULT NULL COLLATE BINARY, variant VARCHAR(255) DEFAULT NULL COLLATE BINARY, note VARCHAR(255) DEFAULT NULL COLLATE BINARY, PRIMARY KEY(id))');
$this->addSql('INSERT INTO trip (id, operator_id, track_id, provider_id, variant, note) SELECT id, operator_id, track_id, provider_id, variant, note FROM __temp__trip');
$this->addSql('DROP TABLE __temp__trip');
}
public function down(Schema $schema) : void
{
// this down() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'sqlite', 'Migration can only be executed safely on \'sqlite\'.');
$this->addSql('CREATE TEMPORARY TABLE __temp__trip AS SELECT id, variant, note, operator_id, track_id, provider_id FROM trip');
$this->addSql('DROP TABLE trip');
$this->addSql('CREATE TABLE trip (id VARCHAR(255) NOT NULL, variant VARCHAR(255) DEFAULT NULL, note VARCHAR(255) DEFAULT NULL, operator_id VARCHAR(255) DEFAULT NULL, track_id VARCHAR(255) DEFAULT NULL, provider_id VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id))');
$this->addSql('INSERT INTO trip (id, variant, note, operator_id, track_id, provider_id) SELECT id, variant, note, operator_id, track_id, provider_id FROM __temp__trip');
$this->addSql('DROP TABLE __temp__trip');
$this->addSql('CREATE INDEX IDX_7656F53BA53A8AA ON trip (provider_id)');
$this->addSql('CREATE INDEX IDX_7656F53B5ED23C43 ON trip (track_id)');
$this->addSql('CREATE INDEX IDX_7656F53B584598A3 ON trip (operator_id)');
$this->addSql('CREATE TEMPORARY TABLE __temp__trip_stop AS SELECT sequence, stop_id, trip_id, arrival, departure FROM trip_stop');
$this->addSql('DROP TABLE trip_stop');
$this->addSql('CREATE TABLE trip_stop (sequence INTEGER NOT NULL, stop_id VARCHAR(255) NOT NULL, trip_id VARCHAR(255) NOT NULL, arrival DATETIME NOT NULL, departure DATETIME NOT NULL, PRIMARY KEY(stop_id, trip_id, sequence))');
$this->addSql('INSERT INTO trip_stop (sequence, stop_id, trip_id, arrival, departure) SELECT sequence, stop_id, trip_id, arrival, departure FROM __temp__trip_stop');
$this->addSql('DROP TABLE __temp__trip_stop');
$this->addSql('CREATE INDEX IDX_926E85DDA5BC2E0E ON trip_stop (trip_id)');
$this->addSql('CREATE INDEX IDX_926E85DD3902063D ON trip_stop (stop_id)');
}
}

View File

@ -1,45 +0,0 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20200131151757 extends AbstractMigration
{
public function getDescription() : string
{
return '';
}
public function up(Schema $schema) : void
{
// this up() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'sqlite', 'Migration can only be executed safely on \'sqlite\'.');
$this->addSql('CREATE TEMPORARY TABLE __temp__stop AS SELECT id, provider_id, name, description, variant, latitude, longitude, on_demand FROM stop');
$this->addSql('DROP TABLE stop');
$this->addSql('CREATE TABLE stop (id VARCHAR(255) NOT NULL COLLATE BINARY, provider_id VARCHAR(255) DEFAULT NULL COLLATE BINARY, name VARCHAR(255) NOT NULL COLLATE BINARY, description VARCHAR(255) DEFAULT NULL COLLATE BINARY, variant VARCHAR(255) DEFAULT NULL COLLATE BINARY, latitude DOUBLE PRECISION DEFAULT NULL, longitude DOUBLE PRECISION DEFAULT NULL, on_demand BOOLEAN NOT NULL, group_name VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id))');
$this->addSql('INSERT INTO stop (id, provider_id, name, description, variant, latitude, longitude, on_demand) SELECT id, provider_id, name, description, variant, latitude, longitude, on_demand FROM __temp__stop');
$this->addSql('DROP TABLE __temp__stop');
$this->addSql('CREATE INDEX group_idx ON stop (group_name)');
}
public function down(Schema $schema) : void
{
// this down() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'sqlite', 'Migration can only be executed safely on \'sqlite\'.');
$this->addSql('DROP INDEX group_idx');
$this->addSql('CREATE TEMPORARY TABLE __temp__stop AS SELECT id, name, description, variant, latitude, longitude, on_demand, provider_id FROM stop');
$this->addSql('DROP TABLE stop');
$this->addSql('CREATE TABLE stop (id VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, description VARCHAR(255) DEFAULT NULL, variant VARCHAR(255) DEFAULT NULL, latitude DOUBLE PRECISION DEFAULT NULL, longitude DOUBLE PRECISION DEFAULT NULL, on_demand BOOLEAN NOT NULL, provider_id VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id))');
$this->addSql('INSERT INTO stop (id, name, description, variant, latitude, longitude, on_demand, provider_id) SELECT id, name, description, variant, latitude, longitude, on_demand, provider_id FROM __temp__stop');
$this->addSql('DROP TABLE __temp__stop');
}
}

View File

@ -1,57 +0,0 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20200206183956 extends AbstractMigration
{
public function getDescription() : string
{
return '';
}
public function up(Schema $schema) : void
{
// this up() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'sqlite', 'Migration can only be executed safely on \'sqlite\'.');
$this->addSql('CREATE TEMPORARY TABLE __temp__track AS SELECT id, line_id, provider_id, variant, description FROM track');
$this->addSql('DROP TABLE track');
$this->addSql('CREATE TABLE track (id VARCHAR(255) NOT NULL COLLATE BINARY, line_id VARCHAR(255) DEFAULT NULL COLLATE BINARY, provider_id VARCHAR(255) DEFAULT NULL COLLATE BINARY, variant VARCHAR(16) DEFAULT NULL COLLATE BINARY, description VARCHAR(256) DEFAULT NULL COLLATE BINARY, final_id INTEGER DEFAULT NULL, PRIMARY KEY(id))');
$this->addSql('INSERT INTO track (id, line_id, provider_id, variant, description) SELECT id, line_id, provider_id, variant, description FROM __temp__track');
$this->addSql('DROP TABLE __temp__track');
$this->addSql('CREATE UNIQUE INDEX UNIQ_D6E3F8A613D41B2D ON track (final_id)');
$this->addSql('CREATE TEMPORARY TABLE __temp__track_stop AS SELECT stop_id, track_id, sequence FROM track_stop');
$this->addSql('DROP TABLE track_stop');
$this->addSql('CREATE TABLE track_stop (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, sequence INTEGER NOT NULL, stop_id VARCHAR(255) DEFAULT NULL, track_id VARCHAR(255) DEFAULT NULL)');
$this->addSql('INSERT INTO track_stop (stop_id, track_id, sequence) SELECT stop_id, track_id, sequence FROM __temp__track_stop');
$this->addSql('DROP TABLE __temp__track_stop');
$this->addSql('CREATE UNIQUE INDEX stop_in_track_idx ON track_stop (stop_id, track_id, sequence)');
}
public function down(Schema $schema) : void
{
// this down() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'sqlite', 'Migration can only be executed safely on \'sqlite\'.');
$this->addSql('DROP INDEX UNIQ_D6E3F8A613D41B2D');
$this->addSql('CREATE TEMPORARY TABLE __temp__track AS SELECT id, variant, description, line_id, provider_id FROM track');
$this->addSql('DROP TABLE track');
$this->addSql('CREATE TABLE track (id VARCHAR(255) NOT NULL, variant VARCHAR(16) DEFAULT NULL, description VARCHAR(256) DEFAULT NULL, line_id VARCHAR(255) DEFAULT NULL, provider_id VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id))');
$this->addSql('INSERT INTO track (id, variant, description, line_id, provider_id) SELECT id, variant, description, line_id, provider_id FROM __temp__track');
$this->addSql('DROP TABLE __temp__track');
$this->addSql('DROP INDEX stop_in_track_idx');
$this->addSql('CREATE TEMPORARY TABLE __temp__track_stop AS SELECT sequence, stop_id, track_id FROM track_stop');
$this->addSql('DROP TABLE track_stop');
$this->addSql('CREATE TABLE track_stop (sequence INTEGER NOT NULL, stop_id VARCHAR(255) NOT NULL COLLATE BINARY, track_id VARCHAR(255) NOT NULL COLLATE BINARY, PRIMARY KEY(stop_id, track_id, sequence))');
$this->addSql('INSERT INTO track_stop (sequence, stop_id, track_id) SELECT sequence, stop_id, track_id FROM __temp__track_stop');
$this->addSql('DROP TABLE __temp__track_stop');
}
}

View File

@ -1,43 +0,0 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20200314112552 extends AbstractMigration
{
public function getDescription() : string
{
return '';
}
public function up(Schema $schema) : void
{
// this up() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'sqlite', 'Migration can only be executed safely on \'sqlite\'.');
$this->addSql('CREATE TEMPORARY TABLE __temp__trip_stop AS SELECT stop_id, trip_id, sequence, arrival, departure FROM trip_stop');
$this->addSql('DROP TABLE trip_stop');
$this->addSql('CREATE TABLE trip_stop (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, sequence INTEGER NOT NULL, arrival DATETIME NOT NULL, departure DATETIME NOT NULL, stop_id VARCHAR(255) DEFAULT NULL, trip_id VARCHAR(255) DEFAULT NULL)');
$this->addSql('INSERT INTO trip_stop (stop_id, trip_id, sequence, arrival, departure) SELECT stop_id, trip_id, sequence, arrival, departure FROM __temp__trip_stop');
$this->addSql('DROP TABLE __temp__trip_stop');
}
public function down(Schema $schema) : void
{
// this down() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'sqlite', 'Migration can only be executed safely on \'sqlite\'.');
$this->addSql('CREATE TEMPORARY TABLE __temp__trip_stop AS SELECT sequence, arrival, departure, stop_id, trip_id FROM trip_stop');
$this->addSql('DROP TABLE trip_stop');
$this->addSql('CREATE TABLE trip_stop (sequence INTEGER NOT NULL, stop_id VARCHAR(255) NOT NULL COLLATE BINARY, trip_id VARCHAR(255) NOT NULL COLLATE BINARY, arrival DATETIME NOT NULL, departure DATETIME NOT NULL, PRIMARY KEY(stop_id, trip_id, sequence))');
$this->addSql('INSERT INTO trip_stop (sequence, arrival, departure, stop_id, trip_id) SELECT sequence, arrival, departure, stop_id, trip_id FROM __temp__trip_stop');
$this->addSql('DROP TABLE __temp__trip_stop');
}
}

View File

@ -1,27 +0,0 @@
<?php
use App\Kernel;
use Symfony\Component\ErrorHandler\Debug;
use Symfony\Component\HttpFoundation\Request;
require dirname(__DIR__).'/config/bootstrap.php';
if ($_SERVER['APP_DEBUG']) {
umask(0000);
Debug::enable();
}
if ($trustedProxies = $_SERVER['TRUSTED_PROXIES'] ?? false) {
Request::setTrustedProxies(explode(',', $trustedProxies), Request::HEADER_X_FORWARDED_FOR | Request::HEADER_X_FORWARDED_PORT | Request::HEADER_X_FORWARDED_PROTO);
}
if ($trustedHosts = $_SERVER['TRUSTED_HOSTS'] ?? false) {
Request::setTrustedHosts([$trustedHosts]);
}
$kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
$request = Request::createFromGlobals();
$response = $kernel->handle($request);
$response->send();
$kernel->terminate($request, $response);

View File

@ -1,29 +0,0 @@
FROM cojedzie/api:latest-rr
COPY --from=mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/local/bin/
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
RUN install-php-extensions xdebug-^3.0;
RUN apk add git;
# XDebug
RUN echo "xdebug.mode=debug" >> $PHP_INI_DIR/conf.d/docker-php-ext-xdebug.ini && \
echo "xdebug.client_host=172.17.0.1" >> $PHP_INI_DIR/conf.d/docker-php-ext-xdebug.ini && \
echo "xdebug.start_with_request=On" >> $PHP_INI_DIR/conf.d/docker-php-ext-xdebug.ini;
# Blackfire
RUN version=$(php -r "echo PHP_MAJOR_VERSION.PHP_MINOR_VERSION;") \
&& curl -A "Docker" -o /tmp/blackfire-probe.tar.gz -D - -L -s https://blackfire.io/api/v1/releases/probe/php/linux/amd64/$version \
&& mkdir -p /tmp/blackfire \
&& tar zxpf /tmp/blackfire-probe.tar.gz -C /tmp/blackfire \
&& mv /tmp/blackfire/blackfire-*.so $(php -r "echo ini_get ('extension_dir');")/blackfire.so \
&& printf "extension=blackfire.so\nblackfire.agent_socket=tcp://blackfire:8707\n" > $PHP_INI_DIR/conf.d/blackfire.ini \
&& rm -rf /tmp/blackfire /tmp/blackfire-probe.tar.gz
# Timezone
RUN ln -snf /usr/share/zoneinfo/Europe/Warsaw /etc/localtime && \
echo "date.timezone = Europe/Warsaw" >> /usr/local/etc/php/conf.d/datetime.ini;
WORKDIR /var/www
EXPOSE 9001

View File

@ -1,48 +0,0 @@
<?php
namespace App\Command;
use App\Message\UpdateDataMessage;
use App\Service\DataUpdater;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Messenger\MessageBusInterface;
class UpdateCommand extends Command
{
/** @var DataUpdater */
private $updater;
/** @var MessageBusInterface */
private $bus;
public function __construct(DataUpdater $updater, MessageBusInterface $bus)
{
parent::__construct('app:update');
$this->updater = $updater;
$this->bus = $bus;
}
protected function configure()
{
$this->addOption(
'async', 'a',
InputOption::VALUE_NONE,
'Run in worker process via message queue.'
);
}
protected function execute(InputInterface $input, OutputInterface $output)
{
if ($input->getOption('async')) {
$this->bus->dispatch(new UpdateDataMessage());
$output->writeln("Update request sent to message queue.");
} else {
$this->updater->update($output);
}
return Command::SUCCESS;
}
}

View File

@ -1,34 +0,0 @@
<?php
namespace App\Controller\Api\v1;
use App\Controller\Controller;
use App\Exception\NonExistentServiceException;
use App\Service\Converter;
use App\Service\ProviderResolver;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use function Kadet\Functional\ref;
class ProviderController extends Controller
{
public function index(ProviderResolver $resolver, Converter $converter)
{
$providers = $resolver
->all()
->map(ref([$converter, 'convert']))
->values()
->toArray()
;
return $this->json($providers);
}
public function one(ProviderResolver $resolver, Converter $converter, $id)
{
try {
$provider = $resolver->resolve($id);
return $this->json($converter->convert($provider));
} catch (NonExistentServiceException $exception) {
throw new NotFoundHttpException($exception->getMessage());
}
}
}

View File

@ -1,98 +0,0 @@
<?php
namespace App\Controller\Api\v1;
use App\Controller\Controller;
use App\Model\Line;
use App\Model\Stop;
use App\Model\Track;
use App\Modifier\IdFilter;
use App\Modifier\RelatedFilter;
use App\Provider\TrackRepository;
use Swagger\Annotations as SWG;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use function App\Functions\encapsulate;
use function Kadet\Functional\ref;
/**
* @Route("/tracks")
* @SWG\Tag(name="Tracks")
*/
class TracksController extends Controller
{
/**
* @SWG\Response(
* response=200,
* description="Returns all tracks for specific provider, e.g. ZTM Gdańsk.",
* )
* @Route("/", methods={"GET"})
*/
public function index(Request $request, TrackRepository $repository)
{
$modifiers = $this->getModifiersFromRequest($request);
return $this->json($repository->all(...$modifiers));
}
/**
* @Route("/stops", methods={"GET"})
* @Route("/{track}/stops", methods={"GET"})
*
* @SWG\Tag(name="Tracks")
*
* @SWG\Response(response=200, description="Stops related to specified query.")
*/
public function stops(Request $request, TrackRepository $repository)
{
$modifiers = $this->getStopsModifiersFromRequest($request);
return $this->json($repository->stops(...$modifiers));
}
private function getModifiersFromRequest(Request $request)
{
if ($request->query->has('stop')) {
$stop = encapsulate($request->query->get('stop'));
$stop = collect($stop)->map([Stop::class, 'reference']);
yield new RelatedFilter($stop, Stop::class);
}
if ($request->query->has('line')) {
$line = encapsulate($request->query->get('line'));
$line = collect($line)->map([Line::class, 'reference']);
yield new RelatedFilter($line, Line::class);
}
if ($request->query->has('id')) {
$id = encapsulate($request->query->get('id'));
yield new IdFilter($id);
}
}
private function getStopsModifiersFromRequest(Request $request)
{
if ($request->query->has('stop')) {
$stop = encapsulate($request->query->get('stop'));
$stop = collect($stop)->map(ref([Stop::class, 'reference']));
yield new RelatedFilter($stop);
}
if ($request->query->has('track') || $request->attributes->has('track')) {
$track = $request->get('track');
$track = Track::reference($track);
yield new RelatedFilter($track);
}
if ($request->query->has('id')) {
$id = encapsulate($request->query->get('id'));
yield new IdFilter($id);
}
}
}

View File

@ -1,27 +0,0 @@
<?php
namespace App\Controller\Api\v1;
use App\Controller\Controller;
use App\Model\Trip;
use App\Modifier\IdFilter;
use App\Modifier\With;
use App\Provider\TripRepository;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
/**
* @Route("/trips")
*/
class TripController extends Controller
{
/**
* @Route("/{id}", methods={"GET"})
*/
public function one($id, TripRepository $repository)
{
$trip = $repository->first(new IdFilter($id), new With('schedule'));
return $this->json($trip, Response::HTTP_OK, [], $this->serializerContextFactory->create(Trip::class));
}
}

View File

@ -1,27 +0,0 @@
<?php
namespace App\Controller;
use App\Service\SerializerContextFactory;
use JMS\Serializer\SerializerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
abstract class Controller extends AbstractController
{
protected $serializer;
protected $serializerContextFactory;
public function __construct(SerializerInterface $serializer, SerializerContextFactory $serializerContextFactory)
{
$this->serializer = $serializer;
$this->serializerContextFactory = $serializerContextFactory;
}
protected function json($data, int $status = 200, array $headers = [], $context = null): JsonResponse
{
return new JsonResponse($this->serializer->serialize($data, "json", $context), $status, $headers, true);
}
}

View File

@ -1,47 +0,0 @@
<?php
namespace App\Doctrine;
use Carbon\Carbon;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\ConversionException;
use Doctrine\DBAL\Types\DateTimeType;
class CarbonDateTimeType extends DateTimeType
{
public function convertToDatabaseValue($value, AbstractPlatform $platform)
{
if ($value instanceof \DateTimeInterface) {
$value = Carbon::instance($value);
}
if ($value instanceof Carbon) {
$value = $value->tz('UTC');
}
return parent::convertToDatabaseValue($value, $platform);
}
public function convertToPHPValue($value, AbstractPlatform $platform)
{
if (null === $value || $value instanceof Carbon) {
return $value;
}
$converted = Carbon::createFromFormat(
$platform->getDateTimeFormatString(),
$value,
'UTC'
);
if (!$converted) {
throw ConversionException::conversionFailedFormat(
$value,
$this->getName(),
$platform->getDateTimeFormatString()
);
}
return $converted;
}
}

View File

@ -1,116 +0,0 @@
<?php
namespace App\Entity;
use App\Model\Fillable;
use App\Model\FillTrait;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table("trip")
*/
class TripEntity implements Entity, Fillable
{
use ReferableEntityTrait, ProviderReferenceTrait, FillTrait;
/**
* Operator of the trip
*
* @var OperatorEntity
* @ORM\ManyToOne(targetEntity=OperatorEntity::class)
*/
private $operator;
/**
* Track of the trip
*
* @var TrackEntity
* @ORM\ManyToOne(targetEntity=TrackEntity::class)
*/
private $track;
/**
* Variant of track, for example some alternative route
*
* @var ?string
* @ORM\Column("variant", nullable=true)
*/
private $variant;
/**
* Description of variant
*
* @var ?string
* @ORM\Column("note", nullable=true)
*/
private $note;
/**
* @var Collection<TripStopEntity>
*
* @ORM\OneToMany(targetEntity=TripStopEntity::class, fetch="EXTRA_LAZY", mappedBy="trip", cascade={"persist"})
* @ORM\OrderBy({"order": "ASC"})
*/
private $stops;
/**
* TripEntity constructor.
*/
public function __construct()
{
$this->setStops([]);
}
public function getOperator(): OperatorEntity
{
return $this->operator;
}
public function setOperator(OperatorEntity $operator): void
{
$this->operator = $operator;
}
public function getTrack(): TrackEntity
{
return $this->track;
}
public function setTrack(TrackEntity $track): void
{
$this->track = $track;
}
public function getVariant(): ?string
{
return $this->variant;
}
public function setVariant(?string $variant): void
{
$this->variant = $variant;
}
public function getNote(): ?string
{
return $this->note;
}
public function setNote(?string $note): void
{
$this->note = $note;
}
public function getStops(): Collection
{
return $this->stops;
}
public function setStops(iterable $stops): void
{
$this->stops = new ArrayCollection(is_array($stops) ? $stops : iterator_to_array($stops));
}
}

View File

@ -1,116 +0,0 @@
<?php
namespace App\Entity;
use App\Model\Fillable;
use App\Model\FillTrait;
use App\Model\Referable;
use App\Model\Trip;
use App\Service\IdUtils;
use Carbon\Carbon;
use Doctrine\ORM\Mapping as ORM;
use JMS\Serializer\Tests\Fixtures\Discriminator\Car;
/**
* @ORM\Entity
* @ORM\Table("trip_stop")
*/
class TripStopEntity implements Fillable, Referable
{
use FillTrait, ReferableEntityTrait;
/**
* Identifier for stop coming from provider
*
* @ORM\Column(type="integer")
* @ORM\Id
* @ORM\GeneratedValue
*/
private $id;
/**
* @var StopEntity
* @ORM\ManyToOne(targetEntity=StopEntity::class, fetch="EAGER")
*/
private $stop;
/**
* @var TripEntity
* @ORM\ManyToOne(targetEntity=TripEntity::class, fetch="EAGER", inversedBy="stops")
*/
private $trip;
/**
* Order in trip
* @var int
*
* @ORM\Column(name="sequence", type="integer")
*/
private $order;
/**
* Arrival time
* @var Carbon
*
* @ORM\Column(type="datetime", nullable=false)
*/
private $arrival;
/**
* Departure time
* @var Carbon
*
* @ORM\Column(type="datetime", nullable=false)
*/
private $departure;
public function getStop()
{
return $this->stop;
}
public function setStop($stop): void
{
$this->stop = $stop;
}
public function getTrip()
{
return $this->trip;
}
public function setTrip($trip): void
{
$this->trip = $trip;
}
public function getOrder(): int
{
return $this->order;
}
public function setOrder(int $order): void
{
$this->order = $order;
}
public function getArrival(): Carbon
{
return $this->arrival;
}
public function setArrival(Carbon $arrival): void
{
$this->arrival = $arrival;
}
public function getDeparture(): Carbon
{
return Carbon::instance($this->departure)->tz('UTC');
}
public function setDeparture(Carbon $departure): void
{
$this->departure = $departure;
}
}

View File

@ -1,25 +0,0 @@
<?php
namespace App\Event;
use App\Service\DataUpdater;
use Symfony\Component\Console\Output\NullOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Contracts\EventDispatcher\Event;
class DataUpdateEvent extends Event
{
const NAME = DataUpdater::UPDATE_EVENT;
private $output;
public function __construct(?OutputInterface $output = null)
{
$this->output = $output ?? new NullOutput();
}
public function getOutput(): OutputInterface
{
return $this->output;
}
}

View File

@ -1,34 +0,0 @@
<?php
namespace App\Event;
use App\Event\HandleModifierEvent;
use App\Modifier\Modifier;
use App\Provider\Repository;
use Doctrine\ORM\QueryBuilder;
class HandleDatabaseModifierEvent extends HandleModifierEvent
{
private $builder;
public function __construct(
Modifier $modifier,
Repository $repository,
QueryBuilder $builder,
array $meta = []
) {
parent::__construct($modifier, $repository, $meta);
$this->builder = $builder;
}
public function getBuilder(): QueryBuilder
{
return $this->builder;
}
public function replaceBuilder(QueryBuilder $builder): void
{
$this->builder = $builder;
}
}

View File

@ -1,35 +0,0 @@
<?php
namespace App\Event;
use App\Modifier\Modifier;
use App\Provider\Repository;
class HandleModifierEvent
{
private $repository;
private $modifier;
private $meta = [];
public function __construct(Modifier $modifier, Repository $repository, array $meta = [])
{
$this->repository = $repository;
$this->modifier = $modifier;
$this->meta = $meta;
}
public function getModifier(): Modifier
{
return $this->modifier;
}
public function getRepository()
{
return $this->repository;
}
public function getMeta(): array
{
return $this->meta;
}
}

View File

@ -1,27 +0,0 @@
<?php
namespace App\Event;
use App\Modifier\Modifier;
use App\Provider\Repository;
class PostProcessEvent extends HandleModifierEvent
{
private $data;
public function __construct($data, Modifier $modifier, Repository $repository, array $meta = [])
{
parent::__construct($modifier, $repository, $meta);
$this->data = $data;
}
public function getData()
{
return $this->data;
}
public function setData($data): void
{
$this->data = $data;
}
}

View File

@ -1,13 +0,0 @@
<?php
namespace App\Exception;
class InvalidArgumentException extends \InvalidArgumentException
{
public static function invalidType($parameter, $value, array $expected = [])
{
return new static(
sprintf('Expected %s to be of type: %s. %s given.', $parameter, implode(', ', $expected), gettype($value))
);
}
}

View File

@ -1,9 +0,0 @@
<?php
namespace App\Exception;
class NonExistentServiceException extends \LogicException
{
}

View File

@ -1,7 +0,0 @@
<?php
namespace App\Exception;
class NotSupportedException extends \LogicException
{
}

View File

@ -1,13 +0,0 @@
<?php
namespace App\Exception;
use App\Modifier\Modifier;
class UnsupportedModifierException extends \LogicException
{
public static function createFromModifier(Modifier $modifier)
{
return new static(sprintf("Modifier %s is not supported.", get_class($modifier)));
}
}

View File

@ -1,3 +0,0 @@
<?php
require_once __DIR__ . '/helpers.php';

View File

@ -1,63 +0,0 @@
<?php
namespace App\Handler\Database;
use App\Event\HandleDatabaseModifierEvent;
use App\Event\HandleModifierEvent;
use App\Handler\ModifierHandler;
use App\Model\ScheduledStop;
use App\Model\Stop;
use App\Modifier\FieldFilter;
use function App\Functions\encapsulate;
class FieldFilterDatabaseHandler implements ModifierHandler
{
protected $mapping = [
Stop::class => [
'name' => 'name',
],
ScheduledStop::class => [
'departure' => 'departure',
'arrival' => 'arrival',
]
];
public function process(HandleModifierEvent $event)
{
if (!$event instanceof HandleDatabaseModifierEvent) {
return;
}
/** @var FieldFilter $modifier */
$modifier = $event->getModifier();
$builder = $event->getBuilder();
$alias = $event->getMeta()['alias'];
$field = $this->mapFieldName($event->getMeta()['type'], $modifier->getField());
$operator = $modifier->getOperator();
$value = $modifier->getValue();
$parameter = sprintf(":%s_%s", $alias, $field);
if ($operator === 'in' || $operator === 'not in') {
$parameter = "($parameter)";
$value = encapsulate($value);
}
$builder
->andWhere(sprintf("%s.%s %s %s", $alias, $field, $operator, $parameter))
->setParameter($parameter, $value)
;
}
protected function mapFieldName(string $class, string $field)
{
if (!isset($this->mapping[$class][$field])) {
throw new \InvalidArgumentException(
sprintf("Unable to map field %s of %s into entity field.", $field, $class)
);
}
return $this->mapping[$class][$field];
}
}

View File

@ -1,103 +0,0 @@
<?php
namespace App\Handler\Database;
use App\Event\HandleDatabaseModifierEvent;
use App\Event\HandleModifierEvent;
use App\Handler\ModifierHandler;
use App\Model\ScheduledStop;
use App\Model\Track;
use App\Model\TrackStop;
use App\Model\Trip;
use App\Modifier\RelatedFilter;
use App\Service\EntityReferenceFactory;
use App\Service\IdUtils;
use Doctrine\ORM\EntityManagerInterface;
use function Kadet\Functional\Transforms\property;
class GenericWithDatabaseHandler implements ModifierHandler
{
protected $mapping = [
Track::class => [
'line' => 'line',
'stops' => 'stopsInTrack',
],
Trip::class => [
'schedule' => 'stops.stop',
],
TrackStop::class => [
'track' => 'track',
],
ScheduledStop::class => [
'trip' => 'trip',
'track' => 'trip.track',
'destination' => 'trip.track.final',
],
];
private $em;
private $id;
private $references;
public function __construct(
EntityManagerInterface $em,
IdUtils $idUtils,
EntityReferenceFactory $references
) {
$this->em = $em;
$this->id = $idUtils;
$this->references = $references;
}
public function process(HandleModifierEvent $event)
{
if (!$event instanceof HandleDatabaseModifierEvent) {
return;
}
/** @var RelatedFilter $modifier */
$modifier = $event->getModifier();
$builder = $event->getBuilder();
$alias = $event->getMeta()['alias'];
$type = $event->getMeta()['type'];
if (!array_key_exists($modifier->getRelationship(), $this->mapping[$type])) {
throw new \InvalidArgumentException(
sprintf("Relationship %s is not supported for .", $type)
);
}
$relationship = $this->mapping[$type][$modifier->getRelationship()];
foreach ($this->getRelationships($relationship, $alias) as [$relationshipPath, $relationshipAlias]) {
$selected = collect($builder->getDQLPart('select'))->flatMap(property('parts'));
if ($selected->contains($relationshipAlias)) {
continue;
}
$builder
->join($relationshipPath, $relationshipAlias)
->addSelect($relationshipAlias);
}
}
/**
* @inheritDoc
*/
public static function getSubscribedServices()
{
return [
TrackByStopDatabaseHandler::class,
];
}
private function getRelationships($relationship, $alias)
{
$relationships = explode('.', $relationship);
foreach ($relationships as $current) {
yield [sprintf("%s.%s", $alias, $current), $alias = sprintf('%s_%s', $alias, $current)];
}
}
}

View File

@ -1,44 +0,0 @@
<?php
namespace App\Handler\Database;
use App\Handler\ModifierHandler;
use App\Modifier\IdFilter;
use App\Event\HandleDatabaseModifierEvent;
use App\Event\HandleModifierEvent;
use App\Service\IdUtils;
use function Kadet\Functional\apply;
class IdFilterDatabaseHandler implements ModifierHandler
{
/**
* @var IdUtils
*/
private $id;
public function __construct(IdUtils $id)
{
$this->id = $id;
}
public function process(HandleModifierEvent $event)
{
if (!$event instanceof HandleDatabaseModifierEvent) {
return;
}
/** @var IdFilter $modifier */
$modifier = $event->getModifier();
$builder = $event->getBuilder();
$alias = $event->getMeta()['alias'];
$provider = $event->getMeta()['provider'];
$id = $modifier->getId();
$mapper = apply([$this->id, 'generate'], $provider);
$builder
->andWhere($modifier->isMultiple() ? "{$alias} in (:id)" : "{$alias} = :id")
->setParameter(':id', $modifier->isMultiple() ? array_map($mapper, $id) : $mapper($id));
;
}
}

View File

@ -1,27 +0,0 @@
<?php
namespace App\Handler\Database;
use App\Event\HandleDatabaseModifierEvent;
use App\Event\HandleModifierEvent;
use App\Handler\ModifierHandler;
use App\Modifier\Limit;
class LimitDatabaseHandler implements ModifierHandler
{
public function process(HandleModifierEvent $event)
{
if (!$event instanceof HandleDatabaseModifierEvent) {
return;
}
/** @var Limit $modifier */
$modifier = $event->getModifier();
$builder = $event->getBuilder();
$builder
->setFirstResult($modifier->getOffset())
->setMaxResults($modifier->getCount())
;
}
}

View File

@ -1,107 +0,0 @@
<?php
namespace App\Handler\Database;
use App\Event\HandleDatabaseModifierEvent;
use App\Event\HandleModifierEvent;
use App\Handler\ModifierHandler;
use App\Model\Line;
use App\Model\ScheduledStop;
use App\Model\Stop;
use App\Model\Track;
use App\Model\TrackStop;
use App\Model\Trip;
use App\Modifier\RelatedFilter;
use App\Service\IdUtils;
use App\Service\EntityReferenceFactory;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Container\ContainerInterface;
use Symfony\Contracts\Service\ServiceSubscriberInterface;
class RelatedFilterDatabaseGenericHandler implements ModifierHandler, ServiceSubscriberInterface
{
protected $mapping = [
Track::class => [
Line::class => 'line',
Stop::class => TrackByStopDatabaseHandler::class,
],
TrackStop::class => [
Stop::class => 'stop',
Track::class => 'track',
],
ScheduledStop::class => [
Stop::class => 'stop',
Trip::class => 'trip',
],
];
private $em;
private $inner;
private $id;
private $references;
public function __construct(
ContainerInterface $inner,
EntityManagerInterface $em,
IdUtils $idUtils,
EntityReferenceFactory $references
) {
$this->inner = $inner;
$this->em = $em;
$this->id = $idUtils;
$this->references = $references;
}
public function process(HandleModifierEvent $event)
{
if (!$event instanceof HandleDatabaseModifierEvent) {
return;
}
/** @var RelatedFilter $modifier */
$modifier = $event->getModifier();
$builder = $event->getBuilder();
$alias = $event->getMeta()['alias'];
$type = $event->getMeta()['type'];
if (!array_key_exists($type, $this->mapping)) {
throw new \InvalidArgumentException(
sprintf("Relationship filtering for %s is not supported.", $type)
);
}
if (!array_key_exists($modifier->getRelationship(), $this->mapping[$type])) {
throw new \InvalidArgumentException(
sprintf("Relationship %s is not supported for %s.", $modifier->getRelationship(), $type)
);
}
$relationship = $this->mapping[$type][$modifier->getRelationship()];
if ($this->inner->has($relationship)) {
/** @var ModifierHandler $inner */
$inner = $this->inner->get($relationship);
$inner->process($event);
return;
}
$parameter = sprintf(":%s_%s", $alias, $relationship);
$reference = $this->references->create($modifier->getRelated(), $event->getMeta()['provider']);
$builder
->join(sprintf('%s.%s', $alias, $relationship), $relationship)
->andWhere(sprintf($modifier->isMultiple() ? "%s in (%s)" : "%s = %s", $relationship, $parameter))
->setParameter($parameter, $reference);
}
/**
* @inheritDoc
*/
public static function getSubscribedServices()
{
return [
TrackByStopDatabaseHandler::class,
];
}
}

View File

@ -1,45 +0,0 @@
<?php
namespace App\Handler\Database;
use App\Entity\TrackStopEntity;
use App\Event\HandleDatabaseModifierEvent;
use App\Event\HandleModifierEvent;
use App\Handler\ModifierHandler;
use App\Modifier\RelatedFilter;
use App\Service\EntityReferenceFactory;
class TrackByStopDatabaseHandler implements ModifierHandler
{
private $references;
public function __construct(EntityReferenceFactory $references)
{
$this->references = $references;
}
public function process(HandleModifierEvent $event)
{
if (!$event instanceof HandleDatabaseModifierEvent) {
return;
}
/** @var RelatedFilter $modifier */
$modifier = $event->getModifier();
$builder = $event->getBuilder();
$alias = $event->getMeta()['alias'];
$relationship = 'stopsInTrack';
$parameter = sprintf(":%s_%s", $alias, $relationship);
$reference = $this->references->create($modifier->getRelated(), $event->getMeta()['provider']);
$condition = $modifier->isMultiple() ? 'stop_in_track.stop IN (%s)' : 'stop_in_track.stop = %s';
$builder
->join(sprintf("%s.%s", $alias, $relationship), 'stop_in_track')
->andWhere(sprintf($condition, $parameter))
->setParameter($parameter, $reference)
;
}
}

View File

@ -1,82 +0,0 @@
<?php
namespace App\Handler\Database;
use App\Entity\TrackEntity;
use App\Event\PostProcessEvent;
use App\Handler\PostProcessingHandler;
use App\Model\Destination;
use App\Model\Stop;
use App\Service\CacheableConverter;
use App\Service\Converter;
use App\Service\IdUtils;
use Doctrine\ORM\EntityManagerInterface;
use Illuminate\Support\Collection;
use Kadet\Functional as f;
use Kadet\Functional\Transforms as t;
class WithDestinationsDatabaseHandler implements PostProcessingHandler
{
private $em;
private $converter;
private $id;
public function __construct(EntityManagerInterface $entityManager, Converter $converter, IdUtils $id)
{
$this->em = $entityManager;
$this->converter = $converter;
$this->id = $id;
if ($this->converter instanceof CacheableConverter) {
$this->converter = clone $this->converter;
$this->converter->reset();
}
}
public function postProcess(PostProcessEvent $event)
{
$provider = $event->getMeta()['provider'];
$stops = $event
->getData()
->map(t\property('id'))
->map(f\apply([$this->id, 'generate'], $provider))
->all();
$destinations = collect($this->em->createQueryBuilder()
->select('t', 'tl', 'f', 'fs', 'ts')
->from(TrackEntity::class, 't')
->join('t.stopsInTrack', 'ts')
->join('t.line', 'tl')
->where('ts.stop IN (:stops)')
->join('t.final', 'f')
->join('f.stop', 'fs')
->getQuery()
->execute(['stops' => $stops]))
->reduce(function ($grouped, TrackEntity $track) {
foreach ($track->getStopsInTrack()->map(t\property('stop'))->map(t\property('id')) as $stop) {
$grouped[$stop] = ($grouped[$stop] ?? collect())->add($track);
}
return $grouped;
}, collect())
->map(function (Collection $tracks) {
return $tracks
->groupBy(function (TrackEntity $track) {
return $track->getFinal()->getStop()->getId();
})->map(function (Collection $tracks, $id) {
return Destination::createFromArray([
'stop' => $this->converter->convert($tracks->first()->getFinal()->getStop()),
'lines' => $tracks
->map(t\property('line'))
->unique(t\property('id'))
->map(f\ref([$this->converter, 'convert']))
->values(),
]);
})->values();
});
$event->getData()->each(function (Stop $stop) use ($provider, $destinations) {
$stop->setDestinations($destinations[$this->id->generate($provider, $stop->getId())]);
});
}
}

View File

@ -1,10 +0,0 @@
<?php
namespace App\Handler;
use App\Event\HandleModifierEvent;
interface ModifierHandler
{
public function process(HandleModifierEvent $event);
}

View File

@ -1,10 +0,0 @@
<?php
namespace App\Handler;
use App\Event\PostProcessEvent;
interface PostProcessingHandler
{
public function postProcess(PostProcessEvent $event);
}

View File

@ -1,7 +0,0 @@
<?php
namespace App\Message;
final class UpdateDataMessage
{
}

View File

@ -1,34 +0,0 @@
<?php
namespace App\MessageHandler;
use App\Message\UpdateDataMessage;
use App\Output\LoggerOutput;
use App\Service\DataUpdater;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
final class UpdateDataMessageHandler implements MessageHandlerInterface, LoggerAwareInterface
{
use LoggerAwareTrait;
/** @var DataUpdater */
private $updater;
public function __construct(DataUpdater $updater)
{
$this->updater = $updater;
}
public function __invoke(UpdateDataMessage $message)
{
try {
$this->updater->update(new LoggerOutput($this->logger));
} catch (\Exception $exception) {
$this->logger->critical($exception->getMessage(), [
'backtrace' => $exception->getTraceAsString()
]);
}
}
}

View File

@ -1,52 +0,0 @@
<?php
namespace App\Model;
use Illuminate\Support\Collection;
use JMS\Serializer\Annotation as Serializer;
use Nelmio\ApiDocBundle\Annotation\Model;
use Swagger\Annotations as SWG;
class Destination implements Fillable
{
use FillTrait;
/**
* Stop associated with destination.
* @Serializer\Type(Stop::class)
* @var Stop
*/
private $stop;
/**
* @Serializer\Type("Collection")
* @SWG\Property(type="array", @SWG\Items(ref=@Model(type=Line::class, groups={"Default"})))
* @var Line[]|Collection<Line>
*/
private $lines;
public function __construct()
{
$this->lines = collect();
}
public function getStop(): Stop
{
return $this->stop;
}
public function setStop(Stop $stop): void
{
$this->stop = $stop;
}
public function getLines(): Collection
{
return $this->lines;
}
public function setLines(iterable $lines): void
{
$this->lines = collect($lines);
}
}

View File

@ -1,55 +0,0 @@
<?php
namespace App\Model;
use JMS\Serializer\Annotation as Serializer;
use Swagger\Annotations as SWG;
class Location
{
/**
* Locations longitude.
* @Serializer\Type("float")
* @Serializer\SerializedName("lng")
*/
private $longitude;
/**
* Locations latitude.
* @Serializer\Type("float")
* @Serializer\SerializedName("lat")
* @SWG\Property()
*/
private $latitude;
public function __construct(float $longitude = 0.0, float $latitude = 0.0)
{
$this->set($longitude, $latitude);
}
public function getLongitude()
{
return $this->longitude;
}
public function setLongitude(float $longitude): void
{
$this->longitude = $longitude;
}
public function getLatitude()
{
return $this->latitude;
}
public function setLatitude(float $latitude): void
{
$this->latitude = $latitude;
}
public function set(float $longitude, float $latitude)
{
$this->setLongitude($longitude);
$this->setLatitude($latitude);
}
}

View File

@ -1,117 +0,0 @@
<?php
namespace App\Model;
use Carbon\Carbon;
use JMS\Serializer\Annotation as Serializer;
use Swagger\Annotations as SWG;
class Provider implements Fillable, Referable
{
use FillTrait;
/**
* Short identifier of provider, ex. "trojmiasto"
* @SWG\Property(example="trojmiasto")
* @Serializer\Type("string")
* @var string
*/
private $id;
/**
* Full name of the provider, ex. "MZKZG Trójmiasto"
* @SWG\Property(example="MZKZG Trójmiasto")
* @Serializer\Type("string")
* @var string
*/
private $name;
/**
* Short name of the provider for easier identification, ex. "Trójmiasto" or "Warszawa"
* @SWG\Property(example="Trójmiasto")
* @Serializer\Type("string")
* @var string
*/
private $shortName;
/**
* Attribution to be presented for this provider, can contain HTML tags.
* @SWG\Property(example="Copyright by XYZ inc.")
* @Serializer\Type("string")
* @var string|null
*/
private $attribution;
/**
* Time when data was last synchronized with this provider.
* @Serializer\Type("Carbon")
* @var Carbon|null
*/
private $lastUpdate;
/**
* Location of provider centre of interest.
* @var Location
*/
private $location;
public function getId(): string
{
return $this->id;
}
public function setId(string $id): void
{
$this->id = $id;
}
public function getName(): string
{
return $this->name;
}
public function setName(string $name): void
{
$this->name = $name;
}
public function getShortName(): string
{
return $this->shortName;
}
public function setShortName(string $shortName): void
{
$this->shortName = $shortName;
}
public function getAttribution(): ?string
{
return $this->attribution;
}
public function setAttribution(?string $attribution): void
{
$this->attribution = $attribution;
}
public function getLastUpdate(): ?Carbon
{
return $this->lastUpdate;
}
public function setLastUpdate(?Carbon $lastUpdate): void
{
$this->lastUpdate = $lastUpdate;
}
public function getLocation(): Location
{
return $this->location;
}
public function setLocation(Location $location): void
{
$this->location = $location;
}
}

View File

@ -1,56 +0,0 @@
<?php
namespace App\Model;
use Carbon\Carbon;
class ScheduledStop extends TrackStop
{
/**
* Arrival time.
* @var Carbon
*/
private $arrival;
/**
* Departure time.
* @var Carbon
*/
private $departure;
/**
* Exact trip that this scheduled stop is part of.
* @var Trip|null
*/
private $trip;
public function getArrival(): Carbon
{
return $this->arrival;
}
public function setArrival(Carbon $arrival): void
{
$this->arrival = $arrival;
}
public function getDeparture(): Carbon
{
return $this->departure;
}
public function setDeparture(Carbon $departure): void
{
$this->departure = $departure;
}
public function getTrip(): ?Trip
{
return $this->trip;
}
public function setTrip(?Trip $trip): void
{
$this->trip = $trip;
}
}

View File

@ -1,58 +0,0 @@
<?php
namespace App\Model;
use JMS\Serializer\Annotation as Serializer;
class TrackStop implements Fillable
{
use FillTrait;
/**
* Order in trip
* @var int
*/
private $order;
/**
* Stop (as a place) related to that scheduled bus stop
* @var Stop
*/
private $stop;
/**
* Track that this stop is part of.
* @var Track|null
*/
private $track;
public function getStop()
{
return $this->stop;
}
public function setStop($stop): void
{
$this->stop = $stop;
}
public function getOrder(): int
{
return $this->order;
}
public function setOrder(int $order): void
{
$this->order = $order;
}
public function getTrack(): ?Track
{
return $this->track;
}
public function setTrack(?Track $track): void
{
$this->track = $track;
}
}

View File

@ -1,106 +0,0 @@
<?php
namespace App\Model;
use App\Serialization\SerializeAs;
use Illuminate\Support\Collection;
use JMS\Serializer\Annotation as Serializer;
class Trip implements Referable, Fillable
{
use ReferableTrait, FillTrait;
/**
* Line variant describing trip, for example 'a'
* @Serializer\Type("string")
* @var string|null
*
*/
private $variant;
/**
* Trip description
* @var string|null
* @Serializer\Type("string")
*/
private $description;
/**
* Line reference
* @var ?Track
* @Serializer\Type("App\Model\Track")
* @SerializeAs({"Default": "Identity"})
*/
private $track;
/**
* Stops in track
* @Serializer\Type("Collection<App\Model\ScheduledStop>")
* @var Collection<ScheduledStop>
*/
private $schedule;
/**
* Destination stop of this trip
* @var Stop|null
*/
private $destination;
/**
* Track constructor.
*/
public function __construct()
{
$this->setSchedule([]);
}
public function getVariant(): ?string
{
return $this->variant;
}
public function setVariant(?string $variant): void
{
$this->variant = $variant;
}
public function getDescription(): ?string
{
return $this->description;
}
public function setDescription(?string $description): void
{
$this->description = $description;
}
public function getTrack(): ?Track
{
return $this->track;
}
public function setTrack(?Track $track): void
{
$this->track = $track;
}
public function getSchedule(): Collection
{
return $this->schedule;
}
public function setSchedule($schedule = [])
{
return $this->schedule = collect($schedule);
}
public function getDestination(): ?Stop
{
return $this->destination;
}
public function setDestination(?Stop $destination): void
{
$this->destination = $destination;
}
}

View File

@ -1,37 +0,0 @@
<?php
namespace App\Modifier;
class FieldFilter implements Modifier
{
private $field;
private $value;
private $operator;
public function __construct(string $field, $value, string $operator = '=')
{
$this->field = $field;
$this->value = $value;
$this->operator = $operator;
}
public static function contains(string $field, string $value)
{
return new static($field, "%$value%", 'LIKE');
}
public function getField(): string
{
return $this->field;
}
public function getValue()
{
return $this->value;
}
public function getOperator(): string
{
return $this->operator;
}
}

View File

@ -1,32 +0,0 @@
<?php
namespace App\Modifier;
use App\Exception\InvalidArgumentException;
use App\Modifier\Modifier;
use App\Service\IterableUtils;
class IdFilter implements Modifier
{
/** @var string|array */
private $id;
public function __construct($id)
{
if (!is_iterable($id) && !is_string($id)) {
throw InvalidArgumentException::invalidType('id', $id, ['string', 'array']);
}
$this->id = is_iterable($id) ? IterableUtils::toArray($id) : $id;
}
public function getId()
{
return $this->id;
}
public function isMultiple()
{
return is_array($this->id);
}
}

View File

@ -1,30 +0,0 @@
<?php
namespace App\Modifier;
class Limit implements Modifier
{
private $offset;
private $count;
public function __construct(int $offset = 0, ?int $count = null)
{
$this->offset = $offset;
$this->count = $count;
}
public function getOffset()
{
return $this->offset;
}
public function getCount()
{
return $this->count;
}
public static function count(int $count)
{
return new static(0, $count);
}
}

View File

@ -1,8 +0,0 @@
<?php
namespace App\Modifier;
interface Modifier
{
}

View File

@ -1,38 +0,0 @@
<?php
namespace App\Modifier;
use App\Exception\InvalidArgumentException;
use App\Model\Referable;
use App\Service\IterableUtils;
class RelatedFilter implements Modifier
{
private $relationship;
private $reference;
public function __construct($reference, ?string $relation = null)
{
if (!is_iterable($reference) && !$reference instanceof Referable) {
throw InvalidArgumentException::invalidType('object', $reference, [Referable::class, 'iterable']);
}
$this->reference = is_iterable($reference) ? IterableUtils::toArray($reference) : $reference;
$this->relationship = $relation ?: get_class($reference);
}
public function getRelationship(): string
{
return $this->relationship;
}
public function getRelated()
{
return $this->reference;
}
public function isMultiple()
{
return is_array($this->reference);
}
}

View File

@ -1,18 +0,0 @@
<?php
namespace App\Modifier;
class With implements Modifier
{
private $relationship;
public function __construct(string $relationship)
{
$this->relationship = $relationship;
}
public function getRelationship(): string
{
return $this->relationship;
}
}

View File

@ -1,30 +0,0 @@
<?php
namespace App\Output;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
use Symfony\Component\Console\Output\Output;
class LoggerOutput extends Output
{
/** @var LoggerInterface */
private $logger;
public function __construct(
LoggerInterface $logger,
?int $verbosity = self::VERBOSITY_NORMAL,
bool $decorated = false,
OutputFormatterInterface $formatter = null
) {
parent::__construct($verbosity, $decorated, $formatter);
$this->logger = $logger;
}
protected function doWrite(string $message, bool $newline)
{
$this->logger->info($message);
}
}

View File

@ -1,159 +0,0 @@
<?php
namespace App\Provider\Database;
use App\Entity\ProviderEntity;
use App\Event\HandleDatabaseModifierEvent;
use App\Event\PostProcessEvent;
use App\Handler\Database\FieldFilterDatabaseHandler;
use App\Handler\Database\IdFilterDatabaseHandler;
use App\Handler\Database\LimitDatabaseHandler;
use App\Handler\Database\RelatedFilterDatabaseGenericHandler;
use App\Handler\Database\GenericWithDatabaseHandler;
use App\Handler\ModifierHandler;
use App\Handler\PostProcessingHandler;
use App\Model\Referable;
use App\Modifier\FieldFilter;
use App\Modifier\IdFilter;
use App\Modifier\Limit;
use App\Modifier\Modifier;
use App\Modifier\RelatedFilter;
use App\Modifier\With;
use App\Provider\Repository;
use App\Service\Converter;
use App\Service\HandlerProvider;
use App\Service\IdUtils;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\QueryBuilder;
use Doctrine\ORM\Tools\Pagination\Paginator;
abstract class DatabaseRepository implements Repository
{
const DEFAULT_LIMIT = 100;
/** @var EntityManagerInterface */
protected $em;
/** @var ProviderEntity */
protected $provider;
/** @var IdUtils */
protected $id;
/** @var Converter */
protected $converter;
/** @var HandlerProvider */
protected $handlers;
/**
* DatabaseRepository constructor.
*
* @param EntityManagerInterface $em
*/
public function __construct(
EntityManagerInterface $em,
IdUtils $id,
Converter $converter,
HandlerProvider $handlers
) {
$this->em = $em;
$this->id = $id;
$this->converter = $converter;
$this->handlers = $handlers;
$this->handlers->loadConfiguration(array_merge([
IdFilter::class => IdFilterDatabaseHandler::class,
Limit::class => LimitDatabaseHandler::class,
FieldFilter::class => FieldFilterDatabaseHandler::class,
RelatedFilter::class => RelatedFilterDatabaseGenericHandler::class,
With::class => GenericWithDatabaseHandler::class,
], static::getHandlers()));
}
/** @return static */
public function withProvider(ProviderEntity $provider)
{
$result = clone $this;
$result->provider = $provider;
return $result;
}
protected function convert($entity)
{
return $this->converter->convert($entity);
}
protected function reference($class, Referable $referable)
{
$id = $this->id->generate($this->provider, $referable->getId());
return $this->em->getReference($class, $id);
}
protected function processQueryBuilder(QueryBuilder $builder, iterable $modifiers, array $meta = [])
{
$reducers = [];
foreach ($modifiers as $modifier) {
$handler = $this->handlers->get($modifier);
if ($handler instanceof ModifierHandler) {
$event = new HandleDatabaseModifierEvent($modifier, $this, $builder, array_merge([
'provider' => $this->provider,
], $meta));
$handler->process($event);
}
if ($handler instanceof PostProcessingHandler) {
$reducers[] = function ($result) use ($meta, $modifier, $handler) {
$event = new PostProcessEvent($result, $modifier, $this, array_merge([
'provider' => $this->provider,
], $meta));
$handler->postProcess($event);
return $event->getData();
};
}
}
return collect($reducers);
}
protected function allFromQueryBuilder(QueryBuilder $builder, iterable $modifiers, array $meta = [])
{
$builder->setMaxResults(self::DEFAULT_LIMIT);
$reducers = $this->processQueryBuilder($builder, $modifiers, $meta);
$query = $builder->getQuery();
$paginator = new Paginator($query);
$result = collect($paginator)->map(\Closure::fromCallable([$this, 'convert']));
return $reducers->reduce(function ($result, $reducer) {
return $reducer($result);
}, $result);
}
public function first(Modifier ...$modifiers)
{
return $this->all(Limit::count(1), ...$modifiers)->first();
}
/**
* Returns array describing handlers for each modifier type. Syntax is as follows:
* [ IdFilter::class => IdFilterDatabaseHandler::class ]
*
* It is internally used as part of service subscriber.
*
* @return array
*/
protected static function getHandlers()
{
return [];
}
}

View File

@ -1,27 +0,0 @@
<?php
namespace App\Provider\Database;
use App\Entity\LineEntity;
use App\Model\Line;
use App\Modifier\Modifier;
use App\Provider\LineRepository;
use Illuminate\Support\Collection;
class GenericLineRepository extends DatabaseRepository implements LineRepository
{
public function all(Modifier ...$modifiers): Collection
{
$builder = $this->em
->createQueryBuilder()
->from(LineEntity::class, 'line')
->select('line')
;
return $this->allFromQueryBuilder($builder, $modifiers, [
'alias' => 'line',
'entity' => LineEntity::class,
'type' => Line::class,
]);
}
}

View File

@ -1,27 +0,0 @@
<?php
namespace App\Provider\Database;
use App\Entity\OperatorEntity;
use App\Model\Operator;
use App\Modifier\Modifier;
use App\Provider\OperatorRepository;
use Illuminate\Support\Collection;
class GenericOperatorRepository extends DatabaseRepository implements OperatorRepository
{
public function all(Modifier ...$modifiers): Collection
{
$builder = $this->em
->createQueryBuilder()
->from(OperatorEntity::class, 'operator')
->select('operator')
;
return $this->allFromQueryBuilder($builder, $modifiers, [
'alias' => 'operator',
'entity' => OperatorEntity::class,
'type' => Operator::class,
]);
}
}

View File

@ -1,86 +0,0 @@
<?php
namespace App\Provider\Database;
use App\Entity\StopEntity;
use App\Entity\TrackEntity;
use App\Entity\TripStopEntity;
use App\Model\Departure;
use App\Model\ScheduledStop;
use App\Model\Stop;
use App\Modifier\Modifier;
use App\Provider\ScheduleRepository;
use Carbon\Carbon;
use Illuminate\Support\Collection;
class GenericScheduleRepository extends DatabaseRepository implements ScheduleRepository
{
public function getDeparturesForStop(
Stop $stop,
Carbon $from,
int $count = ScheduleRepository::DEFAULT_DEPARTURES_COUNT
): Collection {
$query = $this->em
->createQueryBuilder()
->select('ts', 't')
->from(TripStopEntity::class, 'ts')
->join('ts.trip', 't')
->where('ts.departure >= :from')
->andWhere('ts.stop = :stop')
->orderBy('ts.departure', 'ASC')
->setMaxResults($count)
->getQuery();
$schedule = collect($query->execute([
'from' => $from,
'stop' => $this->reference(StopEntity::class, $stop),
]));
$this->em->createQueryBuilder()
->select('t', 's', 'st')
->from(TrackEntity::class, 't')
->join('t.stopsInTrack', 's')
->join('s.stop', 'st')
->where('t.id in (:tracks)')
->getQuery()
->execute([
':tracks' => $schedule->map(function (TripStopEntity $stop) {
return $stop->getTrip()->getTrack()->getId();
})->all()
]);
return $schedule->map(function (TripStopEntity $entity) use ($stop) {
$trip = $entity->getTrip();
$track = $trip->getTrack();
$line = $track->getLine();
/** @var StopEntity $last */
$last = $entity->getTrip()->getTrack()->getStopsInTrack()->last()->getStop();
return Departure::createFromArray([
'key' => sprintf('%s::%s', $this->id->of($entity->getTrip()), $entity->getDeparture()->format('H:i')),
'scheduled' => $entity->getDeparture(),
'stop' => $stop,
'display' => $last->getName(),
'line' => $this->convert($line),
'track' => $this->convert($track),
'trip' => $this->convert($trip),
]);
});
}
public function all(Modifier ...$modifiers): Collection
{
$builder = $this->em
->createQueryBuilder()
->select('trip_stop')
->from(TripStopEntity::class, 'trip_stop')
->orderBy('trip_stop.departure', 'ASC')
;
return $this->allFromQueryBuilder($builder, $modifiers, [
'alias' => 'trip_stop',
'type' => ScheduledStop::class,
'entity' => TripStopEntity::class,
]);
}
}

View File

@ -1,41 +0,0 @@
<?php
namespace App\Provider\Database;
use App\Entity\StopEntity;
use App\Handler\Database\GenericWithDatabaseHandler;
use App\Handler\Database\WithDestinationsDatabaseHandler;
use App\Model\Stop;
use App\Modifier\Modifier;
use App\Modifier\With;
use App\Provider\StopRepository;
use Illuminate\Support\Collection;
class GenericStopRepository extends DatabaseRepository implements StopRepository
{
public function all(Modifier ...$modifiers): Collection
{
$builder = $this->em
->createQueryBuilder()
->from(StopEntity::class, 'stop')
->select('stop')
;
return $this->allFromQueryBuilder($builder, $modifiers, [
'alias' => 'stop',
'entity' => StopEntity::class,
'type' => Stop::class,
]);
}
protected static function getHandlers()
{
return array_merge(parent::getHandlers(), [
With::class => function (With $modifier) {
return $modifier->getRelationship() === 'destinations'
? WithDestinationsDatabaseHandler::class
: GenericWithDatabaseHandler::class;
},
]);
}
}

View File

@ -1,42 +0,0 @@
<?php
namespace App\Provider\Database;
use App\Entity\TrackEntity;
use App\Entity\TrackStopEntity;
use App\Model\Track;
use App\Model\TrackStop;
use App\Modifier\Modifier;
use App\Provider\TrackRepository;
use Illuminate\Support\Collection;
class GenericTrackRepository extends DatabaseRepository implements TrackRepository
{
public function stops(Modifier ...$modifiers): Collection
{
$builder = $this->em
->createQueryBuilder()
->from(TrackStopEntity::class, 'track_stop')
->select(['track_stop']);
return $this->allFromQueryBuilder($builder, $modifiers, [
'alias' => 'track_stop',
'entity' => TrackStopEntity::class,
'type' => TrackStop::class,
]);
}
public function all(Modifier ...$modifiers): Collection
{
$builder = $this->em
->createQueryBuilder()
->from(TrackEntity::class, 'track')
->select('track');
return $this->allFromQueryBuilder($builder, $modifiers, [
'alias' => 'track',
'entity' => TrackEntity::class,
'type' => Track::class,
]);
}
}

View File

@ -1,26 +0,0 @@
<?php
namespace App\Provider\Database;
use App\Entity\TripEntity;
use App\Model\Trip;
use App\Modifier\Modifier;
use App\Provider\TripRepository;
use Illuminate\Support\Collection;
class GenericTripRepository extends DatabaseRepository implements TripRepository
{
public function all(Modifier ...$modifiers): Collection
{
$builder = $this->em
->createQueryBuilder()
->from(TripEntity::class, 'trip')
->select('trip');
return $this->allFromQueryBuilder($builder, $modifiers, [
'alias' => 'trip',
'entity' => TripEntity::class,
'type' => Trip::class,
]);
}
}

View File

@ -1,12 +0,0 @@
<?php
namespace App\Provider;
use App\Modifier\Modifier;
interface DepartureRepository extends Repository
{
public function current(iterable $stops, Modifier ...$modifiers);
}

View File

@ -1,55 +0,0 @@
<?php
namespace App\Provider\Dummy;
use App\Model\Stop;
use App\Modifier\Modifier;
use App\Provider\StopRepository;
use App\Service\Proxy\ReferenceFactory;
use Illuminate\Support\Collection;
use Kadet\Functional as f;
class DummyStopRepository implements StopRepository
{
private $reference;
/**
* DummyDepartureProviderRepository constructor.
*
* @param $reference
*/
public function __construct(ReferenceFactory $reference)
{
$this->reference = $reference;
}
public function getAll(): Collection
{
return collect();
}
public function getById($id): ?Stop
{
return Stop::createFromArray(['id' => $id, 'name' => 'lorem']);
}
public function getManyById($ids): Collection
{
return collect($ids)->map(f\ref([ $this, 'getById' ]));
}
public function findByName(string $name): Collection
{
return collect();
}
public function first(Modifier ...$modifiers)
{
// TODO: Implement first() method.
}
public function all(Modifier ...$modifiers): Collection
{
// TODO: Implement all() method.
}
}

View File

@ -1,12 +0,0 @@
<?php
namespace App\Provider;
use App\Modifier\Modifier;
use Illuminate\Support\Collection;
interface FluentRepository extends Repository
{
public function first(Modifier ...$modifiers);
public function all(Modifier ...$modifiers): Collection;
}

View File

@ -1,8 +0,0 @@
<?php
namespace App\Provider;
interface LineRepository extends FluentRepository
{
}

View File

@ -1,9 +0,0 @@
<?php
namespace App\Provider;
interface OperatorRepository extends FluentRepository
{
}

View File

@ -1,18 +0,0 @@
<?php
namespace App\Provider;
use App\Model\Stop;
use Carbon\Carbon;
use Illuminate\Support\Collection;
interface ScheduleRepository extends FluentRepository
{
const DEFAULT_DEPARTURES_COUNT = 16;
public function getDeparturesForStop(
Stop $stop,
Carbon $from,
int $count = ScheduleRepository::DEFAULT_DEPARTURES_COUNT
): Collection;
}

View File

@ -1,9 +0,0 @@
<?php
namespace App\Provider;
interface StopRepository extends FluentRepository
{
}

View File

@ -1,11 +0,0 @@
<?php
namespace App\Provider;
use App\Modifier\Modifier;
use Illuminate\Support\Collection;
interface TrackRepository extends FluentRepository
{
public function stops(Modifier ...$modifiers): Collection;
}

View File

@ -1,9 +0,0 @@
<?php
namespace App\Provider;
use App\Model\Trip;
interface TripRepository extends FluentRepository
{
}

View File

@ -1,326 +0,0 @@
<?php
namespace App\Provider\ZtmGdansk;
use App\Entity\LineEntity;
use App\Entity\OperatorEntity;
use App\Entity\ProviderEntity;
use App\Entity\StopEntity;
use App\Entity\TrackEntity;
use App\Entity\TrackStopEntity;
use App\Entity\TripEntity;
use App\Entity\TripStopEntity;
use App\Event\DataUpdateEvent;
use App\Model\Line as LineModel;
use App\Service\DataUpdater;
use App\Service\IdUtils;
use Carbon\Carbon;
use Cerbero\JsonObjects\JsonObjects;
use Doctrine\ORM\EntityManagerInterface;
use Illuminate\Support\Collection;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use function Kadet\Functional\ref;
use function Kadet\Functional\Transforms\property;
class ZtmGdanskDataUpdateSubscriber implements EventSubscriberInterface
{
const BASE_URL = 'https://ckan.multimediagdansk.pl/dataset/c24aa637-3619-4dc2-a171-a23eec8f2172/resource';
const OPERATORS_URL = self::BASE_URL."/dff5f71f-0134-4ef3-8116-73c1a8e929a5/download/agencies.json";
const LINES_URL = self::BASE_URL."/22313c56-5acf-41c7-a5fd-dc5dc72b3851/download/routes.json";
const STOPS_URL = self::BASE_URL."/4c4025f0-01bf-41f7-a39f-d156d201b82b/download/stops.json";
const TRACKS_URL = self::BASE_URL."/b15bb11c-7e06-4685-964e-3db7775f912f/download/trips.json";
const STOPS_IN_TRACKS_URL = self::BASE_URL."/3115d29d-b763-4af5-93f6-763b835967d6/download/stopsintrips.json";
const SCHEDULE_URL = "http://ckan2.multimediagdansk.pl/stopTimes";
private $em;
private $ids;
private $logger;
private $provider;
private $stopBlacklist = [];
/**
* ZtmGdanskDataUpdateSubscriber constructor.
*
* @param $provider
* @param $em
*/
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(DataUpdateEvent $event)
{
ini_set('memory_limit', '2G');
$output = $event->getOutput();
$provider = ProviderEntity::createFromArray([
'name' => $this->provider->getName(),
'class' => ZtmGdanskProvider::class,
'id' => $this->provider->getIdentifier(),
]);
$this->em->persist($provider);
$save = ref([$this->em, 'persist']);
$this->getOperators($provider, $event)->each($save);
$this->getStops($provider, $event)->each($save);
$this->getTracks($provider, $event)->each($save);
$lines = $this->getLines($provider, $event)->each($save);
$output->write('Flushing all things into database...');
$this->em->flush();
$this->em->clear();
$output->writeln('done');
$this->updateSchedule($provider, $lines, $event);
}
private function getOperators(ProviderEntity $provider, DataUpdateEvent $event)
{
$output = $event->getOutput();
$output->write('Obtaining operators from ZTM Gdańsk...');
$operators = file_get_contents(self::OPERATORS_URL);
$operators = json_decode($operators, true)['agency'];
$output->writeln(sprintf('done (%d)', count($operators)));
$this->logger->info(sprintf('Saving %s operators from ZTM Gdańsk', count($operators)));
return collect($operators)->map(function ($operator) use ($provider) {
return OperatorEntity::createFromArray([
'id' => $this->ids->generate($provider, $operator['agencyId']),
'name' => $operator['agencyName'],
'provider' => $provider,
]);
});
}
private function getLines(ProviderEntity $provider, DataUpdateEvent $event)
{
$output = $event->getOutput();
$output->write('Obtaining lines from ZTM Gdańsk... ');
$lines = file_get_contents(self::LINES_URL);
$lines = json_decode($lines, true)[date('Y-m-d')]['routes'];
$output->writeln(sprintf('done (%d)', count($lines)));
$this->logger->info(sprintf('Saving %s lines from ZTM Gdańsk', count($lines)));
return collect($lines)->map(function ($line) use ($provider) {
$symbol = $line['routeShortName'];
$operator = $this->em->getReference(
OperatorEntity::class,
$this->ids->generate($provider, $line['agencyId'])
);
$type = [
2 => LineModel::TYPE_TRAM,
5 => LineModel::TYPE_TROLLEYBUS,
];
return LineEntity::createFromArray([
'id' => $this->ids->generate($provider, $line['routeId']),
'symbol' => $symbol,
'description' => $line['routeLongName'],
'type' => $type[$line['agencyId']] ?? LineModel::TYPE_BUS,
'night' => preg_match('/^N\d{1,3}$/', $symbol),
'fast' => preg_match('/^[A-MO-Z]$/', $symbol),
'operator' => $operator,
'provider' => $provider,
]);
});
}
private function getStops(ProviderEntity $provider, DataUpdateEvent $event)
{
$this->stopBlacklist = [];
$output = $event->getOutput();
$output->write('Obtaining stops from ZTM Gdańsk... ');
$stops = file_get_contents(self::STOPS_URL);
$stops = json_decode($stops, true)[date('Y-m-d')]['stops'];
$output->writeln(sprintf('done (%d)', count($stops)));
$this->logger->debug(sprintf("Saving %d stops tracks from ZTM Gdańsk.", count($stops)));
return collect($stops)
->filter(function ($stop) {
if ($stop['nonpassenger'] === 1 || $stop['virtual'] === 1 || $stop['depot'] === 1) {
$this->stopBlacklist[] = $stop['stopId'];
return false;
}
return true;
})
->map(function ($stop) use ($provider) {
$name = trim($stop['stopName'] ?? $stop['stopDesc']);
return StopEntity::createFromArray([
'id' => $this->ids->generate($provider, $stop['stopId']),
'name' => $name,
'variant' => trim($stop['zoneName'] == 'Gdańsk' ? $stop['stopCode'] ?? $stop['subName'] : null),
'latitude' => $stop['stopLat'],
'longitude' => $stop['stopLon'],
'onDemand' => (bool)$stop['onDemand'],
'provider' => $provider,
'group' => $name,
]);
})
;
}
public function getTracks(ProviderEntity $provider, DataUpdateEvent $event, Collection $stops = null)
{
$output = $event->getOutput();
$output->write('Obtaining tracks from ZTM Gdańsk... ');
$tracks = file_get_contents(self::TRACKS_URL);
$tracks = json_decode($tracks, true)[date('Y-m-d')]['trips'];
$output->writeln(sprintf('done (%d)', count($tracks)));
$this->logger->debug(sprintf("Got %d tracks from ZTM Gdańsk.", count($tracks)));
$output->write('Obtaining stops associations... ');
$stops = file_get_contents(self::STOPS_IN_TRACKS_URL);
$stops = json_decode($stops, true)[date('Y-m-d')]['stopsInTrip'];
$output->writeln(sprintf('done (%d)', count($stops)));
$this->logger->debug(sprintf("Got %d stops in all tracks from ZTM Gdańsk.", count($stops)));
$stops = collect($stops)->groupBy(function ($stop) {
return sprintf("R%sT%s", $stop['routeId'], $stop['tripId']);
});
return collect($tracks)->map(function ($track) use ($provider, $stops) {
$entity = TrackEntity::createFromArray([
'id' => $this->ids->generate($provider, $track['id']),
'line' => $this->em->getReference(
LineEntity::class,
$this->ids->generate($provider, $track['routeId'])
),
'description' => preg_replace('/\(\d+\)/', '', $track['tripHeadsign']),
'provider' => $provider,
]);
$stops = $stops->get($track['id'])
->filter(function ($stop) {
return !in_array($stop['stopId'], $this->stopBlacklist);
})
->map(function ($stop) use ($entity, $provider) {
return TrackStopEntity::createFromArray([
'stop' => $this->em->getReference(
StopEntity::class,
$this->ids->generate($provider, $stop['stopId'])
),
'track' => $entity,
// HACK! Gdynia has 0 based sequence
'order' => $stop['stopSequence'] + (int)($stop['stopId'] > 30000),
]);
})
->sortBy(property("order"));
$entity->setStopsInTrack($stops);
return $entity;
});
}
public function saveScheduleForLine(ProviderEntity $provider, LineEntity $line)
{
$url = sprintf("%s?%s", self::SCHEDULE_URL, http_build_query([
'date' => date('Y-m-d'),
'routeId' => $this->ids->of($line),
]));
$schedule = JsonObjects::from($url, 'stopTimes.*');
$trips = new Collection();
$schedule->each(function ($stop) use ($provider, $line, &$trips, &$ids) {
$id = sprintf('%s-%s-%d', $stop['busServiceName'], $stop['tripId'], $stop['order']);
$trip = $trips[$id] ?? $trips[$id] = (function () use ($stop, $id, $provider) {
$trip = TripEntity::createFromArray([
'id' => $this->ids->generate($provider, $id),
'operator' => $this->em->getReference(
OperatorEntity::class,
$this->ids->generate($provider, $stop['agencyId'])
),
'track' => $this->em->getReference(
TrackEntity::class,
$this->ids->generate($provider, sprintf('R%sT%s', $stop['routeId'], $stop['tripId']))
),
]);
$this->em->persist($trip);
return $trip;
})();
$base = Carbon::create(1899, 12, 30, 00, 00, 00);
$date = Carbon::createFromFormat('Y-m-d', $stop['date'], 'Europe/Warsaw')->setTime(00, 00, 00);
$arrival = $base->diff(Carbon::createFromTimeString($stop['arrivalTime']));
$departure = $base->diff(Carbon::createFromTimeString($stop['departureTime']));
$arrival = (clone $date)->add($arrival);
$departure = (clone $date)->add($departure);
$entity = TripStopEntity::createFromArray([
'trip' => $trip,
'stop' => $this->em->getReference(
StopEntity::class,
$this->ids->generate($provider, $stop['stopId'])
),
'order' => $stop['stopSequence'],
'arrival' => $arrival->tz('UTC'),
'departure' => $departure->tz('UTC'),
]);
$entity->setTrip($trip);
$this->em->persist($entity);
});
$this->logger->debug(sprintf('Got schedule for line %s from ZTM Gdańsk', $line->getId()));
$this->em->flush();
$this->em->clear();
gc_collect_cycles();
}
public static function getSubscribedEvents()
{
return [
DataUpdater::UPDATE_EVENT => 'update',
];
}
private function updateSchedule(ProviderEntity $provider, Collection $lines, DataUpdateEvent $event)
{
$event->getOutput()->writeln(sprintf("Obtaining schedule for %d lines...", count($lines)));
$progress = new ProgressBar($event->getOutput(), $lines->count());
$progress->setFormat('%current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s%, line %line%');
$progress->start();
/** @var LineEntity $line */
foreach ($lines as $line) {
$progress->setMessage($line->getSymbol(), 'line');
$progress->display();
$this->saveScheduleForLine($provider, $line);
$progress->advance();
}
$progress->finish();
$event->getOutput()->writeln("");
$event->getOutput()->writeln("done");
}
}

Some files were not shown because too many files have changed in this diff Show More