Compare commits

...

116 Commits
0.2 ... master

Author SHA1 Message Date
Kacper Donat
139c313a84 Add missing DATABSE_URL to base Dockerimage 2021-04-22 15:55:05 +02:00
Kacper Donat
64f1621e04 Add cleanup after request for RoadRunner 2021-04-21 21:29:07 +02:00
Kacper Donat
f9ee00b29b Add contribution guidelines and CLA 2021-04-20 23:07:44 +02:00
Kacper Donat
562599b00f Create base images with variants corresponding to PHP image variants 2021-04-17 15:23:45 +02:00
Kacper Donat
f394bf42eb Create base image for sharing common code 2021-04-15 22:34:17 +02:00
Kacper Donat
59118cd6a4 Add release script 2021-04-15 22:34:17 +02:00
Kacper Donat
961711b34b Split build directory from docker-compose related files
Files related with building distribution images now have it's own
directory. Files for development using docker-compose are now placed in
.docker-compose directory as they are non-essentials.
2021-04-14 18:38:30 +02:00
Kacper Donat
d7ec6aee4d Refactor Dockerfiles for better cacheability 2021-04-11 19:00:06 +02:00
Kacper Donat
af801110aa Messenger configuration 2021-04-09 21:52:51 +02:00
Kacper Donat
d86c1fa0f8 Ability to run update on separate worker process 2021-04-09 18:36:23 +02:00
Kacper Donat
53c72f729e Fix spacing issues 2021-04-08 20:53:38 +02:00
Kacper Donat
188494a996 Update Symfony to 5.2 2021-03-31 21:38:18 +02:00
Kacper Donat
2f14e147b5 Update PHP dependencies 2021-03-31 21:08:33 +02:00
Kacper Donat
0bdc9cafc0 Add simple README and LICENSE files 2020-12-19 09:38:54 +01:00
Kacper Donat
8ea48bcaf3 Add simple README and LICENSE files 2020-12-18 15:19:58 +01:00
Kacper Donat
c4150dc362 Autocreate proxy dir 2020-12-01 21:23:37 +01:00
Kacper Donat
4ed6c45441 Fix symfony version to 4.4 2020-11-30 00:39:53 +01:00
Kacper Donat
eaf58bd477 Create dev docker api image 2020-11-29 23:50:57 +01:00
Kacper Donat
69d51636f2 Change docker base to alpine linux 2020-11-29 19:56:16 +01:00
Kacper Donat
a865c5ec8d update dependencies 2020-11-28 16:01:46 +01:00
Kacper Donat
106aa2149e make JSON default format for API 2020-11-23 00:13:50 +01:00
Kacper Donat
fb6cc37dd8 docker-compose now uses frontend server 2020-11-22 23:10:24 +01:00
Kacper Donat
17dd5ed296 create Dockerfile for frontend 2020-11-22 19:59:03 +01:00
Kacper Donat
535f899aaf simple frontend server to supply manifest 2020-11-22 16:03:14 +01:00
Kacper Donat
edba09be4c Update manifest on page change 2020-11-22 14:28:09 +01:00
Kacper Donat
b71ca34dd3 move frontend routing to frontend instead of symfony 2020-11-02 22:04:40 +01:00
Kacper Donat
65a9819b1b add useful aliases for frontend 2020-11-01 18:50:54 +01:00
Kacper Donat
53d49d3894 cleanup frontend directory structure 2020-11-01 18:39:47 +01:00
Kacper Donat
ab56fe9917 split front and api parts 2020-11-01 17:57:06 +01:00
Kacper Donat
75c0c98c8d history of added stops 2020-11-01 12:11:20 +01:00
Kacper Donat
c2b959a9da Add ability to replace favourite with confirmation. 2020-10-20 21:10:56 +02:00
Kacper Donat
4647bb2cc1 add proper style for primary button 2020-10-19 23:11:37 +02:00
Kacper Donat
2a96c5a863 Fix some tiny errors related to background 2020-10-02 21:08:38 +02:00
Kacper Donat
3bc176936c Refine modal system 2020-10-02 20:00:24 +02:00
Kacper Donat
e0574615a7 Basic Modal system 2020-10-02 20:00:24 +02:00
Kacper Donat
452a5c4993 Add background 2020-10-01 22:04:16 +02:00
Kacper Donat
85a2a2a4a7 Replace google analytics with google tag manager 2020-09-19 19:26:18 +02:00
Kacper Donat
ed8c62f4bd Unify provider and main webapp manifest (manifest.json) 2020-09-18 22:47:22 +02:00
Kacper Donat
63c5f7572b Provide better maskable icon 2020-09-18 22:16:29 +02:00
Kacper Donat
531228dfca Add basic noscript warning 2020-09-18 21:58:29 +02:00
Kacper Donat
b8734d4dc6 Add support for maskable and monochrome icons 2020-09-18 21:57:38 +02:00
Kacper Donat
7db9517840 (Hot) Fix Aggregate Converter loop 2020-05-03 00:03:54 +02:00
Kacper Donat
ab9e1b5b99 Fix passing provider to Vue 2020-05-02 23:51:50 +02:00
Kacper Donat
c72bf86a96 Fix typo in provider picker header 2020-05-02 23:39:27 +02:00
Kacper Donat
a893929cf9 Add map based provider picker 2020-05-02 23:33:15 +02:00
Kacper Donat
ec23a41e37 Add provider DTO and API endpoint 2020-05-02 00:58:03 +02:00
Kacper Donat
7279f2096e Fix bug with non serializing recursive departure 2020-03-21 15:41:49 +01:00
Kacper Donat
cde6507197 Move messaging settings to vuex store 2020-03-19 20:22:50 +01:00
Kacper Donat
10756226f1 Relative departure times 2020-03-19 17:28:26 +01:00
Kacper Donat
5c8a0238f1 Extract departures settings to its own component and store module 2020-03-18 22:05:40 +01:00
Kacper Donat
87e4121444 UI - Add UiNumericInput component 2020-03-18 16:33:06 +01:00
Kacper Donat
ea283a86e7 Merge branch '35_new_repository_pattern_with_filters_and_modifiers' 2020-03-16 21:33:15 +01:00
Kacper Donat
ae05646888 Add paginator to properly handle limits 2020-03-16 20:32:15 +01:00
Kacper Donat
3e695bfef7 Add Limit support for departure repository 2020-03-15 22:35:12 +01:00
Kacper Donat
1a0515742e Add blackfire service for profiling 2020-03-15 17:30:08 +01:00
Kacper Donat
50a79470e7 WIP - Add scheduled stop repository 2020-03-15 12:50:38 +01:00
Kacper Donat
7ffd3c02cd Extract HandlerProvider to own class 2020-03-14 17:19:43 +01:00
Kacper Donat
e6c5047408 Fix situation when there are multiple first stops on same trip 2020-03-14 12:37:11 +01:00
Kacper Donat
87255eaf13 Fix situation when there are multiple first stops on same trip 2020-03-14 12:36:24 +01:00
Kacper Donat
ee0cde0400 Add support for multiple related objects filtering 2020-02-23 17:12:23 +01:00
Kacper Donat
45004444e6 Various little fixes and improvements 2020-02-23 15:23:14 +01:00
Kacper Donat
a9a0f2f413 Track stops fluent quering 2020-02-20 17:33:31 +01:00
Kacper Donat
9f3f6bf22b Add stop filter for tracks 2020-02-18 22:45:16 +01:00
Kacper Donat
a3de2b244f Rewrite Track Repository into fluent pattern 2020-02-17 21:48:51 +01:00
Kacper Donat
950e310096 Move trip and operator repository to fluent pattern 2020-02-16 21:59:38 +01:00
Kacper Donat
847e3a078f Make StopRepository more fluent 2020-02-16 21:09:07 +01:00
Kacper Donat
67f7ba2a88 Remove unnecessary methods from LineRepository 2020-02-12 20:16:26 +01:00
Kacper Donat
6bd71f0ec5 Add service subscriber to database repository 2020-02-12 20:08:56 +01:00
Kacper Donat
c1a58f4bd6 fluent repository prototype 2020-02-11 22:48:30 +01:00
Kacper Donat
934561ca0e Fix missing icon in favourites adder 2020-02-11 20:12:33 +01:00
Kacper Donat
c602737ba1 rebrand to 'Co Jedzie' as 'Czy Dojade' is taken 2020-02-10 22:38:49 +01:00
Kacper Donat
ee3dc50914 Create icon library 2020-02-09 21:59:33 +01:00
Kacper Donat
4d98a8607b Restyle inputs 2020-02-09 15:56:29 +01:00
Kacper Donat
92c12d1c9a Switch instead of checkbox 2020-02-09 14:05:46 +01:00
Kacper Donat
dd5fbc1ed8 Switch instead of checkbox 2020-02-09 13:56:22 +01:00
Kacper Donat
a7bd34677b Fixed build errors 2020-02-08 21:50:20 +01:00
Kacper Donat
66e45c8112 Add lines to destinations
This is useful when we have few different stops, with same destinations and different lines.
2020-02-08 18:36:29 +01:00
Kacper Donat
9ecadfd4d1 Display only unique destinations 2020-02-08 15:14:46 +01:00
Kacper Donat
5e4208067c Remove babel minify as it causes problems with mapbox 2020-02-08 14:27:08 +01:00
Kacper Donat
7da2038e02 Remove unnecesasary poper elements 2020-02-06 23:33:52 +01:00
Kacper Donat
4545899d20 Restyle stop group headers 2020-02-06 23:09:24 +01:00
Kacper Donat
2b0792775f Fix overflow errors 2020-02-06 23:01:25 +01:00
Kacper Donat
38e6ae52e1 Add final stop info to track 2020-02-06 21:31:11 +01:00
Kacper Donat
f3fc6cb071 Add destination field in stops 2020-02-04 22:23:48 +01:00
Kacper Donat
cb6c3a3950 Refactor stop grouping 2020-02-04 18:59:37 +01:00
Kacper Donat
9b13612c43 Fix tooltips on chrome mobile 2020-01-29 18:10:34 +01:00
Kacper Donat
53c43cd431 Add tooltips to every icon only action 2020-01-28 22:19:59 +01:00
Kacper Donat
4bbae18d54 Skipped migration should be marked as done 2020-01-28 22:10:48 +01:00
Kacper Donat
4761d7a44c Fix all JS and Vue errors in tooltip 2020-01-28 22:04:48 +01:00
Kacper Donat
759096b8c4 Fix migrations when state is empty by adding skipping 2020-01-28 22:02:01 +01:00
Kacper Donat
519dc8de22 Tooltip component now works on mobile 2020-01-28 21:57:58 +01:00
Kacper Donat
f9c289aacd Tooltip component now not responsible (but mobile friendly) & tooltips 2020-01-27 22:53:22 +01:00
Kacper Donat
ce8c8f97ec Add tooltip component 2020-01-26 22:43:26 +01:00
Kacper Donat
4bd5ee14ca Show stops in favourites entry 2020-01-26 17:46:25 +01:00
Kacper Donat
b06a89350f Add very primitive migration engine 2020-01-26 17:33:36 +01:00
Kacper Donat
253b206a39 Add stop names in favourite save dialog 2020-01-26 15:15:36 +01:00
Kacper Donat
6df7bdbea0 Don't use OSM tiles 2020-01-25 21:02:52 +01:00
Kacper Donat
89b4f5911a Turn off dragscroll on touch devices as its not needed there 2020-01-25 17:32:23 +01:00
Kacper Donat
b23bf3ad84 Hide virtual stops from trips. 2020-01-25 17:19:02 +01:00
Kacper Donat
0fc075663a Add current stop highlight 2020-01-25 16:41:23 +01:00
Kacper Donat
9e4a7c2d57 Fix typescript errors 2020-01-24 22:37:47 +01:00
Kacper Donat
306c034de5 Add visual info about position of the stop on trip 2020-01-24 22:26:29 +01:00
Kacper Donat
e49a70c71a Exctract trip to separate component 2020-01-24 22:03:28 +01:00
Kacper Donat
c00e038fba Add trip display to departures 2020-01-23 19:27:08 +01:00
Kacper Donat
1bdea1926d Trip controller for accessing stops scheduled in trip 2020-01-22 20:40:39 +01:00
Kacper Donat
2c9a795756 SerializeAs annotation 2020-01-21 21:46:27 +01:00
Kacper Donat
02776c4c90 Add departure id to departures 2020-01-21 19:01:53 +01:00
Kacper Donat
f33b3c21fe Remove unnecessary listeners from popper 2020-01-19 21:53:09 +01:00
Kacper Donat
17d281fffe Restyle popups 2020-01-19 18:20:56 +01:00
Kacper Donat
9802473d7c Refactor popups 2020-01-19 18:04:27 +01:00
Kacper Donat
4b389582ad [ZTM Gdansk] Merge real departure with scheduled for better destination showcase 2020-01-15 20:32:11 +01:00
Kacper Donat
78263302c8 Better icon for scheduled departures 2020-01-15 17:32:38 +01:00
Kacper Donat
d07936d314 Fix order of scheduled and actual departure time 2020-01-14 22:04:29 +01:00
Kacper Donat
3b7932b976 Remove stop details and map from Stop component 2020-01-14 15:26:44 +01:00
Kacper Donat
ecd02d21bb Remember selected stops in session storage 2020-01-13 20:52:40 +01:00
Kacper Donat
cdffda56aa Change default classifiction of message from unknown to info 2020-01-13 17:58:18 +01:00
410 changed files with 14699 additions and 5696 deletions

