├── .editorconfig ├── .gitattributes ├── .gitignore ├── admin ├── .dockerignore ├── .env ├── .gitignore ├── Dockerfile ├── docker │ └── nginx │ │ └── conf.d │ │ └── default.conf ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ └── manifest.json ├── src │ ├── App.js │ ├── App.test.js │ ├── index.js │ └── serviceWorker.js └── yarn.lock ├── api ├── .dockerignore ├── .env ├── .gitignore ├── .php_cs.dist ├── Dockerfile ├── bin │ └── console ├── composer.json ├── composer.lock ├── config │ ├── bootstrap.php │ ├── bundles.php │ ├── packages │ │ ├── api_platform.yaml │ │ ├── cache.yaml │ │ ├── dev │ │ │ ├── hautelook_alice.yaml │ │ │ ├── nelmio_alice.yaml │ │ │ ├── routing.yaml │ │ │ └── web_profiler.yaml │ │ ├── doctrine.yaml │ │ ├── doctrine_migrations.yaml │ │ ├── framework.yaml │ │ ├── mercure.yaml │ │ ├── nelmio_cors.yaml │ │ ├── prod │ │ │ ├── api_platform.yaml │ │ │ ├── doctrine.yaml │ │ │ └── routing.yaml │ │ ├── routing.yaml │ │ ├── security.yaml │ │ ├── test │ │ │ ├── framework.yaml │ │ │ ├── hautelook_alice.yaml │ │ │ ├── nelmio_alice.yaml │ │ │ ├── routing.yaml │ │ │ ├── twig.yaml │ │ │ ├── validator.yaml │ │ │ └── web_profiler.yaml │ │ ├── twig.yaml │ │ └── validator.yaml │ ├── routes.yaml │ ├── routes │ │ ├── annotations.yaml │ │ ├── api_platform.yaml │ │ └── dev │ │ │ ├── twig.yaml │ │ │ └── web_profiler.yaml │ └── services.yaml ├── docker │ ├── nginx │ │ └── conf.d │ │ │ └── default.conf │ ├── php │ │ ├── conf.d │ │ │ ├── api-platform.dev.ini │ │ │ └── api-platform.prod.ini │ │ └── docker-entrypoint.sh │ └── varnish │ │ └── conf │ │ └── default.vcl ├── fixtures │ ├── .gitignore │ └── data.yaml ├── helm │ └── api │ │ ├── .helmignore │ │ ├── Chart.yaml │ │ ├── requirements.lock │ │ ├── requirements.yaml │ │ ├── templates │ │ ├── NOTES.txt │ │ ├── _helpers.tpl │ │ ├── configmap.yaml │ │ ├── ingress.yaml │ │ ├── nginx-deployment.yaml │ │ ├── nginx-service.yaml │ │ ├── php-deployment.yaml │ │ ├── php-service.yaml │ │ ├── secrets.yaml │ │ ├── varnish-deployment.yaml │ │ └── varnish-service.yaml │ │ └── values.yaml ├── public │ ├── favicon.ico │ ├── index.php │ ├── test-max-waterfall.html │ └── test-min-waterfall.html ├── src │ ├── Controller │ │ └── .gitignore │ ├── Entity │ │ ├── .gitignore │ │ ├── Conference.php │ │ ├── Feedback.php │ │ └── Session.php │ ├── Kernel.php │ ├── Migrations │ │ ├── .gitignore │ │ └── Version20190819120152.php │ └── Repository │ │ ├── .gitignore │ │ ├── ConferenceRepository.php │ │ ├── FeedbackRepository.php │ │ └── SessionRepository.php ├── symfony.lock └── templates │ └── base.html.twig ├── client ├── .dockerignore ├── .env ├── .gitignore ├── Dockerfile ├── docker │ └── nginx │ │ └── conf.d │ │ └── default.conf ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ └── manifest.json ├── src │ ├── Welcome.js │ ├── config │ │ └── entrypoint.js │ ├── index.js │ ├── serviceWorker.js │ └── welcome.css └── yarn.lock ├── docker-compose.prod.yml ├── docker-compose.yml ├── docker └── dev-tls │ └── Dockerfile └── test-http-client.php /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | # Change these settings to your own preference 9 | indent_style = space 10 | indent_size = 4 11 | 12 | # We recommend you to keep these unchanged 13 | end_of_line = lf 14 | charset = utf-8 15 | trim_trailing_whitespace = true 16 | insert_final_newline = true 17 | 18 | [*.feature] 19 | indent_style = space 20 | indent_size = 2 21 | 22 | [*.js] 23 | indent_style = space 24 | indent_size = 2 25 | 26 | [*.json] 27 | indent_style = space 28 | indent_size = 2 29 | 30 | [*.md] 31 | trim_trailing_whitespace = false 32 | 33 | [*.php] 34 | indent_style = space 35 | indent_size = 4 36 | 37 | [*.sh] 38 | indent_style = tab 39 | indent_size = 4 40 | 41 | [*.vcl] 42 | indent_style = space 43 | indent_size = 2 44 | 45 | [*.xml] 46 | indent_style = space 47 | indent_size = 4 48 | 49 | [*.{yaml,yml}] 50 | indent_style = space 51 | indent_size = 4 52 | trim_trailing_whitespace = false 53 | 54 | [.gitmodules] 55 | indent_style = tab 56 | indent_size = 4 57 | 58 | [.php_cs{,.dist}] 59 | indent_style = space 60 | indent_size = 4 61 | 62 | [.travis.yml] 63 | indent_style = space 64 | indent_size = 2 65 | 66 | [composer.json] 67 | indent_style = space 68 | indent_size = 4 69 | 70 | [docker-compose{,.*}.{yaml,yml}] 71 | indent_style = space 72 | indent_size = 2 73 | 74 | [Dockerfile] 75 | indent_style = tab 76 | indent_size = 4 77 | 78 | [package.json] 79 | indent_style = space 80 | indent_size = 2 81 | 82 | [phpunit.xml{,.dist}] 83 | indent_style = space 84 | indent_size = 4 85 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | 3 | *.conf text eol=lf 4 | *.html text eol=lf 5 | *.ini text eol=lf 6 | *.js text eol=lf 7 | *.json text eol=lf 8 | *.md text eol=lf 9 | *.php text eol=lf 10 | *.sh text eol=lf 11 | *.yaml text eol=lf 12 | *.yml text eol=lf 13 | bin/console text eol=lf 14 | composer.lock text eol=lf merge=ours 15 | 16 | *.ico binary 17 | *.png binary 18 | 19 | .github export-ignore 20 | .travis.yml export-ignore 21 | LICENSE export-ignore 22 | README.md export-ignore 23 | update-deps.sh export-ignore 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.env 2 | /docker-compose.override.yaml 3 | /docker-compose.override.yml 4 | -------------------------------------------------------------------------------- /admin/.dockerignore: -------------------------------------------------------------------------------- 1 | **/*.log 2 | **/*.md 3 | **/._* 4 | **/.dockerignore 5 | **/.DS_Store 6 | **/.git/ 7 | **/.gitattributes 8 | **/.gitignore 9 | **/.gitmodules 10 | **/docker-compose.*.yaml 11 | **/docker-compose.*.yml 12 | **/docker-compose.yaml 13 | **/docker-compose.yml 14 | **/Dockerfile 15 | **/Thumbs.db 16 | .editorconfig 17 | .env.*.local 18 | .env.local 19 | build/ 20 | node_modules/ 21 | -------------------------------------------------------------------------------- /admin/.env: -------------------------------------------------------------------------------- 1 | REACT_APP_API_ENTRYPOINT=https://localhost:8443 2 | -------------------------------------------------------------------------------- /admin/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | -------------------------------------------------------------------------------- /admin/Dockerfile: -------------------------------------------------------------------------------- 1 | # https://docs.docker.com/develop/develop-images/multistage-build/#stop-at-a-specific-build-stage 2 | # https://docs.docker.com/compose/compose-file/#target 3 | 4 | 5 | # https://docs.docker.com/engine/reference/builder/#understand-how-arg-and-from-interact 6 | ARG NODE_VERSION=13 7 | ARG NGINX_VERSION=1.17 8 | 9 | 10 | # "development" stage 11 | FROM node:${NODE_VERSION}-alpine AS api_platform_admin_development 12 | 13 | WORKDIR /usr/src/admin 14 | 15 | # prevent the reinstallation of node modules at every changes in the source code 16 | COPY package.json yarn.lock ./ 17 | RUN set -eux; \ 18 | apk add --no-cache --virtual .gyp \ 19 | g++ \ 20 | make \ 21 | python \ 22 | ; \ 23 | yarn install; \ 24 | apk del .gyp 25 | 26 | COPY . ./ 27 | 28 | VOLUME /usr/src/admin/node_modules 29 | 30 | ENV HTTPS true 31 | 32 | CMD ["yarn", "start"] 33 | 34 | 35 | # "build" stage 36 | # depends on the "development" stage above 37 | FROM api_platform_admin_development AS api_platform_admin_build 38 | 39 | ARG REACT_APP_API_ENTRYPOINT 40 | 41 | RUN set -eux; \ 42 | yarn build 43 | 44 | 45 | # "nginx" stage 46 | # depends on the "build" stage above 47 | FROM nginx:${NGINX_VERSION}-alpine AS api_platform_admin_nginx 48 | 49 | COPY docker/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf 50 | 51 | WORKDIR /usr/src/admin/build 52 | 53 | COPY --from=api_platform_admin_build /usr/src/admin/build ./ 54 | -------------------------------------------------------------------------------- /admin/docker/nginx/conf.d/default.conf: -------------------------------------------------------------------------------- 1 | server { 2 | root /usr/src/admin/build; 3 | 4 | location / { 5 | try_files $uri /index.html; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /admin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "admin", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@api-platform/admin": "^1.0.0", 7 | "@babel/runtime": "7.0.0-beta.55", 8 | "react": "^16.3.0", 9 | "react-dom": "^16.3.0", 10 | "react-scripts": "^3.1.2" 11 | }, 12 | "scripts": { 13 | "start": "react-scripts start", 14 | "build": "react-scripts build", 15 | "test": "react-scripts test", 16 | "eject": "react-scripts eject" 17 | }, 18 | "browserslist": [ 19 | ">0.2%", 20 | "not dead", 21 | "not ie <= 11", 22 | "not op_mini all" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /admin/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dunglas/demo-vulcain-api-platform/f0d66cf06827f63d5c9cfd3fede2ecedd44483f8/admin/public/favicon.ico -------------------------------------------------------------------------------- /admin/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | API Platform Admin 23 | 24 | 25 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /admin/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "API Platform Admin", 3 | "name": "Admin", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /admin/src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { HydraAdmin } from '@api-platform/admin'; 3 | 4 | export default () => ; 5 | -------------------------------------------------------------------------------- /admin/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /admin/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | import * as serviceWorker from './serviceWorker'; 5 | 6 | ReactDOM.render(, document.getElementById('root')); 7 | 8 | // If you want your app to work offline and load faster, you can change 9 | // unregister() to register() below. Note this comes with some pitfalls. 10 | // Learn more about service workers: http://bit.ly/CRA-PWA 11 | serviceWorker.unregister(); 12 | -------------------------------------------------------------------------------- /admin/src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read http://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.1/8 is considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit http://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See http://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl) 104 | .then(response => { 105 | // Ensure service worker exists, and that we really are getting a JS file. 106 | const contentType = response.headers.get('content-type'); 107 | if ( 108 | response.status === 404 || 109 | (contentType != null && contentType.indexOf('javascript') === -1) 110 | ) { 111 | // No service worker found. Probably a different app. Reload the page. 112 | navigator.serviceWorker.ready.then(registration => { 113 | registration.unregister().then(() => { 114 | window.location.reload(); 115 | }); 116 | }); 117 | } else { 118 | // Service worker found. Proceed as normal. 119 | registerValidSW(swUrl, config); 120 | } 121 | }) 122 | .catch(() => { 123 | console.log( 124 | 'No internet connection found. App is running in offline mode.' 125 | ); 126 | }); 127 | } 128 | 129 | export function unregister() { 130 | if ('serviceWorker' in navigator) { 131 | navigator.serviceWorker.ready.then(registration => { 132 | registration.unregister(); 133 | }); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /api/.dockerignore: -------------------------------------------------------------------------------- 1 | **/*.log 2 | **/*.md 3 | **/*.php~ 4 | **/._* 5 | **/.dockerignore 6 | **/.DS_Store 7 | **/.git/ 8 | **/.gitattributes 9 | **/.gitignore 10 | **/.gitmodules 11 | **/docker-compose.*.yaml 12 | **/docker-compose.*.yml 13 | **/docker-compose.yaml 14 | **/docker-compose.yml 15 | **/Dockerfile 16 | **/Thumbs.db 17 | .editorconfig 18 | .env.*.local 19 | .env.local 20 | .env.local.php 21 | .php_cs.cache 22 | bin/* 23 | !bin/console 24 | build/ 25 | docker/db/data/ 26 | helm/ 27 | public/bundles/ 28 | var/ 29 | vendor/ 30 | -------------------------------------------------------------------------------- /api/.env: -------------------------------------------------------------------------------- 1 | # In all environments, the following files are loaded if they exist, 2 | # the latter taking precedence over the former: 3 | # 4 | # * .env contains default values for the environment variables needed by the app 5 | # * .env.local uncommitted file with local overrides 6 | # * .env.$APP_ENV committed environment-specific defaults 7 | # * .env.$APP_ENV.local uncommitted environment-specific overrides 8 | # 9 | # Real environment variables win over .env files. 10 | # 11 | # DO NOT DEFINE PRODUCTION SECRETS IN THIS FILE NOR IN ANY OTHER COMMITTED FILES. 12 | # 13 | # Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2). 14 | # https://symfony.com/doc/current/best_practices/configuration.html#infrastructure-related-configuration 15 | 16 | ###> doctrine/doctrine-bundle ### 17 | # Format described at https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url 18 | # For an SQLite database, use: "sqlite:///%kernel.project_dir%/var/data.db" 19 | # For a MySQL database, use: "mysql://db_user:db_password@127.0.0.1:3306/db_name?serverVersion=5.7" 20 | # IMPORTANT: You MUST configure your db driver and server version, either here or in config/packages/doctrine.yaml 21 | DATABASE_URL=postgres://api-platform:!ChangeMe!@db/api?server_version=12 22 | ###< doctrine/doctrine-bundle ### 23 | 24 | ###> nelmio/cors-bundle ### 25 | CORS_ALLOW_ORIGIN=^https?://(localhost|127\.0\.0\.1)(:[0-9]+)?$ 26 | ###< nelmio/cors-bundle ### 27 | 28 | ###> symfony/mercure-bundle ### 29 | # See https://symfony.com/doc/current/mercure.html#configuration 30 | MERCURE_PUBLISH_URL=http://mercure/.well-known/mercure 31 | # The default token is signed with the secret key: !ChangeMe! 32 | MERCURE_JWT_TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXJjdXJlIjp7InB1Ymxpc2giOltdfX0.Oo0yg7y4yMa1vr_bziltxuTCqb8JVHKxp-f_FwwOim0 33 | ###< symfony/mercure-bundle ### 34 | 35 | ###> symfony/framework-bundle ### 36 | APP_ENV=dev 37 | APP_SECRET=!ChangeMe! 38 | TRUSTED_PROXIES=10.0.0.0/8,172.16.0.0/12,192.168.0.0/16 39 | TRUSTED_HOSTS='^localhost|api$' 40 | ###< symfony/framework-bundle ### 41 | 42 | # API Platform distribution 43 | MERCURE_SUBSCRIBE_URL=https://localhost:1337/.well-known/mercure 44 | VARNISH_URL=http://cache-proxy 45 | -------------------------------------------------------------------------------- /api/.gitignore: -------------------------------------------------------------------------------- 1 | /docker/db/data 2 | /helm/api/charts 3 | 4 | ###> symfony/framework-bundle ### 5 | /.env.local 6 | /.env.local.php 7 | /.env.*.local 8 | /public/bundles/ 9 | /var/ 10 | /vendor/ 11 | ###< symfony/framework-bundle ### 12 | 13 | ###> friendsofphp/php-cs-fixer ### 14 | /.php_cs 15 | /.php_cs.cache 16 | ###< friendsofphp/php-cs-fixer ### 17 | -------------------------------------------------------------------------------- /api/.php_cs.dist: -------------------------------------------------------------------------------- 1 | in(__DIR__) 5 | ->exclude('var') 6 | ; 7 | 8 | return PhpCsFixer\Config::create() 9 | ->setRules([ 10 | '@Symfony' => true, 11 | 'array_syntax' => ['syntax' => 'short'], 12 | ]) 13 | ->setFinder($finder) 14 | ; 15 | -------------------------------------------------------------------------------- /api/Dockerfile: -------------------------------------------------------------------------------- 1 | # the different stages of this Dockerfile are meant to be built into separate images 2 | # https://docs.docker.com/develop/develop-images/multistage-build/#stop-at-a-specific-build-stage 3 | # https://docs.docker.com/compose/compose-file/#target 4 | 5 | 6 | # https://docs.docker.com/engine/reference/builder/#understand-how-arg-and-from-interact 7 | ARG PHP_VERSION=7.3 8 | ARG NGINX_VERSION=1.17 9 | ARG VARNISH_VERSION=6.3 10 | 11 | 12 | # "php" stage 13 | FROM php:${PHP_VERSION}-fpm-alpine AS api_platform_php 14 | 15 | # persistent / runtime deps 16 | RUN apk add --no-cache \ 17 | acl \ 18 | file \ 19 | gettext \ 20 | git \ 21 | ; 22 | 23 | ARG APCU_VERSION=5.1.18 24 | RUN set -eux; \ 25 | apk add --no-cache --virtual .build-deps \ 26 | $PHPIZE_DEPS \ 27 | icu-dev \ 28 | libzip-dev \ 29 | postgresql-dev \ 30 | zlib-dev \ 31 | ; \ 32 | \ 33 | docker-php-ext-configure zip --with-libzip; \ 34 | docker-php-ext-install -j$(nproc) \ 35 | intl \ 36 | pdo_pgsql \ 37 | zip \ 38 | ; \ 39 | pecl install \ 40 | apcu-${APCU_VERSION} \ 41 | ; \ 42 | pecl clear-cache; \ 43 | docker-php-ext-enable \ 44 | apcu \ 45 | opcache \ 46 | ; \ 47 | \ 48 | runDeps="$( \ 49 | scanelf --needed --nobanner --format '%n#p' --recursive /usr/local/lib/php/extensions \ 50 | | tr ',' '\n' \ 51 | | sort -u \ 52 | | awk 'system("[ -e /usr/local/lib/" $1 " ]") == 0 { next } { print "so:" $1 }' \ 53 | )"; \ 54 | apk add --no-cache --virtual .api-phpexts-rundeps $runDeps; \ 55 | \ 56 | apk del .build-deps 57 | 58 | COPY --from=composer:latest /usr/bin/composer /usr/bin/composer 59 | RUN ln -s $PHP_INI_DIR/php.ini-production $PHP_INI_DIR/php.ini 60 | COPY docker/php/conf.d/api-platform.prod.ini $PHP_INI_DIR/conf.d/api-platform.ini 61 | 62 | # Workaround to allow using PHPUnit 8 with Symfony 4.3 63 | ENV SYMFONY_PHPUNIT_VERSION=8.3 64 | 65 | # https://getcomposer.org/doc/03-cli.md#composer-allow-superuser 66 | ENV COMPOSER_ALLOW_SUPERUSER=1 67 | # install Symfony Flex globally to speed up download of Composer packages (parallelized prefetching) 68 | RUN set -eux; \ 69 | composer global require "symfony/flex" --prefer-dist --no-progress --no-suggest --classmap-authoritative; \ 70 | composer clear-cache 71 | ENV PATH="${PATH}:/root/.composer/vendor/bin" 72 | 73 | WORKDIR /srv/api 74 | 75 | # build for production 76 | ARG APP_ENV=prod 77 | 78 | # prevent the reinstallation of vendors at every changes in the source code 79 | COPY composer.json composer.lock symfony.lock ./ 80 | RUN set -eux; \ 81 | composer install --prefer-dist --no-dev --no-scripts --no-progress --no-suggest; \ 82 | composer clear-cache 83 | 84 | # do not use .env files in production 85 | COPY .env ./ 86 | RUN composer dump-env prod; \ 87 | rm .env 88 | 89 | # copy only specifically what we need 90 | COPY bin bin/ 91 | COPY config config/ 92 | COPY public public/ 93 | COPY src src/ 94 | 95 | RUN set -eux; \ 96 | mkdir -p var/cache var/log; \ 97 | composer dump-autoload --classmap-authoritative --no-dev; \ 98 | composer run-script --no-dev post-install-cmd; \ 99 | chmod +x bin/console; sync 100 | VOLUME /srv/api/var 101 | 102 | COPY docker/php/docker-entrypoint.sh /usr/local/bin/docker-entrypoint 103 | RUN chmod +x /usr/local/bin/docker-entrypoint 104 | 105 | ENTRYPOINT ["docker-entrypoint"] 106 | CMD ["php-fpm"] 107 | 108 | 109 | # "nginx" stage 110 | # depends on the "php" stage above 111 | FROM nginx:${NGINX_VERSION}-alpine AS api_platform_nginx 112 | 113 | COPY docker/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf 114 | 115 | WORKDIR /srv/api/public 116 | 117 | COPY --from=api_platform_php /srv/api/public ./ 118 | 119 | 120 | # "varnish" stage 121 | # does not depend on any of the above stages, but placed here to keep everything in one Dockerfile 122 | FROM varnish:${VARNISH_VERSION} AS api_platform_varnish 123 | 124 | COPY docker/varnish/conf/default.vcl /etc/varnish/default.vcl 125 | 126 | CMD ["varnishd", "-F", "-f", "/etc/varnish/default.vcl", "-p", "http_resp_hdr_len=65536", "-p", "http_resp_size=98304"] 127 | -------------------------------------------------------------------------------- /api/bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | getParameterOption(['--env', '-e'], null, true)) { 23 | putenv('APP_ENV='.$_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = $env); 24 | } 25 | 26 | if ($input->hasParameterOption('--no-debug', true)) { 27 | putenv('APP_DEBUG='.$_SERVER['APP_DEBUG'] = $_ENV['APP_DEBUG'] = '0'); 28 | } 29 | 30 | require dirname(__DIR__).'/config/bootstrap.php'; 31 | 32 | if ($_SERVER['APP_DEBUG']) { 33 | umask(0000); 34 | 35 | if (class_exists(Debug::class)) { 36 | Debug::enable(); 37 | } 38 | } 39 | 40 | $kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']); 41 | $application = new Application($kernel); 42 | $application->run($input); 43 | -------------------------------------------------------------------------------- /api/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "license": "MIT", 3 | "require": { 4 | "php": "^7.1.3", 5 | "ext-ctype": "*", 6 | "ext-iconv": "*", 7 | "api-platform/api-pack": "^1.1", 8 | "api-platform/core": "dev-master", 9 | "doctrine/doctrine-migrations-bundle": "^2.0", 10 | "guzzlehttp/guzzle": "^6.3", 11 | "symfony/console": "4.3.*", 12 | "symfony/dotenv": "4.3.*", 13 | "symfony/flex": "^1.1", 14 | "symfony/framework-bundle": "4.3.*", 15 | "symfony/mercure-bundle": "^0.2", 16 | "symfony/yaml": "4.3.*" 17 | }, 18 | "require-dev": { 19 | "api-platform/schema-generator": "^2.1", 20 | "hautelook/alice-bundle": "^2.5", 21 | "symfony/maker-bundle": "^1.11", 22 | "symfony/profiler-pack": "^1.0" 23 | }, 24 | "conflict": { 25 | "symfony/symfony": "*" 26 | }, 27 | "replace": { 28 | "paragonie/random_compat": "2.*", 29 | "symfony/polyfill-ctype": "*", 30 | "symfony/polyfill-iconv": "*", 31 | "symfony/polyfill-php56": "*", 32 | "symfony/polyfill-php70": "*", 33 | "symfony/polyfill-php71": "*" 34 | }, 35 | "autoload": { 36 | "psr-4": { 37 | "App\\": "src/" 38 | } 39 | }, 40 | "autoload-dev": { 41 | "psr-4": { 42 | "App\\Tests\\": "tests/" 43 | } 44 | }, 45 | "config": { 46 | "preferred-install": { 47 | "*": "dist" 48 | }, 49 | "sort-packages": true 50 | }, 51 | "scripts": { 52 | "auto-scripts": { 53 | "cache:clear": "symfony-cmd", 54 | "assets:install %PUBLIC_DIR%": "symfony-cmd" 55 | }, 56 | "post-install-cmd": [ 57 | "@auto-scripts" 58 | ], 59 | "post-update-cmd": [ 60 | "@auto-scripts" 61 | ] 62 | }, 63 | "extra": { 64 | "symfony": { 65 | "allow-contrib": false, 66 | "require": "4.3.*" 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /api/config/bootstrap.php: -------------------------------------------------------------------------------- 1 | =1.2) 9 | if (is_array($env = @include dirname(__DIR__).'/.env.local.php') && ($_SERVER['APP_ENV'] ?? $_ENV['APP_ENV'] ?? $env['APP_ENV']) === $env['APP_ENV']) { 10 | foreach ($env as $k => $v) { 11 | $_ENV[$k] = $_ENV[$k] ?? (isset($_SERVER[$k]) && 0 !== strpos($k, 'HTTP_') ? $_SERVER[$k] : $v); 12 | } 13 | } elseif (!class_exists(Dotenv::class)) { 14 | throw new RuntimeException('Please run "composer require symfony/dotenv" to load the ".env" files configuring the application.'); 15 | } else { 16 | // load all the .env files 17 | (new Dotenv(false))->loadEnv(dirname(__DIR__).'/.env'); 18 | } 19 | 20 | $_SERVER += $_ENV; 21 | $_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = ($_SERVER['APP_ENV'] ?? $_ENV['APP_ENV'] ?? null) ?: 'dev'; 22 | $_SERVER['APP_DEBUG'] = $_SERVER['APP_DEBUG'] ?? $_ENV['APP_DEBUG'] ?? 'prod' !== $_SERVER['APP_ENV']; 23 | $_SERVER['APP_DEBUG'] = $_ENV['APP_DEBUG'] = (int) $_SERVER['APP_DEBUG'] || filter_var($_SERVER['APP_DEBUG'], FILTER_VALIDATE_BOOLEAN) ? '1' : '0'; 24 | -------------------------------------------------------------------------------- /api/config/bundles.php: -------------------------------------------------------------------------------- 1 | ['all' => true], 5 | Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true], 6 | Symfony\Bundle\MercureBundle\MercureBundle::class => ['all' => true], 7 | Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true], 8 | Doctrine\Bundle\DoctrineCacheBundle\DoctrineCacheBundle::class => ['all' => true], 9 | Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true], 10 | ApiPlatform\Core\Bridge\Symfony\Bundle\ApiPlatformBundle::class => ['all' => true], 11 | Nelmio\CorsBundle\NelmioCorsBundle::class => ['all' => true], 12 | Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true], 13 | Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true], 14 | Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true], 15 | Nelmio\Alice\Bridge\Symfony\NelmioAliceBundle::class => ['dev' => true, 'test' => true], 16 | Fidry\AliceDataFixtures\Bridge\Symfony\FidryAliceDataFixturesBundle::class => ['dev' => true, 'test' => true], 17 | Hautelook\AliceBundle\HautelookAliceBundle::class => ['dev' => true, 'test' => true], 18 | ]; 19 | -------------------------------------------------------------------------------- /api/config/packages/api_platform.yaml: -------------------------------------------------------------------------------- 1 | api_platform: 2 | title: Hello API Platform 3 | version: 1.0.0 4 | mapping: 5 | paths: ['%kernel.project_dir%/src/Entity'] 6 | patch_formats: 7 | json: ['application/merge-patch+json'] 8 | swagger: 9 | versions: [3] 10 | # Mercure integration, remove if unwanted 11 | mercure: 12 | hub_url: '%env(MERCURE_SUBSCRIBE_URL)%' 13 | defaults: 14 | normalization_context: 15 | iri_only: true 16 | #itemOperations: [get] 17 | #collectionOperations: [get] 18 | -------------------------------------------------------------------------------- /api/config/packages/cache.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | cache: 3 | # Put the unique name of your app here: the prefix seed 4 | # is used to compute stable namespaces for cache keys. 5 | #prefix_seed: your_vendor_name/app_name 6 | 7 | # The app cache caches to the filesystem by default. 8 | # Other options include: 9 | 10 | # Redis 11 | #app: cache.adapter.redis 12 | #default_redis_provider: redis://localhost 13 | 14 | # APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues) 15 | #app: cache.adapter.apcu 16 | 17 | # Namespaced pools use the above "app" backend by default 18 | #pools: 19 | #my.dedicated.cache: null 20 | -------------------------------------------------------------------------------- /api/config/packages/dev/hautelook_alice.yaml: -------------------------------------------------------------------------------- 1 | hautelook_alice: 2 | fixtures_path: fixtures 3 | -------------------------------------------------------------------------------- /api/config/packages/dev/nelmio_alice.yaml: -------------------------------------------------------------------------------- 1 | nelmio_alice: 2 | functions_blacklist: 3 | - 'current' 4 | - 'shuffle' 5 | - 'date' 6 | - 'time' 7 | - 'file' 8 | - 'md5' 9 | - 'sha1' 10 | -------------------------------------------------------------------------------- /api/config/packages/dev/routing.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | router: 3 | strict_requirements: true 4 | -------------------------------------------------------------------------------- /api/config/packages/dev/web_profiler.yaml: -------------------------------------------------------------------------------- 1 | web_profiler: 2 | toolbar: true 3 | intercept_redirects: false 4 | 5 | framework: 6 | profiler: { only_exceptions: false } 7 | -------------------------------------------------------------------------------- /api/config/packages/doctrine.yaml: -------------------------------------------------------------------------------- 1 | doctrine: 2 | dbal: 3 | url: '%env(resolve:DATABASE_URL)%' 4 | 5 | # IMPORTANT: You MUST configure your db driver and server version, 6 | # either here or in the DATABASE_URL env var (see .env file) 7 | driver: 'postgresql' 8 | server_version: '12' 9 | orm: 10 | auto_generate_proxy_classes: true 11 | naming_strategy: doctrine.orm.naming_strategy.underscore 12 | auto_mapping: true 13 | mappings: 14 | App: 15 | is_bundle: false 16 | type: annotation 17 | dir: '%kernel.project_dir%/src/Entity' 18 | prefix: 'App\Entity' 19 | alias: App 20 | -------------------------------------------------------------------------------- /api/config/packages/doctrine_migrations.yaml: -------------------------------------------------------------------------------- 1 | doctrine_migrations: 2 | dir_name: '%kernel.project_dir%/src/Migrations' 3 | # namespace is arbitrary but should be different from App\Migrations 4 | # as migrations classes should NOT be autoloaded 5 | namespace: DoctrineMigrations 6 | -------------------------------------------------------------------------------- /api/config/packages/framework.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | secret: '%env(APP_SECRET)%' 3 | #csrf_protection: true 4 | #http_method_override: true 5 | 6 | # Enables session support. Note that the session will ONLY be started if you read or write from it. 7 | # Remove or comment this section to explicitly disable session support. 8 | session: 9 | handler_id: null 10 | cookie_secure: auto 11 | cookie_samesite: lax 12 | 13 | #esi: true 14 | #fragments: true 15 | php_errors: 16 | log: true 17 | -------------------------------------------------------------------------------- /api/config/packages/mercure.yaml: -------------------------------------------------------------------------------- 1 | mercure: 2 | enable_profiler: '%kernel.debug%' 3 | hubs: 4 | default: 5 | url: '%env(MERCURE_PUBLISH_URL)%' 6 | jwt: '%env(MERCURE_JWT_TOKEN)%' 7 | -------------------------------------------------------------------------------- /api/config/packages/nelmio_cors.yaml: -------------------------------------------------------------------------------- 1 | nelmio_cors: 2 | defaults: 3 | origin_regex: true 4 | allow_origin: ['%env(CORS_ALLOW_ORIGIN)%'] 5 | allow_methods: ['GET', 'OPTIONS', 'POST', 'PUT', 'PATCH', 'DELETE'] 6 | allow_headers: ['Content-Type', 'Authorization'] 7 | expose_headers: ['Link'] 8 | max_age: 3600 9 | paths: 10 | '^/': null 11 | -------------------------------------------------------------------------------- /api/config/packages/prod/api_platform.yaml: -------------------------------------------------------------------------------- 1 | api_platform: 2 | # Varnish integration, remove if unwanted 3 | http_cache: 4 | invalidation: 5 | enabled: true 6 | varnish_urls: ['%env(VARNISH_URL)%'] 7 | max_age: 0 8 | shared_max_age: 3600 9 | vary: ['Content-Type', 'Authorization', 'Origin'] 10 | public: true 11 | -------------------------------------------------------------------------------- /api/config/packages/prod/doctrine.yaml: -------------------------------------------------------------------------------- 1 | doctrine: 2 | orm: 3 | auto_generate_proxy_classes: false 4 | metadata_cache_driver: 5 | type: service 6 | id: doctrine.system_cache_provider 7 | query_cache_driver: 8 | type: service 9 | id: doctrine.system_cache_provider 10 | result_cache_driver: 11 | type: service 12 | id: doctrine.result_cache_provider 13 | 14 | services: 15 | doctrine.result_cache_provider: 16 | class: Symfony\Component\Cache\DoctrineProvider 17 | public: false 18 | arguments: 19 | - '@doctrine.result_cache_pool' 20 | doctrine.system_cache_provider: 21 | class: Symfony\Component\Cache\DoctrineProvider 22 | public: false 23 | arguments: 24 | - '@doctrine.system_cache_pool' 25 | 26 | framework: 27 | cache: 28 | pools: 29 | doctrine.result_cache_pool: 30 | adapter: cache.app 31 | doctrine.system_cache_pool: 32 | adapter: cache.system 33 | -------------------------------------------------------------------------------- /api/config/packages/prod/routing.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | router: 3 | strict_requirements: null 4 | -------------------------------------------------------------------------------- /api/config/packages/routing.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | router: 3 | utf8: true 4 | -------------------------------------------------------------------------------- /api/config/packages/security.yaml: -------------------------------------------------------------------------------- 1 | security: 2 | # https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers 3 | providers: 4 | in_memory: { memory: null } 5 | firewalls: 6 | dev: 7 | pattern: ^/(_(profiler|wdt)|css|images|js)/ 8 | security: false 9 | main: 10 | anonymous: true 11 | 12 | # activate different ways to authenticate 13 | # https://symfony.com/doc/current/security.html#firewalls-authentication 14 | 15 | # https://symfony.com/doc/current/security/impersonating_user.html 16 | # switch_user: true 17 | 18 | # Easy way to control access for large sections of your site 19 | # Note: Only the *first* access control that matches will be used 20 | access_control: 21 | # - { path: ^/admin, roles: ROLE_ADMIN } 22 | # - { path: ^/profile, roles: ROLE_USER } 23 | -------------------------------------------------------------------------------- /api/config/packages/test/framework.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | test: true 3 | session: 4 | storage_id: session.storage.mock_file 5 | -------------------------------------------------------------------------------- /api/config/packages/test/hautelook_alice.yaml: -------------------------------------------------------------------------------- 1 | imports: 2 | - { resource: ../dev/hautelook_alice.yaml } 3 | -------------------------------------------------------------------------------- /api/config/packages/test/nelmio_alice.yaml: -------------------------------------------------------------------------------- 1 | imports: 2 | - { resource: ../dev/nelmio_alice.yaml } 3 | -------------------------------------------------------------------------------- /api/config/packages/test/routing.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | router: 3 | strict_requirements: true 4 | -------------------------------------------------------------------------------- /api/config/packages/test/twig.yaml: -------------------------------------------------------------------------------- 1 | twig: 2 | strict_variables: true 3 | -------------------------------------------------------------------------------- /api/config/packages/test/validator.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | validation: 3 | not_compromised_password: false 4 | -------------------------------------------------------------------------------- /api/config/packages/test/web_profiler.yaml: -------------------------------------------------------------------------------- 1 | web_profiler: 2 | toolbar: false 3 | intercept_redirects: false 4 | 5 | framework: 6 | profiler: { collect: false } 7 | -------------------------------------------------------------------------------- /api/config/packages/twig.yaml: -------------------------------------------------------------------------------- 1 | twig: 2 | default_path: '%kernel.project_dir%/templates' 3 | debug: '%kernel.debug%' 4 | strict_variables: '%kernel.debug%' 5 | -------------------------------------------------------------------------------- /api/config/packages/validator.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | validation: 3 | email_validation_mode: html5 4 | 5 | # Enables validator auto-mapping support. 6 | # For instance, basic validation constraints will be inferred from Doctrine's metadata. 7 | #auto_mapping: 8 | # App\Entity\: [] 9 | -------------------------------------------------------------------------------- /api/config/routes.yaml: -------------------------------------------------------------------------------- 1 | #index: 2 | # path: / 3 | # controller: App\Controller\DefaultController::index 4 | -------------------------------------------------------------------------------- /api/config/routes/annotations.yaml: -------------------------------------------------------------------------------- 1 | controllers: 2 | resource: ../../src/Controller/ 3 | type: annotation 4 | -------------------------------------------------------------------------------- /api/config/routes/api_platform.yaml: -------------------------------------------------------------------------------- 1 | api_platform: 2 | resource: . 3 | type: api_platform 4 | -------------------------------------------------------------------------------- /api/config/routes/dev/twig.yaml: -------------------------------------------------------------------------------- 1 | _errors: 2 | resource: '@TwigBundle/Resources/config/routing/errors.xml' 3 | prefix: /_error 4 | -------------------------------------------------------------------------------- /api/config/routes/dev/web_profiler.yaml: -------------------------------------------------------------------------------- 1 | web_profiler_wdt: 2 | resource: '@WebProfilerBundle/Resources/config/routing/wdt.xml' 3 | prefix: /_wdt 4 | 5 | web_profiler_profiler: 6 | resource: '@WebProfilerBundle/Resources/config/routing/profiler.xml' 7 | prefix: /_profiler 8 | -------------------------------------------------------------------------------- /api/config/services.yaml: -------------------------------------------------------------------------------- 1 | # This file is the entry point to configure your own services. 2 | # Files in the packages/ subdirectory configure your dependencies. 3 | 4 | # Put parameters here that don't need to change on each machine where the app is deployed 5 | # https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration 6 | parameters: 7 | 8 | services: 9 | # default configuration for services in *this* file 10 | _defaults: 11 | autowire: true # Automatically injects dependencies in your services. 12 | autoconfigure: true # Automatically registers your services as commands, event subscribers, etc. 13 | 14 | # makes classes in src/ available to be used as services 15 | # this creates a service per class whose id is the fully-qualified class name 16 | App\: 17 | resource: '../src/*' 18 | exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}' 19 | 20 | # controllers are imported separately to make sure services can be injected 21 | # as action arguments even if you don't extend any base controller class 22 | App\Controller\: 23 | resource: '../src/Controller' 24 | tags: ['controller.service_arguments'] 25 | 26 | # add more service definitions when explicit configuration is needed 27 | # please note that last definitions always *replace* previous ones 28 | -------------------------------------------------------------------------------- /api/docker/nginx/conf.d/default.conf: -------------------------------------------------------------------------------- 1 | server { 2 | root /srv/api/public; 3 | 4 | location / { 5 | # try to serve file directly, fallback to index.php 6 | try_files $uri /index.php$is_args$args; 7 | } 8 | 9 | location ~ ^/index\.php(/|$) { 10 | # Comment the next line and uncomment the next to enable dynamic resolution (incompatible with Kubernetes) 11 | fastcgi_pass php:9000; 12 | #resolver 127.0.0.11; 13 | #set $upstream_host php; 14 | #fastcgi_pass $upstream_host:9000; 15 | 16 | # Increase the buffer size to handle large cache invalidation headers 17 | fastcgi_buffer_size 32k; 18 | fastcgi_buffers 32 4k; 19 | 20 | fastcgi_split_path_info ^(.+\.php)(/.*)$; 21 | include fastcgi_params; 22 | 23 | # When you are using symlinks to link the document root to the 24 | # current version of your application, you should pass the real 25 | # application path instead of the path to the symlink to PHP 26 | # FPM. 27 | # Otherwise, PHP's OPcache may not properly detect changes to 28 | # your PHP files (see https://github.com/zendtech/ZendOptimizerPlus/issues/126 29 | # for more information). 30 | fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; 31 | fastcgi_param DOCUMENT_ROOT $realpath_root; 32 | # Prevents URIs that include the front controller. This will 404: 33 | # http://domain.tld/index.php/some-path 34 | # Remove the internal directive to allow URIs like this 35 | internal; 36 | } 37 | 38 | # return 404 for all other php files not matching the front controller 39 | # this prevents access to other php files you don't want to be accessible. 40 | location ~ \.php$ { 41 | return 404; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /api/docker/php/conf.d/api-platform.dev.ini: -------------------------------------------------------------------------------- 1 | apc.enable_cli = 1 2 | date.timezone = UTC 3 | session.auto_start = Off 4 | short_open_tag = Off 5 | 6 | # https://symfony.com/doc/current/performance.html 7 | opcache.interned_strings_buffer = 16 8 | opcache.max_accelerated_files = 20000 9 | opcache.memory_consumption = 256 10 | realpath_cache_size = 4096K 11 | realpath_cache_ttl = 600 12 | -------------------------------------------------------------------------------- /api/docker/php/conf.d/api-platform.prod.ini: -------------------------------------------------------------------------------- 1 | apc.enable_cli = 1 2 | date.timezone = UTC 3 | session.auto_start = Off 4 | short_open_tag = Off 5 | 6 | # https://symfony.com/doc/current/performance.html 7 | opcache.interned_strings_buffer = 16 8 | opcache.max_accelerated_files = 20000 9 | opcache.memory_consumption = 256 10 | opcache.validate_timestamps = 0 11 | realpath_cache_size = 4096K 12 | realpath_cache_ttl = 600 13 | -------------------------------------------------------------------------------- /api/docker/php/docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | # first arg is `-f` or `--some-option` 5 | if [ "${1#-}" != "$1" ]; then 6 | set -- php-fpm "$@" 7 | fi 8 | 9 | if [ "$1" = 'php-fpm' ] || [ "$1" = 'php' ] || [ "$1" = 'bin/console' ]; then 10 | PHP_INI_RECOMMENDED="$PHP_INI_DIR/php.ini-production" 11 | if [ "$APP_ENV" != 'prod' ]; then 12 | PHP_INI_RECOMMENDED="$PHP_INI_DIR/php.ini-development" 13 | fi 14 | ln -sf "$PHP_INI_RECOMMENDED" "$PHP_INI_DIR/php.ini" 15 | 16 | mkdir -p var/cache var/log 17 | setfacl -R -m u:www-data:rwX -m u:"$(whoami)":rwX var 18 | setfacl -dR -m u:www-data:rwX -m u:"$(whoami)":rwX var 19 | 20 | if [ "$APP_ENV" != 'prod' ]; then 21 | composer install --prefer-dist --no-progress --no-suggest --no-interaction 22 | fi 23 | 24 | echo "Waiting for db to be ready..." 25 | until bin/console doctrine:query:sql "SELECT 1" > /dev/null 2>&1; do 26 | sleep 1 27 | done 28 | 29 | if ls -A src/Migrations/*.php > /dev/null 2>&1; then 30 | bin/console doctrine:migrations:migrate --no-interaction 31 | fi 32 | fi 33 | 34 | exec docker-php-entrypoint "$@" 35 | -------------------------------------------------------------------------------- /api/docker/varnish/conf/default.vcl: -------------------------------------------------------------------------------- 1 | vcl 4.0; 2 | 3 | import std; 4 | 5 | backend default { 6 | .host = "api"; 7 | .port = "80"; 8 | # Health check 9 | #.probe = { 10 | # .url = "/"; 11 | # .timeout = 5s; 12 | # .interval = 10s; 13 | # .window = 5; 14 | # .threshold = 3; 15 | #} 16 | } 17 | 18 | # Hosts allowed to send BAN requests 19 | acl invalidators { 20 | "localhost"; 21 | "php"; 22 | # local Kubernetes network 23 | "10.0.0.0"/8; 24 | "172.16.0.0"/12; 25 | "192.168.0.0"/16; 26 | } 27 | 28 | sub vcl_recv { 29 | if (req.restarts > 0) { 30 | set req.hash_always_miss = true; 31 | } 32 | 33 | # Remove the "Forwarded" HTTP header if exists (security) 34 | unset req.http.forwarded; 35 | # Remove "Preload" and "Fields" HTTP header to improve Vulcain's performance 36 | unset req.http.preload; 37 | unset req.http.fields; 38 | 39 | # To allow API Platform to ban by cache tags 40 | if (req.method == "BAN") { 41 | if (client.ip !~ invalidators) { 42 | return (synth(405, "Not allowed")); 43 | } 44 | 45 | if (req.http.ApiPlatform-Ban-Regex) { 46 | ban("obj.http.Cache-Tags ~ " + req.http.ApiPlatform-Ban-Regex); 47 | 48 | return (synth(200, "Ban added")); 49 | } 50 | 51 | return (synth(400, "ApiPlatform-Ban-Regex HTTP header must be set.")); 52 | } 53 | 54 | # For health checks 55 | if (req.method == "GET" && req.url == "/healthz") { 56 | return (synth(200, "OK")); 57 | } 58 | } 59 | 60 | sub vcl_hit { 61 | if (obj.ttl >= 0s) { 62 | # A pure unadulterated hit, deliver it 63 | return (deliver); 64 | } 65 | 66 | if (std.healthy(req.backend_hint)) { 67 | # The backend is healthy 68 | # Fetch the object from the backend 69 | return (restart); 70 | } 71 | 72 | # No fresh object and the backend is not healthy 73 | if (obj.ttl + obj.grace > 0s) { 74 | # Deliver graced object 75 | # Automatically triggers a background fetch 76 | return (deliver); 77 | } 78 | 79 | # No valid object to deliver 80 | # No healthy backend to handle request 81 | # Return error 82 | return (synth(503, "API is down")); 83 | } 84 | 85 | sub vcl_deliver { 86 | # Don't send cache tags related headers to the client 87 | unset resp.http.url; 88 | # Comment the following line to send the "Cache-Tags" header to the client (e.g. to use CloudFlare cache tags) 89 | unset resp.http.Cache-Tags; 90 | } 91 | 92 | sub vcl_backend_response { 93 | # Ban lurker friendly header 94 | set beresp.http.url = bereq.url; 95 | 96 | # Add a grace in case the backend is down 97 | set beresp.grace = 1h; 98 | } 99 | -------------------------------------------------------------------------------- /api/fixtures/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dunglas/demo-vulcain-api-platform/f0d66cf06827f63d5c9cfd3fede2ecedd44483f8/api/fixtures/.gitignore -------------------------------------------------------------------------------- /api/fixtures/data.yaml: -------------------------------------------------------------------------------- 1 | App\Entity\Conference: 2 | conference_{1..10}: 3 | name: '' 4 | 5 | App\Entity\Session: 6 | session_{1..100}: 7 | conference: '@conference_*' 8 | title: '' 9 | summary: '' 10 | author: '' 11 | 12 | App\Entity\Feedback: 13 | feedback_{1..100}: 14 | session: '@session_*' 15 | comment: '' 16 | rating: '' 17 | -------------------------------------------------------------------------------- /api/helm/api/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *~ 18 | # Various IDEs 19 | .project 20 | .idea/ 21 | *.tmproj 22 | .vscode/ 23 | -------------------------------------------------------------------------------- /api/helm/api/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | appVersion: 0.1.0 3 | description: A Helm chart for an API Platform API 4 | name: api 5 | version: 0.1.0 6 | home: https://api-platform.com 7 | icon: https://api-platform.com/logo-250x250.png 8 | -------------------------------------------------------------------------------- /api/helm/api/requirements.lock: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - name: postgresql 3 | repository: https://kubernetes-charts.storage.googleapis.com/ 4 | version: 3.9.5 5 | - name: mercure 6 | repository: https://kubernetes-charts.storage.googleapis.com/ 7 | version: 1.0.0 8 | digest: sha256:6466d376987cc08b70fe36945dd93474836aab05edc155955951bcb0774a0896 9 | generated: 2019-03-26T18:14:32.4074+01:00 10 | -------------------------------------------------------------------------------- /api/helm/api/requirements.yaml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - name: postgresql 3 | version: ~3.9.0 4 | repository: https://kubernetes-charts.storage.googleapis.com/ 5 | condition: postgresql.enabled 6 | - name: mercure 7 | version: ~1.0.0 8 | repository: https://kubernetes-charts.storage.googleapis.com/ 9 | condition: mercure.enabled 10 | -------------------------------------------------------------------------------- /api/helm/api/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | API deployed. 2 | 3 | To get the ingress's IP: 4 | 5 | kubectl --namespace {{ .Release.Namespace }} get ingress -o jsonpath="{.items[0].status.loadBalancer.ingress[0].ip}" 6 | -------------------------------------------------------------------------------- /api/helm/api/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | {{/* 3 | Expand the name of the chart. 4 | */}} 5 | {{- define "name" -}} 6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 7 | {{- end -}} 8 | 9 | {{/* 10 | Create a default fully qualified app name. 11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 12 | */}} 13 | {{- define "fullname" -}} 14 | {{- $name := default .Chart.Name .Values.nameOverride -}} 15 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} 16 | {{- end -}} 17 | 18 | {{- define "postgresql.fullname" -}} 19 | {{- printf "%s-%s" .Release.Name "postgresql" | trunc 63 | trimSuffix "-" -}} 20 | {{- end -}} 21 | 22 | {{/* 23 | Create chart name and version as used by the chart label. 24 | */}} 25 | {{- define "chart" -}} 26 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} 27 | {{- end -}} 28 | -------------------------------------------------------------------------------- /api/helm/api/templates/configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: {{ template "fullname" . }} 5 | labels: 6 | app.kubernetes.io/name: {{ include "name" . }} 7 | app.kubernetes.io/part-of: {{ include "name" . }} 8 | helm.sh/chart: {{ include "chart" . }} 9 | app.kubernetes.io/instance: {{ .Release.Name }} 10 | app.kubernetes.io/managed-by: {{ .Release.Service }} 11 | data: 12 | env: {{ .Values.php.env | quote }} 13 | debug: {{ .Values.php.debug | quote }} 14 | cors-allow-origin: {{ .Values.php.corsAllowOrigin | quote }} 15 | varnish-url: {{ if .Values.varnish.url }}{{ .Values.varnish.url | quote }}{{ else }}http://varnish{{ end }} 16 | trusted-hosts: {{ .Values.php.trustedHosts | quote }} 17 | trusted-proxies: {{ join "," .Values.php.trustedProxies }} 18 | mercure-publish-url: {{ .Values.mercure.publishUrl | quote }} 19 | mercure-subscribe-url: {{ .Values.mercure.subscribeUrl | quote }} 20 | -------------------------------------------------------------------------------- /api/helm/api/templates/ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions/v1beta1 2 | kind: Ingress 3 | metadata: 4 | name: {{ template "fullname" . }} 5 | labels: 6 | app.kubernetes.io/name: {{ include "name" . }}-ingress 7 | app.kubernetes.io/part-of: {{ include "name" . }} 8 | helm.sh/chart: {{ include "chart" . }} 9 | app.kubernetes.io/instance: {{ .Release.Name }} 10 | app.kubernetes.io/managed-by: {{ .Release.Service }} 11 | {{- with .Values.ingress.annotations }} 12 | annotations: 13 | {{- toYaml . | nindent 4 }} 14 | {{- end }} 15 | spec: 16 | {{- if .Values.ingress.tls }} 17 | tls: 18 | {{- range .Values.ingress.tls }} 19 | - hosts: 20 | {{- range .hosts }} 21 | - {{ .host | quote }} 22 | {{- end }} 23 | secretName: {{ .secretName }} 24 | {{- end }} 25 | {{- end }} 26 | rules: 27 | {{- range .Values.ingress.hosts }} 28 | - host: {{ .host | quote }} 29 | http: 30 | paths: 31 | - path: /* 32 | backend: 33 | serviceName: {{ .serviceName }} 34 | servicePort: {{ .servicePort | default 80 }} 35 | {{- end }} 36 | -------------------------------------------------------------------------------- /api/helm/api/templates/nginx-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions/v1beta1 2 | kind: Deployment 3 | metadata: 4 | name: {{ template "fullname" . }}-nginx 5 | labels: 6 | app.kubernetes.io/name: {{ include "name" . }}-nginx 7 | app.kubernetes.io/part-of: {{ include "name" . }} 8 | helm.sh/chart: {{ include "chart" . }} 9 | app.kubernetes.io/instance: {{ .Release.Name }} 10 | app.kubernetes.io/managed-by: {{ .Release.Service }} 11 | spec: 12 | replicas: {{ .Values.nginx.replicaCount }} 13 | template: 14 | metadata: 15 | labels: 16 | app.kubernetes.io/name: {{ include "name" . }}-nginx 17 | app.kubernetes.io/part-of: {{ include "name" . }} 18 | helm.sh/chart: {{ include "chart" . }} 19 | app.kubernetes.io/instance: {{ .Release.Name }} 20 | app.kubernetes.io/managed-by: {{ .Release.Service }} 21 | spec: 22 | containers: 23 | - name: {{ .Chart.Name }}-nginx 24 | image: "{{ .Values.nginx.repository }}:{{ .Values.nginx.tag }}" 25 | imagePullPolicy: {{ .Values.nginx.pullPolicy }} 26 | ports: 27 | - containerPort: 80 28 | resources: 29 | {{ toYaml .Values.resources | indent 12 }} 30 | {{- if .Values.nodeSelector }} 31 | nodeSelector: 32 | {{ toYaml .Values.nodeSelector | indent 8 }} 33 | {{- end }} 34 | -------------------------------------------------------------------------------- /api/helm/api/templates/nginx-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: api 5 | labels: 6 | app.kubernetes.io/name: {{ include "name" . }}-nginx 7 | app.kubernetes.io/part-of: {{ include "name" . }} 8 | helm.sh/chart: {{ include "chart" . }} 9 | app.kubernetes.io/instance: {{ .Release.Name }} 10 | app.kubernetes.io/managed-by: {{ .Release.Service }} 11 | spec: 12 | type: NodePort 13 | ports: 14 | - port: 80 15 | targetPort: 80 16 | protocol: TCP 17 | selector: 18 | app.kubernetes.io/name: {{ include "name" . }}-nginx 19 | app.kubernetes.io/instance: {{ .Release.Name }} 20 | -------------------------------------------------------------------------------- /api/helm/api/templates/php-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions/v1beta1 2 | kind: Deployment 3 | metadata: 4 | name: {{ template "fullname" . }}-php 5 | labels: 6 | app.kubernetes.io/name: {{ include "name" . }}-php 7 | app.kubernetes.io/part-of: {{ include "name" . }} 8 | helm.sh/chart: {{ include "chart" . }} 9 | app.kubernetes.io/instance: {{ .Release.Name }} 10 | app.kubernetes.io/managed-by: {{ .Release.Service }} 11 | spec: 12 | replicas: {{ .Values.php.replicaCount }} 13 | template: 14 | metadata: 15 | labels: 16 | app.kubernetes.io/name: {{ include "name" . }}-php 17 | app.kubernetes.io/part-of: {{ include "name" . }} 18 | helm.sh/chart: {{ include "chart" . }} 19 | app.kubernetes.io/instance: {{ .Release.Name }} 20 | app.kubernetes.io/managed-by: {{ .Release.Service }} 21 | spec: 22 | containers: 23 | - name: {{ .Chart.Name }}-php 24 | image: "{{ .Values.php.repository }}:{{ .Values.php.tag }}" 25 | imagePullPolicy: {{ .Values.php.pullPolicy }} 26 | ports: 27 | - containerPort: 9000 28 | env: 29 | - name: TRUSTED_HOSTS 30 | valueFrom: 31 | configMapKeyRef: 32 | name: {{ template "fullname" . }} 33 | key: trusted-hosts 34 | - name: TRUSTED_PROXIES 35 | valueFrom: 36 | configMapKeyRef: 37 | name: {{ template "fullname" . }} 38 | key: trusted-proxies 39 | - name: APP_ENV 40 | valueFrom: 41 | configMapKeyRef: 42 | name: {{ template "fullname" . }} 43 | key: env 44 | - name: APP_DEBUG 45 | valueFrom: 46 | configMapKeyRef: 47 | name: {{ template "fullname" . }} 48 | key: debug 49 | - name: CORS_ALLOW_ORIGIN 50 | valueFrom: 51 | configMapKeyRef: 52 | name: {{ template "fullname" . }} 53 | key: cors-allow-origin 54 | - name: VARNISH_URL 55 | valueFrom: 56 | configMapKeyRef: 57 | name: {{ template "fullname" . }} 58 | key: varnish-url 59 | - name: APP_SECRET 60 | valueFrom: 61 | secretKeyRef: 62 | name: {{ template "fullname" . }} 63 | key: secret 64 | - name: DATABASE_URL 65 | valueFrom: 66 | secretKeyRef: 67 | name: {{ template "fullname" . }} 68 | key: database-url 69 | - name: MERCURE_PUBLISH_URL 70 | valueFrom: 71 | configMapKeyRef: 72 | name: {{ template "fullname" . }} 73 | key: mercure-publish-url 74 | - name: MERCURE_SUBSCRIBE_URL 75 | valueFrom: 76 | configMapKeyRef: 77 | name: {{ template "fullname" . }} 78 | key: mercure-subscribe-url 79 | - name: MERCURE_JWT_TOKEN 80 | valueFrom: 81 | secretKeyRef: 82 | name: {{ template "fullname" . }} 83 | key: mercure-jwt-token 84 | resources: 85 | {{ toYaml .Values.resources | indent 12 }} 86 | {{- if .Values.nodeSelector }} 87 | nodeSelector: 88 | {{ toYaml .Values.nodeSelector | indent 8 }} 89 | {{- end }} 90 | -------------------------------------------------------------------------------- /api/helm/api/templates/php-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: php 5 | labels: 6 | app.kubernetes.io/name: {{ include "name" . }}-php 7 | app.kubernetes.io/part-of: {{ include "name" . }} 8 | helm.sh/chart: {{ include "chart" . }} 9 | app.kubernetes.io/instance: {{ .Release.Name }} 10 | app.kubernetes.io/managed-by: {{ .Release.Service }} 11 | spec: 12 | type: ClusterIP 13 | ports: 14 | - port: 9000 15 | selector: 16 | app.kubernetes.io/name: {{ include "name" . }}-php 17 | app.kubernetes.io/instance: {{ .Release.Name }} 18 | -------------------------------------------------------------------------------- /api/helm/api/templates/secrets.yaml: -------------------------------------------------------------------------------- 1 | {{- $postgresqlServiceName := include "postgresql.fullname" . -}} 2 | apiVersion: v1 3 | kind: Secret 4 | metadata: 5 | name: {{ template "fullname" . }} 6 | labels: 7 | app.kubernetes.io/name: {{ include "name" . }} 8 | app.kubernetes.io/part-of: {{ include "name" . }} 9 | helm.sh/chart: {{ include "chart" . }} 10 | app.kubernetes.io/instance: {{ .Release.Name }} 11 | app.kubernetes.io/managed-by: {{ .Release.Service }} 12 | type: Opaque 13 | data: 14 | {{ if .Values.postgresql.enabled }} 15 | database-url: {{ printf "pgsql://%s:%s@%s/%s?serverVersion=9.6" .Values.postgresql.postgresqlUsername .Values.postgresql.postgresqlPassword $postgresqlServiceName .Values.postgresql.postgresqlDatabase | b64enc | quote }} 16 | {{ else }} 17 | database-url: {{ .Values.postgresql.url | b64enc | quote }} 18 | {{ end }} 19 | secret: {{ .Values.php.secret | default (randAlphaNum 40) | b64enc | quote }} 20 | mercure-jwt-token: {{ .Values.php.mercure.jwtToken | b64enc | quote }} 21 | -------------------------------------------------------------------------------- /api/helm/api/templates/varnish-deployment.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.varnish.enabled -}} 2 | apiVersion: extensions/v1beta1 3 | kind: Deployment 4 | metadata: 5 | name: {{ template "fullname" . }}-varnish 6 | labels: 7 | app.kubernetes.io/name: {{ include "name" . }}-varnish 8 | app.kubernetes.io/part-of: {{ include "name" . }} 9 | helm.sh/chart: {{ include "chart" . }} 10 | app.kubernetes.io/instance: {{ .Release.Name }} 11 | app.kubernetes.io/managed-by: {{ .Release.Service }} 12 | spec: 13 | replicas: {{ .Values.varnish.replicaCount }} 14 | template: 15 | metadata: 16 | labels: 17 | app.kubernetes.io/name: {{ include "name" . }}-varnish 18 | helm.sh/chart: {{ include "chart" . }} 19 | app.kubernetes.io/instance: {{ .Release.Name }} 20 | app.kubernetes.io/managed-by: {{ .Release.Service }} 21 | spec: 22 | containers: 23 | - name: {{ .Chart.Name }}-varnish 24 | image: "{{ .Values.varnish.repository }}:{{ .Values.varnish.tag }}" 25 | imagePullPolicy: {{ .Values.varnish.pullPolicy }} 26 | command: ["varnishd"] 27 | args: ["-F", "-f", "/etc/varnish/default.vcl", "-p", "http_resp_hdr_len=65536", "-p", "http_resp_size=98304"] 28 | ports: 29 | - containerPort: 80 30 | livenessProbe: 31 | httpGet: 32 | path: /healthz 33 | port: 80 34 | readinessProbe: 35 | httpGet: 36 | path: /healthz 37 | port: 80 38 | resources: 39 | {{ toYaml .Values.resources | indent 12 }} 40 | {{- if .Values.nodeSelector }} 41 | nodeSelector: 42 | {{ toYaml .Values.nodeSelector | indent 8 }} 43 | {{- end }} 44 | {{- end -}} 45 | -------------------------------------------------------------------------------- /api/helm/api/templates/varnish-service.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.varnish.enabled -}} 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: varnish 6 | labels: 7 | app.kubernetes.io/name: {{ include "name" . }}-varnish 8 | app.kubernetes.io/part-of: {{ include "name" . }} 9 | helm.sh/chart: {{ include "chart" . }} 10 | app.kubernetes.io/instance: {{ .Release.Name }} 11 | app.kubernetes.io/managed-by: {{ .Release.Service }} 12 | spec: 13 | type: NodePort 14 | ports: 15 | - port: 80 16 | targetPort: 80 17 | protocol: TCP 18 | selector: 19 | app.kubernetes.io/name: {{ include "name" . }}-varnish 20 | app.kubernetes.io/instance: {{ .Release.Name }} 21 | {{- end -}} 22 | -------------------------------------------------------------------------------- /api/helm/api/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for api. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | 5 | php: 6 | repository: quay.io/api-platform/php 7 | tag: latest 8 | pullPolicy: Always 9 | replicaCount: 1 10 | mercure: 11 | jwtToken: "" 12 | env: prod 13 | debug: '0' 14 | secret: "" 15 | corsAllowOrigin: "^https?://.*?\\.example\\.com$" 16 | trustedHosts: "^.*\\.example\\.com$" 17 | trustedProxies: 18 | - 10.0.0.0/8 19 | - 172.16.0.0/12 20 | - 192.168.0.0/16 21 | 22 | nginx: 23 | repository: quay.io/api-platform/nginx 24 | tag: latest 25 | pullPolicy: Always 26 | replicaCount: 1 27 | 28 | varnish: 29 | enabled: true 30 | #url: https://example.com 31 | repository: quay.io/api-platform/varnish 32 | tag: latest 33 | pullPolicy: Always 34 | replicaCount: 1 35 | 36 | postgresql: 37 | enabled: true 38 | imageTag: 10-alpine 39 | # If bringing your own PostgreSQL, the full uri to use 40 | #url: pgsql://api-platform:!ChangeMe!@example.com/api?serverVersion=10.1 41 | postgresqlUsername: "example" 42 | postgresqlPassword: "!ChangeMe!" 43 | postgresqlDatabase: "api" 44 | # Persistent Volume Storage configuration. 45 | # ref: https://kubernetes.io/docs/user-guide/persistent-volumes 46 | persistence: 47 | enabled: false 48 | pullPolicy: IfNotPresent 49 | # image: 50 | # repository: postgres 51 | # tag: alpine 52 | 53 | mercure: 54 | enabled: true 55 | publishUrl: http://mercure/.well-known/mercure 56 | subscribeUrl: https://mercure.example.com/.well-known/mercure 57 | allowAnonymous: "1" 58 | corsAllowedOrigins: "^https?://.*?\\.example\\.com$" 59 | acmeHosts: "" # TODO: Fix the Mercure chart 60 | jwtKey: "" 61 | service: 62 | type: NodePort 63 | port: 80 64 | 65 | ingress: 66 | annotations: 67 | # kubernetes.io/ingress.global-static-ip-name: chart-ip 68 | # kubernetes.io/ingress.class: gce 69 | # kubernetes.io/tls-acme: "true" 70 | tls: 71 | # Secrets must be manually created in the namespace, you can also use cert-manager. 72 | # - hosts: 73 | # - example.com 74 | # - mercure.example.com 75 | hosts: 76 | api: 77 | host: example.com 78 | serviceName: varnish 79 | mercure: 80 | host: mercure.example.com 81 | serviceName: mercure 82 | 83 | resources: {} 84 | # We usually recommend not to specify default resources and to leave this as a conscious 85 | # choice for the user. This also increases chances charts run on environments with little 86 | # resources, such as Minikube. If you do want to specify resources, uncomment the following 87 | # lines, adjust them as necessary, and remove the curly braces after 'resources:'. 88 | # limits: 89 | # cpu: 100m 90 | # memory: 128Mi 91 | # requests: 92 | # cpu: 100m 93 | # memory: 128Mi 94 | -------------------------------------------------------------------------------- /api/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dunglas/demo-vulcain-api-platform/f0d66cf06827f63d5c9cfd3fede2ecedd44483f8/api/public/favicon.ico -------------------------------------------------------------------------------- /api/public/index.php: -------------------------------------------------------------------------------- 1 | handle($request); 26 | $response->send(); 27 | $kernel->terminate($request, $response); 28 | -------------------------------------------------------------------------------- /api/public/test-max-waterfall.html: -------------------------------------------------------------------------------- 1 | 2 | Maximum waterfall: API Platform X Vulcain 3 | 33 |