1
.docker-compose/api/.env Normal file
View File

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

View File

@ -0,0 +1,31 @@
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

@ -0,0 +1,44 @@
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

@ -1,17 +0,0 @@
# This file is a "template" of which env vars need to be defined for your application
# 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=1bdf86cdc78fba654e4f2c309c6bbdbd
###< symfony/framework-bundle ###
###> doctrine/doctrine-bundle ###
# 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
###< doctrine/doctrine-bundle ###

16
.gitignore vendored
View File

@ -1,17 +1 @@
###> 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 Normal file
View File

@ -0,0 +1,71 @@
# 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.

38
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,38 @@
# 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

41
LICENSE.md Normal file
View File

@ -0,0 +1,41 @@
“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.

32
README.md Normal file
View File

@ -0,0 +1,32 @@
# [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

2
api/.dockerignore Normal file
View File

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

22
api/.env Normal file
View File

@ -0,0 +1,22 @@
# This file is a "template" of which env vars need to be defined for your application
# 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
###> 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)$'
###< 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
#
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
api/.gitignore vendored Normal file
View File

@ -0,0 +1,16 @@
###> 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 ###

11
api/.rr.dev.yaml Normal file
View File

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

13
api/.rr.yaml Normal file
View File

@ -0,0 +1,13 @@
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"]

3
api/CONTRIBUTING.md Normal file
View File

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

28
api/Dockerfile Normal file
View File

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

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

5
api/bin/docker-init.sh Executable file
View File

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

View File

@ -1,34 +1,37 @@
{
"name": "kadet/czydojade",
"name": "kadet/cojedzie",
"description": "Co Jedzie",
"type": "project",
"license": "MIT",
"license": "MIT with Commons Clause",
"require": {
"php": "^7.1.3",
"php": "^7.4",
"ext-ctype": "*",
"ext-iconv": "*",
"ext-json": "*",
"baldinof/roadrunner-bundle": "^1.3",
"cerbero/json-objects": "^1.1",
"doctrine/doctrine-cache-bundle": "^1.4",
"illuminate/collections": "^8.35",
"jms/serializer-bundle": "^3.5",
"kadet/functional": "dev-master",
"nelmio/api-doc-bundle": "^3.5",
"nesbot/carbon": "^1.33",
"nesbot/carbon": "^2.46.0",
"ocramius/proxy-manager": "^2.0",
"sensio/framework-extra-bundle": "^5.2",
"symfony/asset": "^4.4",
"symfony/console": "^4.4",
"spiral/roadrunner": "^1.8",
"symfony/asset": "^5.2",
"symfony/console": "^5.2",
"symfony/doctrine-messenger": "5.2.*",
"symfony/flex": "^1.1",
"symfony/framework-bundle": "^4.4",
"symfony/framework-bundle": "^5.2",
"symfony/messenger": "5.2.*",
"symfony/monolog-bundle": "^3.3",
"symfony/orm-pack": "^1.0",
"symfony/profiler-pack": "^1.0",
"symfony/twig-bundle": "^4.4",
"symfony/yaml": "^4.4",
"tightenco/collect": "^5.6"
},
"require-dev": {
"symfony/dotenv": "^4.4",
"symfony/web-server-bundle": "^4.4",
"kadet/functional": "dev-master"
"symfony/twig-bundle": "^5.2",
"symfony/yaml": "^5.2",
"symfony/dotenv": "5.2.*",
"symfony/amqp-messenger": "5.2.*",
"symfony/redis-messenger": "5.2.*"
},
"config": {
"preferred-install": {
@ -37,14 +40,17 @@
"sort-packages": true,
"secure-http": false,
"platform": {
"php": "7.3.12"
"php": "7.4.15"
}
},
"autoload": {
"psr-4": {
"App\\": "src/"
},
"files": ["./src/Functions/index.php"]
"files": [
"vendor/symfony/dependency-injection/Loader/Configurator/ContainerConfigurator.php",
"./src/Functions/index.php"
]
},
"autoload-dev": {
"psr-4": {
@ -75,7 +81,8 @@
},
"extra": {
"symfony": {
"allow-contrib": true
"allow-contrib": true,
"require": "5.2.*"
}
},
"repositories": [
@ -83,5 +90,8 @@
"type": "vcs",
"url": "https://git.kadet.net/kadet/functional-php.git"
}
]
],
"require-dev": {
"symfony/maker-bundle": "^1.30"
}
}