This script is designed to simulate more data dependencies than they really exist to increase the waterfall effect. 34 | -------------------------------------------------------------------------------- /api/public/test-min-waterfall.html: -------------------------------------------------------------------------------- 1 | 2 | Minimum waterfall: API Platform X Vulcain 3 | 35 |

As fast as possible (don't simulate extra data dependencies). 36 | -------------------------------------------------------------------------------- /api/src/Controller/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dunglas/demo-vulcain-api-platform/f0d66cf06827f63d5c9cfd3fede2ecedd44483f8/api/src/Controller/.gitignore -------------------------------------------------------------------------------- /api/src/Entity/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dunglas/demo-vulcain-api-platform/f0d66cf06827f63d5c9cfd3fede2ecedd44483f8/api/src/Entity/.gitignore -------------------------------------------------------------------------------- /api/src/Entity/Conference.php: -------------------------------------------------------------------------------- 1 | sessions = new ArrayCollection(); 36 | } 37 | 38 | public function getId(): ?int 39 | { 40 | return $this->id; 41 | } 42 | 43 | public function getName(): ?string 44 | { 45 | return $this->name; 46 | } 47 | 48 | public function setName(string $name): self 49 | { 50 | $this->name = $name; 51 | 52 | return $this; 53 | } 54 | 55 | /** 56 | * @return Collection|Session[] 57 | */ 58 | public function getSessions(): Collection 59 | { 60 | return $this->sessions; 61 | } 62 | 63 | public function addSession(Session $session): self 64 | { 65 | if (!$this->sessions->contains($session)) { 66 | $this->sessions[] = $session; 67 | $session->setConference($this); 68 | } 69 | 70 | return $this; 71 | } 72 | 73 | public function removeSession(Session $session): self 74 | { 75 | if ($this->sessions->contains($session)) { 76 | $this->sessions->removeElement($session); 77 | // set the owning side to null (unless already changed) 78 | if ($session->getConference() === $this) { 79 | $session->setConference(null); 80 | } 81 | } 82 | 83 | return $this; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /api/src/Entity/Feedback.php: -------------------------------------------------------------------------------- 1 | id; 40 | } 41 | 42 | public function getComment(): ?string 43 | { 44 | return $this->comment; 45 | } 46 | 47 | public function setComment(string $comment): self 48 | { 49 | $this->comment = $comment; 50 | 51 | return $this; 52 | } 53 | 54 | public function getRating(): ?int 55 | { 56 | return $this->rating; 57 | } 58 | 59 | public function setRating(int $rating): self 60 | { 61 | $this->rating = $rating; 62 | 63 | return $this; 64 | } 65 | 66 | public function getSession(): ?Session 67 | { 68 | return $this->session; 69 | } 70 | 71 | public function setSession(?Session $session): self 72 | { 73 | $this->session = $session; 74 | 75 | return $this; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /api/src/Entity/Session.php: -------------------------------------------------------------------------------- 1 | feedback = new ArrayCollection(); 52 | } 53 | 54 | public function getId(): ?int 55 | { 56 | return $this->id; 57 | } 58 | 59 | public function getTitle(): ?string 60 | { 61 | return $this->title; 62 | } 63 | 64 | public function setTitle(string $title): self 65 | { 66 | $this->title = $title; 67 | 68 | return $this; 69 | } 70 | 71 | public function getSummary(): ?string 72 | { 73 | return $this->summary; 74 | } 75 | 76 | public function setSummary(?string $summary): self 77 | { 78 | $this->summary = $summary; 79 | 80 | return $this; 81 | } 82 | 83 | public function getAuthor(): ?string 84 | { 85 | return $this->author; 86 | } 87 | 88 | public function setAuthor(string $author): self 89 | { 90 | $this->author = $author; 91 | 92 | return $this; 93 | } 94 | 95 | public function getConference(): ?Conference 96 | { 97 | return $this->conference; 98 | } 99 | 100 | public function setConference(?Conference $conference): self 101 | { 102 | $this->conference = $conference; 103 | 104 | return $this; 105 | } 106 | 107 | /** 108 | * @return Collection|Feedback[] 109 | */ 110 | public function getFeedback(): Collection 111 | { 112 | return $this->feedback; 113 | } 114 | 115 | public function addFeedback(Feedback $feedback): self 116 | { 117 | if (!$this->feedback->contains($feedback)) { 118 | $this->feedback[] = $feedback; 119 | $feedback->setSession($this); 120 | } 121 | 122 | return $this; 123 | } 124 | 125 | public function removeFeedback(Feedback $feedback): self 126 | { 127 | if ($this->feedback->contains($feedback)) { 128 | $this->feedback->removeElement($feedback); 129 | // set the owning side to null (unless already changed) 130 | if ($feedback->getSession() === $this) { 131 | $feedback->setSession(null); 132 | } 133 | } 134 | 135 | return $this; 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /api/src/Kernel.php: -------------------------------------------------------------------------------- 1 | getProjectDir().'/config/bundles.php'; 21 | foreach ($contents as $class => $envs) { 22 | if ($envs[$this->environment] ?? $envs['all'] ?? false) { 23 | yield new $class(); 24 | } 25 | } 26 | } 27 | 28 | public function getProjectDir(): string 29 | { 30 | return \dirname(__DIR__); 31 | } 32 | 33 | protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader): void 34 | { 35 | $container->addResource(new FileResource($this->getProjectDir().'/config/bundles.php')); 36 | $container->setParameter('container.dumper.inline_class_loader', \PHP_VERSION_ID < 70400 || !ini_get('opcache.preload')); 37 | $container->setParameter('container.dumper.inline_factories', true); 38 | $confDir = $this->getProjectDir().'/config'; 39 | 40 | $loader->load($confDir.'/{packages}/*'.self::CONFIG_EXTS, 'glob'); 41 | $loader->load($confDir.'/{packages}/'.$this->environment.'/*'.self::CONFIG_EXTS, 'glob'); 42 | $loader->load($confDir.'/{services}'.self::CONFIG_EXTS, 'glob'); 43 | $loader->load($confDir.'/{services}_'.$this->environment.self::CONFIG_EXTS, 'glob'); 44 | } 45 | 46 | protected function configureRoutes(RouteCollectionBuilder $routes): void 47 | { 48 | $confDir = $this->getProjectDir().'/config'; 49 | 50 | $routes->import($confDir.'/{routes}/'.$this->environment.'/*'.self::CONFIG_EXTS, '/', 'glob'); 51 | $routes->import($confDir.'/{routes}/*'.self::CONFIG_EXTS, '/', 'glob'); 52 | $routes->import($confDir.'/{routes}'.self::CONFIG_EXTS, '/', 'glob'); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /api/src/Migrations/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dunglas/demo-vulcain-api-platform/f0d66cf06827f63d5c9cfd3fede2ecedd44483f8/api/src/Migrations/.gitignore -------------------------------------------------------------------------------- /api/src/Migrations/Version20190819120152.php: -------------------------------------------------------------------------------- 1 | abortIf('postgresql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'postgresql\'.'); 20 | 21 | $this->addSql('CREATE SEQUENCE greeting_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); 22 | $this->addSql('CREATE TABLE greeting (id INT NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY(id))'); 23 | } 24 | 25 | public function down(Schema $schema): void 26 | { 27 | $this->abortIf('postgresql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'postgresql\'.'); 28 | 29 | $this->addSql('DROP SEQUENCE greeting_id_seq CASCADE'); 30 | $this->addSql('DROP TABLE greeting'); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /api/src/Repository/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dunglas/demo-vulcain-api-platform/f0d66cf06827f63d5c9cfd3fede2ecedd44483f8/api/src/Repository/.gitignore -------------------------------------------------------------------------------- /api/src/Repository/ConferenceRepository.php: -------------------------------------------------------------------------------- 1 | createQueryBuilder('c') 29 | ->andWhere('c.exampleField = :val') 30 | ->setParameter('val', $value) 31 | ->orderBy('c.id', 'ASC') 32 | ->setMaxResults(10) 33 | ->getQuery() 34 | ->getResult() 35 | ; 36 | } 37 | */ 38 | 39 | /* 40 | public function findOneBySomeField($value): ?Conference 41 | { 42 | return $this->createQueryBuilder('c') 43 | ->andWhere('c.exampleField = :val') 44 | ->setParameter('val', $value) 45 | ->getQuery() 46 | ->getOneOrNullResult() 47 | ; 48 | } 49 | */ 50 | } 51 | -------------------------------------------------------------------------------- /api/src/Repository/FeedbackRepository.php: -------------------------------------------------------------------------------- 1 | createQueryBuilder('f') 29 | ->andWhere('f.exampleField = :val') 30 | ->setParameter('val', $value) 31 | ->orderBy('f.id', 'ASC') 32 | ->setMaxResults(10) 33 | ->getQuery() 34 | ->getResult() 35 | ; 36 | } 37 | */ 38 | 39 | /* 40 | public function findOneBySomeField($value): ?Feedback 41 | { 42 | return $this->createQueryBuilder('f') 43 | ->andWhere('f.exampleField = :val') 44 | ->setParameter('val', $value) 45 | ->getQuery() 46 | ->getOneOrNullResult() 47 | ; 48 | } 49 | */ 50 | } 51 | -------------------------------------------------------------------------------- /api/src/Repository/SessionRepository.php: -------------------------------------------------------------------------------- 1 | createQueryBuilder('s') 29 | ->andWhere('s.exampleField = :val') 30 | ->setParameter('val', $value) 31 | ->orderBy('s.id', 'ASC') 32 | ->setMaxResults(10) 33 | ->getQuery() 34 | ->getResult() 35 | ; 36 | } 37 | */ 38 | 39 | /* 40 | public function findOneBySomeField($value): ?Session 41 | { 42 | return $this->createQueryBuilder('s') 43 | ->andWhere('s.exampleField = :val') 44 | ->setParameter('val', $value) 45 | ->getQuery() 46 | ->getOneOrNullResult() 47 | ; 48 | } 49 | */ 50 | } 51 | -------------------------------------------------------------------------------- /api/symfony.lock: -------------------------------------------------------------------------------- 1 | { 2 | "api-platform/api-pack": { 3 | "version": "v1.2.1" 4 | }, 5 | "api-platform/core": { 6 | "version": "2.5", 7 | "recipe": { 8 | "repo": "github.com/symfony/recipes", 9 | "branch": "master", 10 | "version": "2.5", 11 | "ref": "a93061567140e386f107be75340ac2aee3f86cbf" 12 | }, 13 | "files": [ 14 | "config/packages/api_platform.yaml", 15 | "config/routes/api_platform.yaml", 16 | "src/Entity/.gitignore" 17 | ] 18 | }, 19 | "api-platform/schema-generator": { 20 | "version": "v2.1.0" 21 | }, 22 | "composer/semver": { 23 | "version": "1.5.0" 24 | }, 25 | "composer/xdebug-handler": { 26 | "version": "1.4.0" 27 | }, 28 | "doctrine/annotations": { 29 | "version": "1.0", 30 | "recipe": { 31 | "repo": "github.com/symfony/recipes", 32 | "branch": "master", 33 | "version": "1.0", 34 | "ref": "cb4152ebcadbe620ea2261da1a1c5a9b8cea7672" 35 | }, 36 | "files": [ 37 | "config/routes/annotations.yaml" 38 | ] 39 | }, 40 | "doctrine/cache": { 41 | "version": "1.9.1" 42 | }, 43 | "doctrine/collections": { 44 | "version": "1.6.4" 45 | }, 46 | "doctrine/common": { 47 | "version": "v2.11.0" 48 | }, 49 | "doctrine/data-fixtures": { 50 | "version": "1.4.0" 51 | }, 52 | "doctrine/dbal": { 53 | "version": "v2.10.0" 54 | }, 55 | "doctrine/doctrine-bundle": { 56 | "version": "1.6", 57 | "recipe": { 58 | "repo": "github.com/symfony/recipes", 59 | "branch": "master", 60 | "version": "1.6", 61 | "ref": "ae7f137d0cacaf8f259f4e05af59e326f065ba5e" 62 | }, 63 | "files": [ 64 | "config/packages/doctrine.yaml", 65 | "config/packages/prod/doctrine.yaml", 66 | "src/Entity/.gitignore", 67 | "src/Repository/.gitignore" 68 | ] 69 | }, 70 | "doctrine/doctrine-cache-bundle": { 71 | "version": "1.3.5" 72 | }, 73 | "doctrine/doctrine-migrations-bundle": { 74 | "version": "1.2", 75 | "recipe": { 76 | "repo": "github.com/symfony/recipes", 77 | "branch": "master", 78 | "version": "1.2", 79 | "ref": "c1431086fec31f17fbcfe6d6d7e92059458facc1" 80 | }, 81 | "files": [ 82 | "config/packages/doctrine_migrations.yaml", 83 | "src/Migrations/.gitignore" 84 | ] 85 | }, 86 | "doctrine/event-manager": { 87 | "version": "1.1.0" 88 | }, 89 | "doctrine/inflector": { 90 | "version": "1.3.1" 91 | }, 92 | "doctrine/instantiator": { 93 | "version": "1.3.0" 94 | }, 95 | "doctrine/lexer": { 96 | "version": "1.2.0" 97 | }, 98 | "doctrine/migrations": { 99 | "version": "2.2.0" 100 | }, 101 | "doctrine/orm": { 102 | "version": "v2.6.4" 103 | }, 104 | "doctrine/persistence": { 105 | "version": "1.2.0" 106 | }, 107 | "doctrine/reflection": { 108 | "version": "v1.0.0" 109 | }, 110 | "easyrdf/easyrdf": { 111 | "version": "0.9.1" 112 | }, 113 | "fig/link-util": { 114 | "version": "1.0.0" 115 | }, 116 | "friendsofphp/php-cs-fixer": { 117 | "version": "2.2", 118 | "recipe": { 119 | "repo": "github.com/symfony/recipes", 120 | "branch": "master", 121 | "version": "2.2", 122 | "ref": "cc05ab6abf6894bddb9bbd6a252459010ebe040b" 123 | }, 124 | "files": [ 125 | ".php_cs.dist" 126 | ] 127 | }, 128 | "fzaninotto/faker": { 129 | "version": "v1.9.0" 130 | }, 131 | "guzzlehttp/guzzle": { 132 | "version": "6.4.1" 133 | }, 134 | "guzzlehttp/promises": { 135 | "version": "v1.3.1" 136 | }, 137 | "guzzlehttp/psr7": { 138 | "version": "1.6.1" 139 | }, 140 | "hautelook/alice-bundle": { 141 | "version": "2.1", 142 | "recipe": { 143 | "repo": "github.com/symfony/recipes", 144 | "branch": "master", 145 | "version": "2.1", 146 | "ref": "71822522faf7ed2792d86b7f94ce73443358ccb9" 147 | }, 148 | "files": [ 149 | "config/packages/dev/hautelook_alice.yaml", 150 | "config/packages/test/hautelook_alice.yaml", 151 | "fixtures/.gitignore" 152 | ] 153 | }, 154 | "jdorn/sql-formatter": { 155 | "version": "v1.2.17" 156 | }, 157 | "league/html-to-markdown": { 158 | "version": "4.9.0" 159 | }, 160 | "myclabs/deep-copy": { 161 | "version": "1.9.3" 162 | }, 163 | "nelmio/alice": { 164 | "version": "3.2", 165 | "recipe": { 166 | "repo": "github.com/symfony/recipes", 167 | "branch": "master", 168 | "version": "3.2", 169 | "ref": "0b9900ece737bec7752e4155c0164639dd9b0cb0" 170 | }, 171 | "files": [ 172 | "config/packages/dev/nelmio_alice.yaml", 173 | "config/packages/test/nelmio_alice.yaml" 174 | ] 175 | }, 176 | "nelmio/cors-bundle": { 177 | "version": "1.5", 178 | "recipe": { 179 | "repo": "github.com/symfony/recipes", 180 | "branch": "master", 181 | "version": "1.5", 182 | "ref": "6388de23860284db9acce0a7a5d9d13153bcb571" 183 | }, 184 | "files": [ 185 | "config/packages/nelmio_cors.yaml" 186 | ] 187 | }, 188 | "nikic/php-parser": { 189 | "version": "v4.3.0" 190 | }, 191 | "ocramius/package-versions": { 192 | "version": "1.5.1" 193 | }, 194 | "ocramius/proxy-manager": { 195 | "version": "2.2.3" 196 | }, 197 | "php-cs-fixer/diff": { 198 | "version": "v1.3.0" 199 | }, 200 | "phpdocumentor/reflection-common": { 201 | "version": "2.0.0" 202 | }, 203 | "phpdocumentor/reflection-docblock": { 204 | "version": "4.3.2" 205 | }, 206 | "phpdocumentor/type-resolver": { 207 | "version": "1.0.1" 208 | }, 209 | "psr/cache": { 210 | "version": "1.0.1" 211 | }, 212 | "psr/container": { 213 | "version": "1.0.0" 214 | }, 215 | "psr/http-message": { 216 | "version": "1.0.1" 217 | }, 218 | "psr/link": { 219 | "version": "1.0.0" 220 | }, 221 | "psr/log": { 222 | "version": "1.1.2" 223 | }, 224 | "ralouphie/getallheaders": { 225 | "version": "3.0.3" 226 | }, 227 | "sebastian/comparator": { 228 | "version": "3.0.2" 229 | }, 230 | "sebastian/diff": { 231 | "version": "3.0.2" 232 | }, 233 | "sebastian/exporter": { 234 | "version": "3.1.2" 235 | }, 236 | "sebastian/recursion-context": { 237 | "version": "3.0.0" 238 | }, 239 | "symfony/asset": { 240 | "version": "v4.3.8" 241 | }, 242 | "symfony/cache": { 243 | "version": "v4.3.8" 244 | }, 245 | "symfony/cache-contracts": { 246 | "version": "v1.1.7" 247 | }, 248 | "symfony/config": { 249 | "version": "v4.3.8" 250 | }, 251 | "symfony/console": { 252 | "version": "3.3", 253 | "recipe": { 254 | "repo": "github.com/symfony/recipes", 255 | "branch": "master", 256 | "version": "3.3", 257 | "ref": "482d233eb8de91ebd042992077bbd5838858890c" 258 | }, 259 | "files": [ 260 | "bin/console", 261 | "config/bootstrap.php" 262 | ] 263 | }, 264 | "symfony/debug": { 265 | "version": "v4.3.8" 266 | }, 267 | "symfony/dependency-injection": { 268 | "version": "v4.3.8" 269 | }, 270 | "symfony/doctrine-bridge": { 271 | "version": "v4.3.8" 272 | }, 273 | "symfony/dotenv": { 274 | "version": "v4.3.8" 275 | }, 276 | "symfony/event-dispatcher": { 277 | "version": "v4.3.8" 278 | }, 279 | "symfony/event-dispatcher-contracts": { 280 | "version": "v1.1.7" 281 | }, 282 | "symfony/expression-language": { 283 | "version": "v4.3.8" 284 | }, 285 | "symfony/filesystem": { 286 | "version": "v4.3.8" 287 | }, 288 | "symfony/finder": { 289 | "version": "v4.3.8" 290 | }, 291 | "symfony/flex": { 292 | "version": "1.0", 293 | "recipe": { 294 | "repo": "github.com/symfony/recipes", 295 | "branch": "master", 296 | "version": "1.0", 297 | "ref": "19fa03bacd9a6619583d1e4939da4388df22984d" 298 | }, 299 | "files": [ 300 | ".env" 301 | ] 302 | }, 303 | "symfony/framework-bundle": { 304 | "version": "4.2", 305 | "recipe": { 306 | "repo": "github.com/symfony/recipes", 307 | "branch": "master", 308 | "version": "4.2", 309 | "ref": "c71284f93644a00d4241dfaf068f0ba8c1efe2f6" 310 | }, 311 | "files": [ 312 | "config/bootstrap.php", 313 | "config/packages/cache.yaml", 314 | "config/packages/framework.yaml", 315 | "config/packages/test/framework.yaml", 316 | "config/services.yaml", 317 | "public/index.php", 318 | "src/Controller/.gitignore", 319 | "src/Kernel.php" 320 | ] 321 | }, 322 | "symfony/http-client": { 323 | "version": "v4.3.8" 324 | }, 325 | "symfony/http-client-contracts": { 326 | "version": "v1.1.8" 327 | }, 328 | "symfony/http-foundation": { 329 | "version": "v4.3.8" 330 | }, 331 | "symfony/http-kernel": { 332 | "version": "v4.3.8" 333 | }, 334 | "symfony/inflector": { 335 | "version": "v4.3.8" 336 | }, 337 | "symfony/maker-bundle": { 338 | "version": "1.0", 339 | "recipe": { 340 | "repo": "github.com/symfony/recipes", 341 | "branch": "master", 342 | "version": "1.0", 343 | "ref": "fadbfe33303a76e25cb63401050439aa9b1a9c7f" 344 | } 345 | }, 346 | "symfony/mercure": { 347 | "version": "v0.3.0" 348 | }, 349 | "symfony/mercure-bundle": { 350 | "version": "0.2", 351 | "recipe": { 352 | "repo": "github.com/symfony/recipes", 353 | "branch": "master", 354 | "version": "0.2", 355 | "ref": "a37d3438e4bd4f9f924c516996bc29e25f66b07e" 356 | }, 357 | "files": [ 358 | "config/packages/mercure.yaml" 359 | ] 360 | }, 361 | "symfony/mime": { 362 | "version": "v4.3.8" 363 | }, 364 | "symfony/options-resolver": { 365 | "version": "v4.3.8" 366 | }, 367 | "symfony/polyfill-intl-idn": { 368 | "version": "v1.12.0" 369 | }, 370 | "symfony/polyfill-mbstring": { 371 | "version": "v1.12.0" 372 | }, 373 | "symfony/polyfill-php72": { 374 | "version": "v1.12.0" 375 | }, 376 | "symfony/polyfill-php73": { 377 | "version": "v1.12.0" 378 | }, 379 | "symfony/process": { 380 | "version": "v4.3.8" 381 | }, 382 | "symfony/profiler-pack": { 383 | "version": "v1.0.4" 384 | }, 385 | "symfony/property-access": { 386 | "version": "v4.3.8" 387 | }, 388 | "symfony/property-info": { 389 | "version": "v4.3.8" 390 | }, 391 | "symfony/routing": { 392 | "version": "4.2", 393 | "recipe": { 394 | "repo": "github.com/symfony/recipes", 395 | "branch": "master", 396 | "version": "4.2", 397 | "ref": "683dcb08707ba8d41b7e34adb0344bfd68d248a7" 398 | }, 399 | "files": [ 400 | "config/packages/prod/routing.yaml", 401 | "config/packages/routing.yaml", 402 | "config/routes.yaml" 403 | ] 404 | }, 405 | "symfony/security-bundle": { 406 | "version": "3.3", 407 | "recipe": { 408 | "repo": "github.com/symfony/recipes", 409 | "branch": "master", 410 | "version": "3.3", 411 | "ref": "e5a0228251d1dd2bca4c8ef918e14423c06db625" 412 | }, 413 | "files": [ 414 | "config/packages/security.yaml" 415 | ] 416 | }, 417 | "symfony/security-core": { 418 | "version": "v4.3.8" 419 | }, 420 | "symfony/security-csrf": { 421 | "version": "v4.3.8" 422 | }, 423 | "symfony/security-guard": { 424 | "version": "v4.3.8" 425 | }, 426 | "symfony/security-http": { 427 | "version": "v4.3.8" 428 | }, 429 | "symfony/serializer": { 430 | "version": "v4.3.8" 431 | }, 432 | "symfony/service-contracts": { 433 | "version": "v1.1.8" 434 | }, 435 | "symfony/stopwatch": { 436 | "version": "v4.3.8" 437 | }, 438 | "symfony/translation-contracts": { 439 | "version": "v1.1.7" 440 | }, 441 | "symfony/twig-bridge": { 442 | "version": "v4.3.8" 443 | }, 444 | "symfony/twig-bundle": { 445 | "version": "3.3", 446 | "recipe": { 447 | "repo": "github.com/symfony/recipes", 448 | "branch": "master", 449 | "version": "3.3", 450 | "ref": "1da1987340b5ba64b16383906d678b989e3d096e" 451 | }, 452 | "files": [ 453 | "config/packages/test/twig.yaml", 454 | "config/packages/twig.yaml", 455 | "config/routes/dev/twig.yaml", 456 | "templates/base.html.twig" 457 | ] 458 | }, 459 | "symfony/validator": { 460 | "version": "4.3", 461 | "recipe": { 462 | "repo": "github.com/symfony/recipes", 463 | "branch": "master", 464 | "version": "4.3", 465 | "ref": "d902da3e4952f18d3bf05aab29512eb61cabd869" 466 | }, 467 | "files": [ 468 | "config/packages/test/validator.yaml", 469 | "config/packages/validator.yaml" 470 | ] 471 | }, 472 | "symfony/var-dumper": { 473 | "version": "v4.3.8" 474 | }, 475 | "symfony/var-exporter": { 476 | "version": "v4.3.8" 477 | }, 478 | "symfony/web-link": { 479 | "version": "v4.3.8" 480 | }, 481 | "symfony/web-profiler-bundle": { 482 | "version": "3.3", 483 | "recipe": { 484 | "repo": "github.com/symfony/recipes", 485 | "branch": "master", 486 | "version": "3.3", 487 | "ref": "6bdfa1a95f6b2e677ab985cd1af2eae35d62e0f6" 488 | }, 489 | "files": [ 490 | "config/packages/dev/web_profiler.yaml", 491 | "config/packages/test/web_profiler.yaml", 492 | "config/routes/dev/web_profiler.yaml" 493 | ] 494 | }, 495 | "symfony/yaml": { 496 | "version": "v4.3.8" 497 | }, 498 | "theofidry/alice-data-fixtures": { 499 | "version": "1.0", 500 | "recipe": { 501 | "repo": "github.com/symfony/recipes", 502 | "branch": "master", 503 | "version": "1.0", 504 | "ref": "fe5a50faf580eb58f08ada2abe8afbd2d4941e05" 505 | } 506 | }, 507 | "twig/twig": { 508 | "version": "v2.12.2" 509 | }, 510 | "webmozart/assert": { 511 | "version": "1.5.0" 512 | }, 513 | "willdurand/negotiation": { 514 | "version": "v2.3.1" 515 | }, 516 | "zendframework/zend-code": { 517 | "version": "3.4.0" 518 | }, 519 | "zendframework/zend-eventmanager": { 520 | "version": "3.2.1" 521 | } 522 | } 523 | -------------------------------------------------------------------------------- /api/templates/base.html.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {% block title %}Welcome!{% endblock %} 6 | {% block stylesheets %}{% endblock %} 7 | 8 | 9 | {% block body %}{% endblock %} 10 | {% block javascripts %}{% endblock %} 11 | 12 | 13 | -------------------------------------------------------------------------------- /client/.dockerignore: -------------------------------------------------------------------------------- 1 | **/*.log 2 | **/*.md 3 | **/._* 4 | **/.dockerignore 5 | **/.DS_Store 6 | **/.git/ 7 | **/.gitattributes 8 | **/.gitignore 9 | **/.gitmodules 10 | **/docker-compose.*.yaml 11 | **/docker-compose.*.yml 12 | **/docker-compose.yaml 13 | **/docker-compose.yml 14 | **/Dockerfile 15 | **/Thumbs.db 16 | .editorconfig 17 | .env.*.local 18 | .env.local 19 | build/ 20 | node_modules/ 21 | -------------------------------------------------------------------------------- /client/.env: -------------------------------------------------------------------------------- 1 | REACT_APP_API_ENTRYPOINT=https://localhost:8443 2 | -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | -------------------------------------------------------------------------------- /client/Dockerfile: -------------------------------------------------------------------------------- 1 | # https://docs.docker.com/develop/develop-images/multistage-build/#stop-at-a-specific-build-stage 2 | # https://docs.docker.com/compose/compose-file/#target 3 | 4 | 5 | # https://docs.docker.com/engine/reference/builder/#understand-how-arg-and-from-interact 6 | ARG NODE_VERSION=13 7 | ARG NGINX_VERSION=1.17 8 | 9 | 10 | # "development" stage 11 | FROM node:${NODE_VERSION}-alpine AS api_platform_client_development 12 | 13 | WORKDIR /usr/src/client 14 | 15 | RUN yarn global add @api-platform/client-generator 16 | 17 | # prevent the reinstallation of node modules at every changes in the source code 18 | COPY package.json yarn.lock ./ 19 | RUN set -eux; \ 20 | yarn install 21 | 22 | COPY . ./ 23 | 24 | VOLUME /usr/src/client/node_modules 25 | 26 | ENV HTTPS true 27 | 28 | CMD ["yarn", "start"] 29 | 30 | 31 | # "build" stage 32 | # depends on the "development" stage above 33 | FROM api_platform_client_development AS api_platform_client_build 34 | 35 | ARG REACT_APP_API_ENTRYPOINT 36 | 37 | RUN set -eux; \ 38 | yarn build 39 | 40 | 41 | # "nginx" stage 42 | # depends on the "build" stage above 43 | FROM nginx:${NGINX_VERSION}-alpine AS api_platform_client_nginx 44 | 45 | COPY docker/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf 46 | 47 | WORKDIR /usr/src/client/build 48 | 49 | COPY --from=api_platform_client_build /usr/src/client/build ./ 50 | -------------------------------------------------------------------------------- /client/docker/nginx/conf.d/default.conf: -------------------------------------------------------------------------------- 1 | server { 2 | root /usr/src/client/build; 3 | 4 | location / { 5 | try_files $uri /index.html; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "bootstrap": "^4.1.3", 7 | "connected-react-router": "^5.0.1", 8 | "font-awesome": "^4.7.0", 9 | "jest-junit": "^5.2.0", 10 | "lodash.get": "^4.4.2", 11 | "lodash.has": "^4.5.2", 12 | "lodash.mapvalues": "^4.6.0", 13 | "prettier": "^1.14.3", 14 | "prop-types": "^15.6.2", 15 | "react": "^16.6.0", 16 | "react-dom": "^16.6.0", 17 | "react-redux": "^5.1.0", 18 | "react-router-dom": "^4.3.1", 19 | "react-scripts": "^3.1.2", 20 | "redux": "^4.0.1", 21 | "redux-form": "^7.4.2", 22 | "redux-thunk": "^2.3.0" 23 | }, 24 | "scripts": { 25 | "start": "react-scripts start", 26 | "build": "react-scripts build", 27 | "test": "react-scripts test", 28 | "eject": "react-scripts eject" 29 | }, 30 | "browserslist": [ 31 | ">0.2%", 32 | "not dead", 33 | "not ie <= 11", 34 | "not op_mini all" 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dunglas/demo-vulcain-api-platform/f0d66cf06827f63d5c9cfd3fede2ecedd44483f8/client/public/favicon.ico -------------------------------------------------------------------------------- /client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | Welcome to API Platform 23 | 24 | 25 | 28 |