File diff suppressed because it is too large Load Diff

23
api/config/bootstrap.php Normal file
View File

@ -0,0 +1,23 @@
<?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

@ -2,14 +2,14 @@
return [
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
Symfony\Bundle\WebServerBundle\WebServerBundle::class => ['dev' => true],
Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class => ['all' => true],
Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
Doctrine\Bundle\DoctrineCacheBundle\DoctrineCacheBundle::class => ['all' => true],
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
Nelmio\ApiDocBundle\NelmioApiDocBundle::class => ['all' => true],
JMS\SerializerBundle\JMSSerializerBundle::class => ['all' => true],
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
Baldinof\RoadRunnerBundle\BaldinofRoadRunnerBundle::class => ['all' => true],
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
];

View File

@ -0,0 +1,18 @@
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

@ -0,0 +1,19 @@
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,5 +1,5 @@
web_profiler:
toolbar: true
toolbar: false
intercept_redirects: false
framework:

View File

@ -5,14 +5,13 @@ doctrine:
dbal:
driver: 'pdo_sqlite'
url: '%env(resolve:DATABASE_URL)%'
logging: true
profiling: true
logging: '%kernel.debug%'
profiling: '%kernel.debug%'
types:
datetime: App\Doctrine\CarbonDateTimeType
orm:
auto_generate_proxy_classes: '%kernel.debug%'
naming_strategy: doctrine.orm.naming_strategy.underscore
auto_generate_proxy_classes: true
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
auto_mapping: true
mappings:
App:
@ -20,4 +19,4 @@ doctrine:
type: annotation
dir: '%kernel.project_dir%/src/Entity'
prefix: 'App\Entity'
alias: Entity
alias: App