29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /client/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "API Platform", 3 | "name": "Client", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /client/src/config/entrypoint.js: -------------------------------------------------------------------------------- 1 | export const ENTRYPOINT = process.env.REACT_APP_API_ENTRYPOINT; 2 | -------------------------------------------------------------------------------- /client/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { createStore, combineReducers, applyMiddleware } from 'redux'; 4 | import { Provider } from 'react-redux'; 5 | import thunk from 'redux-thunk'; 6 | import { reducer as form } from 'redux-form'; 7 | import { Route, Switch } from 'react-router-dom'; 8 | import createBrowserHistory from 'history/createBrowserHistory'; 9 | import { 10 | ConnectedRouter, 11 | connectRouter, 12 | routerMiddleware 13 | } from 'connected-react-router'; 14 | import 'bootstrap/dist/css/bootstrap.css'; 15 | import 'font-awesome/css/font-awesome.css'; 16 | import * as serviceWorker from './serviceWorker'; 17 | // Import your reducers and routes here 18 | import Welcome from './Welcome'; 19 | 20 | const history = createBrowserHistory(); 21 | const store = createStore( 22 | combineReducers({ 23 | router: connectRouter(history), 24 | form, 25 | /* Add your reducers here */ 26 | }), 27 | applyMiddleware(routerMiddleware(history), thunk) 28 | ); 29 | 30 | ReactDOM.render( 31 | 32 | 33 | 34 | 35 | {/* Add your routes here */} 36 |