View File

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

View File

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

View File

@ -1,4 +1,8 @@
jms_serializer:
default_context:
serialization:
serialize_null: true
visitors:
xml_serialization:
format_output: '%kernel.debug%'

View File

@ -0,0 +1,11 @@
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,7 +1,7 @@
nelmio_api_doc:
documentation:
info:
title: Czy Dojadę?
title: Co Jedzie?
version: 0.1.0
parameters:
provider:
@ -13,7 +13,7 @@ nelmio_api_doc:
areas:
path_patterns:
- ^/[^\/]+/api(?!/doc$) # Accepts routes under /api except /api/doc
- /api(?!/doc$) # Accepts routes under /api except /api/doc

View File

@ -0,0 +1,20 @@
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,3 +1,4 @@
framework:
router:
strict_requirements: ~

View File

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

View File

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

9
api/config/preload.php Normal file
View File

@ -0,0 +1,9 @@
<?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';
}

16
api/config/routes.yaml Normal file
View File

@ -0,0 +1,16 @@
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

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

View File

@ -19,11 +19,14 @@ services:
App\Provider\Provider:
tags: [ app.provider ]
App\Service\Converter:
tags: [ app.converter ]
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/*'
exclude: '../src/{DependencyInjection,Entity,Model,Migrations,Tests,Functions,Kernel.php}'
exclude: '../src/{DependencyInjection,Exception,Modifier,Entity,Model,Migrations,Tests,Functions,Handler,Kernel.php}'
# controllers are imported separately to make sure services can be injected
# as action arguments even if you don't extend any base controller class
@ -35,12 +38,9 @@ services:
resource: '../src/Provider'
public: true
# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones
#assets
assets.modified_time_version_strategy:
class: App\Asset\ModifiedTimeVersionStrategy
App\Handler\:
resource: '../src/Handler'
tags: [ app.handler ]
#eerialziser
jms_serializer.serialized_name_annotation_strategy:
@ -53,7 +53,7 @@ services:
#proxy configuration
proxy.locator:
class: 'ProxyManager\FileLocator\FileLocator'
class: 'App\Service\Proxy\FileLocator'
arguments: ['%kernel.cache_dir%/proxy']
proxy.strategy:
@ -68,16 +68,23 @@ services:
ProxyManager\Configuration: '@proxy.config'
# serializer configuration
serializer.datetime_normalizer:
class: Symfony\Component\Serializer\Normalizer\DateTimeNormalizer
arguments: [!php/const DateTime::ATOM]
tags: [serializer.normalizer]
# converter
App\Service\AggregateConverter:
arguments:
- !tagged_iterator app.converter
App\Service\Converter: '@App\Service\AggregateConverter'
# serializer configuration
App\Service\SerializerContextFactory:
arguments:
$factory: '@jms_serializer.metadata_factory'
App\Service\Normalizer\:
resource: '../src/Service/Normalizer'
tags: [serializer.normalizer]
# other servces
App\Service\ProviderResolver:
arguments: [!tagged app.provider, '%kernel.debug%']
App\Service\HandlerProvider:
arguments: [!tagged_locator app.handler]
shared: false

View File

@ -0,0 +1,45 @@
<?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

@ -0,0 +1,57 @@
<?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

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

27
api/public/index.php Normal file
View File

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

29
api/rr.Dockerfile Normal file
View File

@ -0,0 +1,29 @@
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,6 +1,6 @@
<?xml version="1.0"?>
<rulset name="Kadet.CzyDojade">
<description>Czy Dojadę ruleset</description>
<rulset name="CoJedzie">
<description>Co Jedzie ruleset</description>
<arg name="colors"/>
<arg name="parallel" value="75"/>

View File

@ -0,0 +1,48 @@
<?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

@ -5,8 +5,13 @@ namespace App\Controller\Api\v1;
use App\Controller\Controller;
use App\Model\Departure;
use App\Modifier\FieldFilter;
use App\Modifier\IdFilter;
use App\Modifier\Limit;
use App\Modifier\With;
use App\Provider\DepartureRepository;
use App\Provider\StopRepository;
use App\Service\SerializerContextFactory;
use Nelmio\ApiDocBundle\Annotation\Model;
use Swagger\Annotations as SWG;
use Symfony\Component\HttpFoundation\Request;
@ -31,11 +36,11 @@ class DeparturesController extends Controller
* @SWG\Schema(type="array", @SWG\Items(ref=@Model(type=Departure::class)))
* )
*/
public function stop(DepartureRepository $departures, StopRepository $stops, $stop)
public function stop(DepartureRepository $departures, StopRepository $stops, $stop, Request $request)
{
$stop = $stops->getById($stop);
$stop = $stops->first(new IdFilter($stop));
return $this->json($departures->getForStop($stop));
return $this->json($departures->current(collect($stop), ...$this->getModifiersFromRequest($request)));
}
/**
@ -63,11 +68,21 @@ class DeparturesController extends Controller
*/
public function stops(DepartureRepository $departures, StopRepository $stops, Request $request)
{
$stops = $stops
->getManyById($request->query->get('stop'))
->flatMap(ref([ $departures, 'getForStop' ]))
->sortBy(property('departure'));
$stops = $stops->all(new IdFilter($request->query->get('stop', [])));
$result = $departures->current($stops, ...$this->getModifiersFromRequest($request));
return $this->json($stops->values()->slice(0, (int)$request->query->get('limit', 8)));
return $this->json(
$result->values()->slice(0, (int)$request->query->get('limit', 8)),
200,
[],
$this->serializerContextFactory->create(Departure::class, ['Default'])
);
}
private function getModifiersFromRequest(Request $request)
{
if ($request->query->has('limit')) {
yield Limit::count($request->query->getInt('limit'));
}
}
}

View File

@ -0,0 +1,34 @@
<?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,15 +1,18 @@
<?php
namespace App\Controller\Api\v1;
use App\Controller\Controller;
use App\Model\Stop;
use App\Model\Track;
use App\Model\StopGroup;
use App\Model\TrackStop;
use App\Modifier\FieldFilter;
use App\Modifier\IdFilter;
use App\Modifier\RelatedFilter;
use App\Modifier\With;
use App\Provider\StopRepository;
use App\Provider\TrackRepository;
use App\Service\Proxy\ReferenceFactory;
use Illuminate\Support\Collection;
use Nelmio\ApiDocBundle\Annotation\Model;
use Swagger\Annotations as SWG;
use Symfony\Component\HttpFoundation\Request;
@ -37,7 +40,8 @@ class StopsController extends Controller
* name="id",
* in="query",
* type="array",
* description="Stop identificators to retrieve at once. Can be used to bulk load data. If not specified will return all data.",
* description="Stop identificators to retrieve at once. Can be used to bulk load data. If not specified will
* return all data.",
* @SWG\Items(type="string")
* )
*
@ -45,16 +49,9 @@ class StopsController extends Controller
*/
public function index(Request $request, StopRepository $stops)
{
switch (true) {
case $request->query->has('id'):
$result = $stops->getManyById($request->query->get('id'));
break;
$modifiers = $this->getModifiersFromRequest($request);
default:
$result = $stops->getAllGroups();
}
return $this->json($result->all());
return $this->json($stops->all(...$modifiers)->toArray());
}
/**
@ -75,16 +72,9 @@ class StopsController extends Controller
*/
public function groups(Request $request, StopRepository $stops)
{
switch (true) {
case $request->query->has('name'):
$result = $stops->findGroupsByName($request->query->get('name'));
break;
$modifiers = $this->getModifiersFromRequest($request);
default:
$result = $stops->getAllGroups();
}
return $this->json($result->all());
return $this->json(static::group($stops->all(...$modifiers))->toArray());
}
/**
@ -105,7 +95,7 @@ class StopsController extends Controller
*/
public function one(Request $request, StopRepository $stops, $id)
{
return $this->json($stops->getById($id));
return $this->json($stops->first(new IdFilter($id), new With("destinations")));
}
/**
@ -114,20 +104,40 @@ class StopsController extends Controller
* @SWG\Response(
* response=200,
* description="Returns specific stop referenced via identificator.",
* @SWG\Schema(type="object", properties={
* @SWG\Property(property="track", type="object", ref=@Model(type=Track::class)),
* @SWG\Property(property="order", type="integer", minimum="0")
* })
* @SWG\Schema(ref=@Model(type=TrackStop::class))
* )
*
* @SWG\Tag(name="Tracks")
*/
public function tracks(ReferenceFactory $reference, TrackRepository $tracks, $id)
public function tracks(TrackRepository $tracks, $id)
{
$stop = $reference->get(Stop::class, $id);
return $this->json($tracks->getByStop($stop)->map(function ($tuple) {
return array_combine(['track', 'order'], $tuple);
}));
return $this->json($tracks->stops(new RelatedFilter(Stop::reference($id))));
}
}
public static function group(Collection $stops)
{
return $stops->groupBy(function (Stop $stop) {
return $stop->getGroup();
})->map(function ($stops, $key) {
$group = new StopGroup();
$group->setName($key);
$group->setStops($stops);
return $group;
})->values();
}
private function getModifiersFromRequest(Request $request)
{
if ($request->query->has('name')) {
yield FieldFilter::contains('name', $request->query->get('name'));
}
if ($request->query->has('id')) {
yield new IdFilter($request->query->get('id'));
}
if ($request->query->has('include-destinations')) {
yield new With("destinations");
}
}
}