Not Found

} /> 37 |
38 |
39 |
, 40 | document.getElementById('root') 41 | ); 42 | 43 | // If you want your app to work offline and load faster, you can change 44 | // unregister() to register() below. Note this comes with some pitfalls. 45 | // Learn more about service workers: http://bit.ly/CRA-PWA 46 | serviceWorker.unregister(); 47 | -------------------------------------------------------------------------------- /client/src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read http://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.1/8 is considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit http://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See http://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl) 104 | .then(response => { 105 | // Ensure service worker exists, and that we really are getting a JS file. 106 | const contentType = response.headers.get('content-type'); 107 | if ( 108 | response.status === 404 || 109 | (contentType != null && contentType.indexOf('javascript') === -1) 110 | ) { 111 | // No service worker found. Probably a different app. Reload the page. 112 | navigator.serviceWorker.ready.then(registration => { 113 | registration.unregister().then(() => { 114 | window.location.reload(); 115 | }); 116 | }); 117 | } else { 118 | // Service worker found. Proceed as normal. 119 | registerValidSW(swUrl, config); 120 | } 121 | }) 122 | .catch(() => { 123 | console.log( 124 | 'No internet connection found. App is running in offline mode.' 125 | ); 126 | }); 127 | } 128 | 129 | export function unregister() { 130 | if ('serviceWorker' in navigator) { 131 | navigator.serviceWorker.ready.then(registration => { 132 | registration.unregister(); 133 | }); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /client/src/welcome.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css?family=Open+Sans:400,700|Roboto+Slab:300,700'); 2 | 3 | body { 4 | margin: 0; 5 | } 6 | 7 | /***** GLOBAL *****/ 8 | 9 | .welcome { 10 | height: 100vh; 11 | width: 100vw; 12 | text-align: center; 13 | color: #1d1e1c; 14 | font-family: 'Open Sans', sans-serif; 15 | font-size: 14px; 16 | overflow: auto; 17 | background-color: #ececec; 18 | } 19 | 20 | .welcome a { 21 | text-decoration: none; 22 | color: #38a9b4; 23 | font-weight: bold; 24 | } 25 | 26 | .welcome h1 { 27 | font-family: 'Roboto Slab', serif; 28 | font-weight: 300; 29 | font-size: 36px; 30 | margin: 0 0 10px; 31 | line-height: 30px; 32 | } 33 | 34 | .welcome h1 strong { 35 | font-weight: 700; 36 | color: #38a9b4; 37 | } 38 | 39 | .welcome h2 { 40 | text-transform: uppercase; 41 | font-size: 18px; 42 | font-weight: bold; 43 | margin: 25px 0 5px; 44 | } 45 | 46 | .welcome h3 { 47 | text-transform: uppercase; 48 | font-weight: 500; 49 | color: #38a9b4; 50 | font-size: 16px; 51 | margin: 0 0 5px; 52 | display: block; 53 | } 54 | 55 | /***** TOP *****/ 56 | 57 | .welcome__top { 58 | background-color: #67cece; 59 | padding-bottom: 40px; 60 | } 61 | 62 | .welcome__flag { 63 | transform: rotate(30deg); 64 | position: fixed; 65 | right: -190px; 66 | top: 65px; 67 | box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.2); 68 | z-index: 5; 69 | } 70 | 71 | /***** MAIN *****/ 72 | 73 | .welcome__main { 74 | box-shadow: 0 6px 10px 0 rgba(0, 0, 0, 0.14), 75 | 0 1px 18px 0 rgba(0, 0, 0, 0.12), 0 3px 5px -1px rgba(0, 0, 0, 0.3); 76 | width: 80%; 77 | max-width: 1100px; 78 | margin-left: auto; 79 | margin-right: auto; 80 | transform: translateY(-50px); 81 | background-color: white; 82 | display: flex; 83 | } 84 | 85 | .main__aside { 86 | background-color: #afe5e5; 87 | width: 30%; 88 | position: relative; 89 | overflow: hidden; 90 | } 91 | 92 | .aside__circle, 93 | .main__aside svg { 94 | position: absolute; 95 | left: 50%; 96 | top: 50%; 97 | transform: translate(-50%, -50%); 98 | } 99 | 100 | .aside__circle { 101 | background-color: white; 102 | border-radius: 50%; 103 | width: 90%; 104 | height: 0; 105 | padding-bottom: 90%; 106 | } 107 | 108 | .aside__circle:after { 109 | content: ''; 110 | width: 4px; 111 | left: calc(50% - 5px); 112 | top: -50%; 113 | position: absolute; 114 | height: 100%; 115 | background-color: #1d1e1c; 116 | } 117 | 118 | .main__aside svg { 119 | width: 100%; 120 | } 121 | 122 | .main__content { 123 | padding: 30px; 124 | text-align: left; 125 | flex: auto; 126 | } 127 | .other__bloc { 128 | display: inline-flex; 129 | align-items: center; 130 | border: 4px solid #afe5e5; 131 | padding: 10px 20px; 132 | margin: 10px 0; 133 | height: 170px; 134 | box-sizing: border-box; 135 | text-align: left; 136 | width: 40%; 137 | } 138 | 139 | .other__bloc:not(:last-of-type) { 140 | margin-right: 10px; 141 | } 142 | 143 | .other__bloc h3:not(:first-child) { 144 | margin-top: 15px; 145 | padding-top: 5px; 146 | } 147 | 148 | .other__circle { 149 | width: 110px; 150 | height: 110px; 151 | background-color: #afe5e5; 152 | border-radius: 50%; 153 | margin-right:20px; 154 | } 155 | 156 | .other__circle svg{ 157 | width: 110px; 158 | } 159 | 160 | .buttons__group { 161 | display: inline-flex; 162 | vertical-align: center; 163 | } 164 | 165 | .buttons__group .buttons__or { 166 | width: 4px; 167 | position: relative; 168 | text-align:center; 169 | } 170 | 171 | .buttons__group .buttons__or:before { 172 | content: 'or'; 173 | font-size: 12px; 174 | color: #aaa; 175 | line-height: 18px; 176 | position: absolute; 177 | border-radius: 50%; 178 | top: 50%; 179 | left: 50%; 180 | transform: translate(-50%, -50%); 181 | background-color: white; 182 | width: 18px; 183 | height: 18px; 184 | } 185 | 186 | .buttons__group .other__button:first-child { 187 | border-radius: 5px 0 0 5px; 188 | padding-right: 15px; 189 | } 190 | 191 | .buttons__group .other__button:last-child { 192 | border-radius: 0 5px 5px 0; 193 | padding-left: 15px; 194 | } 195 | 196 | a.other__button { 197 | background-color: #e0e1e2; 198 | font-size: 11px; 199 | color: #686e63; 200 | cursor: pointer; 201 | padding: 5px 10px; 202 | display: inline-block; 203 | transition: all ease 0.2s; 204 | text-transform: uppercase; 205 | } 206 | 207 | .other__button:hover { 208 | background-color: #afe5e5; 209 | color: #339ba5; 210 | } 211 | 212 | .main__button { 213 | display: inline-block; 214 | padding: 10px 50px 10px 10px; 215 | border: 3px solid #339ba5; 216 | font-size: 22px; 217 | color: #339ba5; 218 | text-transform: uppercase; 219 | margin: 15px 0; 220 | overflow: hidden; 221 | transition: all ease 0.3s; 222 | cursor: pointer; 223 | position: relative; 224 | } 225 | 226 | .main__button svg { 227 | position: absolute; 228 | right: 10px; 229 | top: 50%; 230 | transform: translateY(-50%); 231 | transition: transform ease 0.2s; 232 | } 233 | 234 | .main__button:hover { 235 | background-color: #afe5e5; 236 | } 237 | 238 | .main__button:hover svg { 239 | transform: translateY(-50%) rotate(35deg); 240 | } 241 | 242 | /***** HELP *****/ 243 | 244 | .welcome__help { 245 | background-color: white; 246 | box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.2); 247 | padding: 10px; 248 | position: fixed; 249 | right: -5px; 250 | top: 50%; 251 | transform: translateY(-50%); 252 | border-radius: 5px; 253 | text-align: center; 254 | } 255 | 256 | .welcome__help h2 { 257 | color: #aaa; 258 | font-size: 12px; 259 | margin: 10px 0; 260 | } 261 | 262 | .help__circle { 263 | width: 36px; 264 | height: 36px; 265 | border-radius: 50%; 266 | border: 2px solid #ccc; 267 | display: block; 268 | margin: 10px auto; 269 | transition: all ease 0.2s; 270 | position:relative; 271 | } 272 | 273 | .help__circle svg { 274 | position:absolute; 275 | left: 50%; 276 | top: 50%; 277 | transform:translate(-50%, -50%); 278 | } 279 | 280 | .help__circle:hover { 281 | border-color: #67cece; 282 | background-color: #afe5e5; 283 | } 284 | 285 | /***** MEDIAS *****/ 286 | 287 | @media (max-width: 1200px) { 288 | .main__aside, 289 | .welcome__help { 290 | display: none; 291 | } 292 | .main__content { 293 | width: 100%; 294 | text-align: center; 295 | padding: 20px; 296 | } 297 | } 298 | 299 | @media (max-width: 600px) { 300 | .welcome__main { 301 | width: calc(100% - 40px); 302 | } 303 | .welcome h1 { 304 | display: none; 305 | } 306 | .welcome__flag, 307 | .main__other { 308 | display: none; 309 | } 310 | .main__content { 311 | padding: 10px; 312 | } 313 | } 314 | -------------------------------------------------------------------------------- /docker-compose.prod.yml: -------------------------------------------------------------------------------- 1 | version: '3.4' 2 | 3 | x-cache-from: 4 | - &api-cache-from 5 | cache_from: 6 | - ${NGINX_IMAGE:-quay.io/api-platform/nginx} 7 | - ${PHP_IMAGE:-quay.io/api-platform/php} 8 | 9 | services: 10 | php: 11 | build: 12 | context: ./api 13 | target: api_platform_php 14 | <<: *api-cache-from 15 | image: ${PHP_IMAGE:-quay.io/api-platform/php} 16 | environment: 17 | - APP_ENV=prod 18 | depends_on: 19 | - db 20 | volumes: 21 | - ./api:/srv/api:rw,cached 22 | - ./api/docker/php/conf.d/api-platform.dev.ini/:/usr/local/etc/php/conf.d/api-platform.ini 23 | # if you develop on Linux, you may use a bind-mounted host directory instead 24 | # - ./api/var:/srv/api/var:rw 25 | 26 | api: 27 | build: 28 | context: ./api 29 | target: api_platform_nginx 30 | <<: *api-cache-from 31 | image: ${NGINX_IMAGE:-quay.io/api-platform/nginx} 32 | depends_on: 33 | - php 34 | volumes: 35 | - ./api/public:/srv/api/public:ro 36 | 37 | cache-proxy: 38 | build: 39 | context: ./api 40 | target: api_platform_varnish 41 | restart: always 42 | depends_on: 43 | - api 44 | tmpfs: 45 | - /usr/local/var/varnish:exec 46 | 47 | vulcain: 48 | image: dunglas/vulcain 49 | environment: 50 | - CERT_FILE=/certs/localhost.crt 51 | - KEY_FILE=/certs/localhost.key 52 | - UPSTREAM=http://cache-proxy 53 | depends_on: 54 | - cache-proxy 55 | - dev-tls 56 | volumes: 57 | - dev-certs:/certs:ro 58 | ports: 59 | - target: 443 60 | published: 8443 61 | protocol: tcp 62 | 63 | db: 64 | image: postgres:12-alpine 65 | environment: 66 | - POSTGRES_DB=api 67 | - POSTGRES_PASSWORD=!ChangeMe! 68 | - POSTGRES_USER=api-platform 69 | volumes: 70 | - db-data:/var/lib/postgresql/data:rw 71 | # you may use a bind-mounted host directory instead, so that it is harder to accidentally remove the volume and lose all your data! 72 | # - ./api/docker/db/data:/var/lib/postgresql/data:rw 73 | ports: 74 | - target: 5432 75 | published: 5432 76 | protocol: tcp 77 | 78 | mercure: 79 | image: dunglas/mercure 80 | environment: 81 | - ALLOW_ANONYMOUS=1 82 | - CERT_FILE=/certs/localhost.crt 83 | - CORS_ALLOWED_ORIGINS=* 84 | - DEMO=1 85 | - JWT_KEY=!ChangeMe! 86 | - KEY_FILE=/certs/localhost.key 87 | - PUBLISH_ALLOWED_ORIGINS=https://localhost:1337 # required for publishing from the demo page 88 | depends_on: 89 | - dev-tls 90 | volumes: 91 | - dev-certs:/certs:ro 92 | ports: 93 | - target: 443 94 | published: 1337 95 | protocol: tcp 96 | 97 | dev-tls: 98 | build: 99 | context: ./docker/dev-tls 100 | volumes: 101 | - dev-certs:/certs:rw 102 | ports: 103 | - target: 80 104 | published: 80 105 | protocol: tcp 106 | 107 | volumes: 108 | db-data: {} 109 | dev-certs: {} 110 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.4' 2 | 3 | x-cache-from: 4 | - &api-cache-from 5 | cache_from: 6 | - ${NGINX_IMAGE:-quay.io/api-platform/nginx} 7 | - ${PHP_IMAGE:-quay.io/api-platform/php} 8 | 9 | services: 10 | php: 11 | build: 12 | context: ./api 13 | target: api_platform_php 14 | <<: *api-cache-from 15 | image: ${PHP_IMAGE:-quay.io/api-platform/php} 16 | depends_on: 17 | - db 18 | volumes: 19 | - ./api:/srv/api:rw,cached 20 | - ./api/docker/php/conf.d/api-platform.dev.ini/:/usr/local/etc/php/conf.d/api-platform.ini 21 | # if you develop on Linux, you may use a bind-mounted host directory instead 22 | # - ./api/var:/srv/api/var:rw 23 | 24 | api: 25 | build: 26 | context: ./api 27 | target: api_platform_nginx 28 | <<: *api-cache-from 29 | image: ${NGINX_IMAGE:-quay.io/api-platform/nginx} 30 | depends_on: 31 | - php 32 | volumes: 33 | - ./api/public:/srv/api/public:ro 34 | 35 | vulcain: 36 | image: dunglas/vulcain 37 | environment: 38 | - CERT_FILE=/certs/localhost.crt 39 | - KEY_FILE=/certs/localhost.key 40 | - UPSTREAM=http://api 41 | depends_on: 42 | - api 43 | - dev-tls 44 | volumes: 45 | - dev-certs:/certs:ro 46 | ports: 47 | - target: 443 48 | published: 8443 49 | protocol: tcp 50 | 51 | db: 52 | image: postgres:12-alpine 53 | environment: 54 | - POSTGRES_DB=api 55 | - POSTGRES_PASSWORD=!ChangeMe! 56 | - POSTGRES_USER=api-platform 57 | volumes: 58 | - db-data:/var/lib/postgresql/data:rw 59 | # you may use a bind-mounted host directory instead, so that it is harder to accidentally remove the volume and lose all your data! 60 | # - ./api/docker/db/data:/var/lib/postgresql/data:rw 61 | ports: 62 | - target: 5432 63 | published: 5432 64 | protocol: tcp 65 | 66 | mercure: 67 | image: dunglas/mercure 68 | environment: 69 | - ALLOW_ANONYMOUS=1 70 | - CERT_FILE=/certs/localhost.crt 71 | - CORS_ALLOWED_ORIGINS=* 72 | - DEMO=1 73 | - JWT_KEY=!ChangeMe! 74 | - KEY_FILE=/certs/localhost.key 75 | - PUBLISH_ALLOWED_ORIGINS=https://localhost:1337 # required for publishing from the demo page 76 | depends_on: 77 | - dev-tls 78 | volumes: 79 | - dev-certs:/certs:ro 80 | ports: 81 | - target: 443 82 | published: 1337 83 | protocol: tcp 84 | 85 | client: 86 | build: 87 | context: ./client 88 | target: api_platform_client_development 89 | cache_from: 90 | - ${CLIENT_IMAGE:-quay.io/api-platform/client} 91 | image: ${CLIENT_IMAGE:-quay.io/api-platform/client} 92 | environment: 93 | - API_PLATFORM_CLIENT_GENERATOR_ENTRYPOINT=http://api 94 | - API_PLATFORM_CLIENT_GENERATOR_OUTPUT=src 95 | depends_on: 96 | - dev-tls 97 | volumes: 98 | - ./client:/usr/src/client:rw,cached 99 | - dev-certs:/usr/src/client/node_modules/webpack-dev-server/ssl:ro 100 | ports: 101 | - target: 3000 102 | published: 443 103 | protocol: tcp 104 | 105 | admin: 106 | build: 107 | context: ./admin 108 | target: api_platform_admin_development 109 | cache_from: 110 | - ${ADMIN_IMAGE:-quay.io/api-platform/admin} 111 | image: ${ADMIN_IMAGE:-quay.io/api-platform/admin} 112 | depends_on: 113 | - dev-tls 114 | volumes: 115 | - ./admin:/usr/src/admin:rw,cached 116 | - dev-certs:/usr/src/admin/node_modules/webpack-dev-server/ssl:ro 117 | ports: 118 | - target: 3000 119 | published: 444 120 | protocol: tcp 121 | 122 | dev-tls: 123 | build: 124 | context: ./docker/dev-tls 125 | volumes: 126 | - dev-certs:/certs:rw 127 | ports: 128 | - target: 80 129 | published: 80 130 | protocol: tcp 131 | 132 | volumes: 133 | db-data: {} 134 | dev-certs: {} 135 | -------------------------------------------------------------------------------- /docker/dev-tls/Dockerfile: -------------------------------------------------------------------------------- 1 | # use this self-generated certificate only in dev, IT IS NOT SECURE! 2 | 3 | 4 | # https://docs.docker.com/engine/reference/builder/#understand-how-arg-and-from-interact 5 | ARG NGINX_VERSION=1.17 6 | 7 | 8 | FROM nginx:${NGINX_VERSION}-alpine 9 | 10 | # persistent / runtime deps 11 | RUN apk add --no-cache \ 12 | nss-tools \ 13 | ; 14 | 15 | WORKDIR /certs 16 | 17 | ARG MKCERT_VERSION=1.4.1 18 | RUN set -eux; \ 19 | wget -O /usr/local/bin/mkcert https://github.com/FiloSottile/mkcert/releases/download/v$MKCERT_VERSION/mkcert-v$MKCERT_VERSION-linux-amd64; \ 20 | chmod +x /usr/local/bin/mkcert; \ 21 | mkcert --cert-file localhost.crt --key-file localhost.key localhost; \ 22 | # the file must be named server.pem - the default certificate path in webpack-dev-server 23 | cat localhost.key localhost.crt > server.pem 24 | 25 | VOLUME /certs 26 | 27 | # add redirect from http://localhost to https://localhost 28 | RUN set -eux; \ 29 | { \ 30 | echo 'server {'; \ 31 | echo ' return 301 https://$host$request_uri;'; \ 32 | echo '}'; \ 33 | } | tee /etc/nginx/conf.d/default.conf 34 | -------------------------------------------------------------------------------- /test-http-client.php: -------------------------------------------------------------------------------- 1 | 'https://localhost:8443', 'verify_peer' => false], 500, 500); 16 | //$client->setLogger(new Logger(LogLevel::DEBUG)); 17 | 18 | $conferences = $client->request( 19 | 'GET', 20 | '/conferences', 21 | ['headers' => ['Preload' => '/hydra:member/*/@id/sessions/*/feedback/*']] 22 | )->toArray(); 23 | foreach ($conferences['hydra:member'] as $conferenceRel) { 24 | $conference = $client->request('GET', $conferenceRel['@id'])->toArray(); 25 | foreach ($conference['sessions'] as $sessionUrl) { 26 | $session = $client->request('GET', $sessionUrl)->toArray(); 27 | foreach ($session['feedback'] as $feedbackURL) { 28 | $feedback = $client->request('GET', $feedbackURL)->toArray(); 29 | 30 | if (++$i === 100) { 31 | $time = microtime(true) - $start; 32 | echo "All data retrieved in {$time}".PHP_EOL; 33 | } 34 | } 35 | } 36 | } 37 | --------------------------------------------------------------------------------