View File

@ -0,0 +1,98 @@
<?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

@ -0,0 +1,27 @@
<?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

@ -0,0 +1,27 @@
<?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

@ -9,7 +9,9 @@ use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(readOnly=true)
* @ORM\Table("stop")
* @ORM\Table("stop", indexes={
* @ORM\Index(name="group_idx", columns={"group_name"})
* })
*/
class StopEntity implements Entity, Fillable
{
@ -27,10 +29,18 @@ class StopEntity implements Entity, Fillable
* Stop name
* @var string
*
* @ORM\Column(type="string")
* @ORM\Column(type="string", length=255)
*/
private $name;
/**
* Stop group name
* @var string|null
*
* @ORM\Column(type="string", length=255, nullable=true, name="group_name")
*/
private $group;
/**
* Optional stop description, should not be longer than 255 chars
* @var string|null
@ -43,7 +53,7 @@ class StopEntity implements Entity, Fillable
* Optional stop variant - for example number of shed
* @var string|null
*
* @ORM\Column(type="string", nullable=true)
* @ORM\Column(type="string", length=255, nullable=true)
*/
private $variant;
@ -81,6 +91,16 @@ class StopEntity implements Entity, Fillable
$this->name = $name;
}
public function getGroup(): ?string
{
return $this->group;
}
public function setGroup(?string $group): void
{
$this->group = $group;
}
public function getDescription(): ?string
{
return $this->description;
@ -130,4 +150,4 @@ class StopEntity implements Entity, Fillable
{
$this->onDemand = $onDemand;
}
}
}

View File

@ -4,6 +4,7 @@ namespace App\Entity;
use App\Model\Fillable;
use App\Model\FillTrait;
use App\Service\IterableUtils;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
@ -44,12 +45,20 @@ class TrackEntity implements Entity, Fillable
/**
* Stops in track
* @var Collection
* @ORM\OneToMany(targetEntity=StopInTrack::class, fetch="LAZY", mappedBy="track", cascade={"persist"})
*
* @var TrackStopEntity[]|Collection
* @ORM\OneToMany(targetEntity=TrackStopEntity::class, fetch="LAZY", mappedBy="track", cascade={"persist"})
* @ORM\OrderBy({"order": "ASC"})
*/
private $stopsInTrack;
/**
* Final stop in this track.
*
* @var TrackStopEntity
* @ORM\OneToOne(targetEntity=TrackStopEntity::class, fetch="LAZY")
*/
private $final;
/**
* Track constructor.
@ -98,10 +107,17 @@ class TrackEntity implements Entity, Fillable
}
/**
* @param Collection $stopsInTrack
* @param iterable $stopsInTrack
*/
public function setStopsInTrack(array $stopsInTrack): void
public function setStopsInTrack(iterable $stopsInTrack): void
{
$this->stopsInTrack = new ArrayCollection($stopsInTrack);
$this->stopsInTrack = IterableUtils::toArrayCollection($stopsInTrack);
$this->final = $this->stopsInTrack->last();
}
public function getFinal(): TrackStopEntity
{
return $this->final;
}
}

View File

@ -4,32 +4,42 @@ namespace App\Entity;
use App\Model\Fillable;
use App\Model\FillTrait;
use App\Model\Referable;
use App\Model\ReferableTrait;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table("track_stop")
* @ORM\Table("track_stop", uniqueConstraints={
* @ORM\UniqueConstraint(name="stop_in_track_idx", columns={"stop_id", "track_id", "sequence"})
* })
*/
class StopInTrack implements Fillable
class TrackStopEntity implements Fillable, Referable
{
use FillTrait;
use FillTrait, ReferableEntityTrait;
/**
* Identifier for stop coming from provider
*
* @ORM\Column(type="integer")
* @ORM\Id
* @ORM\GeneratedValue
*/
private $id;
/**
* @ORM\ManyToOne(targetEntity=StopEntity::class, fetch="EAGER")
* @ORM\Id
*/
private $stop;
/**
* @ORM\ManyToOne(targetEntity=TrackEntity::class, fetch="EAGER", inversedBy="stopsInTrack")
* @ORM\Id
*/
private $track;
/**
* Order in track
* @var int
* @ORM\Id
* @ORM\Column(name="sequence", type="integer")
*/
private $order;

View File

@ -4,6 +4,7 @@ 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;
@ -14,21 +15,28 @@ use JMS\Serializer\Tests\Fixtures\Discriminator\Car;
* @ORM\Entity
* @ORM\Table("trip_stop")
*/
class TripStopEntity implements Fillable
class TripStopEntity implements Fillable, Referable
{
use FillTrait;
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")
* @ORM\Id
*/
private $stop;
/**
* @var TripEntity
* @ORM\ManyToOne(targetEntity=TripEntity::class, fetch="EAGER", inversedBy="stops")
* @ORM\Id
*/
private $trip;
@ -37,7 +45,6 @@ class TripStopEntity implements Fillable
* @var int
*
* @ORM\Column(name="sequence", type="integer")
* @ORM\Id
*/
private $order;

View File

@ -0,0 +1,34 @@
<?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

@ -0,0 +1,35 @@
<?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

@ -0,0 +1,27 @@
<?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

@ -0,0 +1,13 @@
<?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

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

View File

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

View File

@ -0,0 +1,13 @@
<?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

@ -12,4 +12,10 @@ function encapsulate($value)
default:
return [ $value ];
}
}
}
function setup($value, $callback)
{
$callback($value);
return $value;
}

View File

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

View File

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

@ -0,0 +1,103 @@
<?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

@ -0,0 +1,44 @@
<?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

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

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