This commit is contained in:
Pitchaya Boonsarngsuk
2019-07-05 16:54:32 +01:00
commit c3d3dcbff7
96 changed files with 32248 additions and 0 deletions

84
.editorconfig Normal file
View File

@@ -0,0 +1,84 @@
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org
root = true
[*]
# Change these settings to your own preference
indent_style = space
indent_size = 4
# We recommend you to keep these unchanged
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.feature]
indent_style = space
indent_size = 2
[*.js]
indent_style = space
indent_size = 2
[*.json]
indent_style = space
indent_size = 2
[*.md]
trim_trailing_whitespace = false
[*.php]
indent_style = space
indent_size = 4
[*.sh]
indent_style = tab
indent_size = 4
[*.vcl]
indent_style = space
indent_size = 2
[*.xml]
indent_style = space
indent_size = 4
[*.{yaml,yml}]
indent_style = space
indent_size = 4
trim_trailing_whitespace = false
[.gitmodules]
indent_style = tab
indent_size = 4
[.php_cs{,.dist}]
indent_style = space
indent_size = 4
[.travis.yml]
indent_style = space
indent_size = 2
[composer.json]
indent_style = space
indent_size = 4
[docker-compose{,.override}.{yaml,yml}]
indent_style = space
indent_size = 2
[Dockerfile]
indent_style = tab
indent_size = 4
[package.json]
indent_style = space
indent_size = 2
[phpunit.xml{,.dist}]
indent_style = space
indent_size = 4

1
.env Normal file
View File

@@ -0,0 +1 @@
CONTAINER_REGISTRY_BASE=quay.io/api-platform

22
.gitattributes vendored Normal file
View File

@@ -0,0 +1,22 @@
* text=auto eol=lf
*.conf text eol=lf
*.html text eol=lf
*.ini text eol=lf
*.js text eol=lf
*.json text eol=lf
*.md text eol=lf
*.php text eol=lf
*.sh text eol=lf
*.yaml text eol=lf
*.yml text eol=lf
bin/console text eol=lf
*.ico binary
*.png binary
.github export-ignore
.travis.yml export-ignore
LICENSE export-ignore
README.md export-ignore
update-deps.sh export-ignore

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/docker-compose.override.yaml
/docker-compose.override.yml

14
admin/.dockerignore Normal file
View File

@@ -0,0 +1,14 @@
**/*.log
**/*.md
**/._*
**/.dockerignore
**/.DS_Store
**/.git/
**/.gitattributes
**/.gitignore
**/.gitmodules
**/Dockerfile*
**/Thumbs.db
.env*
build/
node_modules/

1
admin/.env Normal file
View File

@@ -0,0 +1 @@
REACT_APP_API_ENTRYPOINT=https://localhost:8443

20
admin/.gitignore vendored Normal file
View File

@@ -0,0 +1,20 @@
# See https://help.github.com/ignore-files/ for more about ignoring files.
# dependencies
/node_modules
# testing
/coverage
# production
/build
# misc
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

19
admin/Dockerfile Normal file
View File

@@ -0,0 +1,19 @@
FROM node:11.5-alpine
RUN mkdir -p /usr/src/admin
WORKDIR /usr/src/admin
# Prevent the reinstallation of node modules at every changes in the source code
COPY package.json yarn.lock ./
RUN apk add --no-cache --virtual .gyp \
python \
make \
g++ \
&& yarn install \
&& apk del .gyp
COPY . ./
CMD yarn start

24
admin/package.json Normal file
View File

@@ -0,0 +1,24 @@
{
"name": "admin",
"version": "0.1.0",
"private": true,
"dependencies": {
"@api-platform/admin": "^0.6.1",
"@babel/runtime": "7.0.0-beta.55",
"react": "^16.3.0",
"react-dom": "^16.3.0",
"react-scripts": "^2.1.2"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"browserslist": [
">0.2%",
"not dead",
"not ie <= 11",
"not op_mini all"
]
}

BIN
admin/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

40
admin/public/index.html Normal file
View File

@@ -0,0 +1,40 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<!--
manifest.json provides metadata used when your web app is added to the
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>API Platform Admin</title>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

View File

@@ -0,0 +1,15 @@
{
"short_name": "API Platform Admin",
"name": "Admin",
"icons": [
{
"src": "favicon.ico",
"sizes": "192x192",
"type": "image/png"
}
],
"start_url": "./index.html",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

4
admin/src/App.js Normal file
View File

@@ -0,0 +1,4 @@
import React from 'react';
import { HydraAdmin } from '@api-platform/admin';
export default () => <HydraAdmin entrypoint={process.env.REACT_APP_API_ENTRYPOINT}/>;

9
admin/src/App.test.js Normal file
View File

@@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<App />, div);
ReactDOM.unmountComponentAtNode(div);
});

11
admin/src/index.js Normal file
View File

@@ -0,0 +1,11 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import * as serviceWorker from './serviceWorker';
ReactDOM.render(<App />, document.getElementById('root'));
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: http://bit.ly/CRA-PWA
serviceWorker.unregister();

135
admin/src/serviceWorker.js Normal file
View File

@@ -0,0 +1,135 @@
// This optional code is used to register a service worker.
// register() is not called by default.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on subsequent visits to a page, after all the
// existing tabs open on the page have been closed, since previously cached
// resources are updated in the background.
// To learn more about the benefits of this model and instructions on how to
// opt-in, read http://bit.ly/CRA-PWA
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.1/8 is considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
export function register(config) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
return;
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config);
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit http://bit.ly/CRA-PWA'
);
});
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
});
}
}
function registerValidSW(swUrl, config) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
return;
}
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the updated precached content has been fetched,
// but the previous service worker will still serve the older
// content until all client tabs are closed.
console.log(
'New content is available and will be used when all ' +
'tabs for this page are closed. See http://bit.ly/CRA-PWA.'
);
// Execute callback
if (config && config.onUpdate) {
config.onUpdate(registration);
}
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.');
// Execute callback
if (config && config.onSuccess) {
config.onSuccess(registration);
}
}
}
};
};
})
.catch(error => {
console.error('Error during service worker registration:', error);
});
}
function checkValidServiceWorker(swUrl, config) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl)
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type');
if (
response.status === 404 ||
(contentType != null && contentType.indexOf('javascript') === -1)
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload();
});
});
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl, config);
}
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.'
);
});
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(registration => {
registration.unregister();
});
}
}

11067
admin/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

22
api/.dockerignore Normal file
View File

@@ -0,0 +1,22 @@
**/*.log
**/*.md
**/*.php~
**/._*
**/.dockerignore
**/.DS_Store
**/.git/
**/.gitattributes
**/.gitignore
**/.gitmodules
**/Dockerfile
**/Thumbs.db
.editorconfig
.env.*
.php_cs.cache
bin/*
!bin/console
docker/db/data/
helm/
public/bundles/
var/
vendor/

40
api/.env Normal file
View File

@@ -0,0 +1,40 @@
# In all environments, the following files are loaded if they exist,
# the later taking precedence over the former:
#
# * .env contains default values for the environment variables needed by the app
# * .env.local uncommitted file with local overrides
# * .env.$APP_ENV committed environment-specific defaults
# * .env.$APP_ENV.local uncommitted environment-specific overrides
#
# Real environment variables win over .env files.
#
# DO NOT DEFINE PRODUCTION SECRETS IN THIS FILE NOR IN ANY OTHER COMMITTED FILES.
#
# Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2).
# https://symfony.com/doc/current/best_practices/configuration.html#infrastructure-related-configuration
MERCURE_SUBSCRIBE_URL=http://localhost:1337/hub
VARNISH_URL=http://cache-proxy
###> symfony/framework-bundle ###
APP_ENV=dev
APP_SECRET=!ChangeMe!
TRUSTED_PROXIES=10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
TRUSTED_HOSTS='^localhost|api$'
###< symfony/framework-bundle ###
###> doctrine/doctrine-bundle ###
# Format described at http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url
# For an SQLite database, use: "sqlite:///%kernel.project_dir%/var/data.db"
# Configure your db driver and server_version in config/packages/doctrine.yaml
DATABASE_URL=postgres://api-platform:!ChangeMe!@db/api
###< doctrine/doctrine-bundle ###
###> nelmio/cors-bundle ###
CORS_ALLOW_ORIGIN=^https?://localhost(:[0-9]+)?$
###< nelmio/cors-bundle ###
###> symfony/mercure-bundle ###
MERCURE_PUBLISH_URL=http://mercure/hub
MERCURE_JWT_SECRET=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXJjdXJlIjp7InN1YnNjcmliZSI6WyJmb28iLCJiYXIiXSwicHVibGlzaCI6WyJmb28iXX19.LRLvirgONK13JgacQ_VbcjySbVhkSmHy3IznH3tA9PM
###< symfony/mercure-bundle ###

15
api/.gitignore vendored Normal file
View File

@@ -0,0 +1,15 @@
/helm/api/charts
###> symfony/framework-bundle ###
/.env.local
/.env.local.php
/.env.*.local
/public/bundles/
/var/
/vendor/
###< symfony/framework-bundle ###
###> friendsofphp/php-cs-fixer ###
/.php_cs
/.php_cs.cache
###< friendsofphp/php-cs-fixer ###

14
api/.php_cs.dist Normal file
View File

@@ -0,0 +1,14 @@
<?php
$finder = PhpCsFixer\Finder::create()
->in(__DIR__)
->exclude('var')
;
return PhpCsFixer\Config::create()
->setRules([
'@Symfony' => true,
'array_syntax' => ['syntax' => 'short'],
])
->setFinder($finder)
;

121
api/Dockerfile Normal file
View File

@@ -0,0 +1,121 @@
# the different stages of this Dockerfile are meant to be built into separate images
# https://docs.docker.com/develop/develop-images/multistage-build/#stop-at-a-specific-build-stage
# https://docs.docker.com/compose/compose-file/#target
# https://docs.docker.com/engine/reference/builder/#understand-how-arg-and-from-interact
ARG PHP_VERSION=7.3
ARG NGINX_VERSION=1.15
ARG VARNISH_VERSION=6.2
# "php" stage
FROM php:${PHP_VERSION}-fpm-alpine AS api_platform_php
# persistent / runtime deps
RUN apk add --no-cache \
acl \
file \
gettext \
git \
;
ARG APCU_VERSION=5.1.17
RUN set -eux; \
apk add --no-cache --virtual .build-deps \
$PHPIZE_DEPS \
icu-dev \
libzip-dev \
postgresql-dev \
zlib-dev \
; \
\
docker-php-ext-configure zip --with-libzip; \
docker-php-ext-install -j$(nproc) \
intl \
pdo_pgsql \
zip \
; \
pecl install \
apcu-${APCU_VERSION} \
; \
pecl clear-cache; \
docker-php-ext-enable \
apcu \
opcache \
; \
\
runDeps="$( \
scanelf --needed --nobanner --format '%n#p' --recursive /usr/local/lib/php/extensions \
| tr ',' '\n' \
| sort -u \
| awk 'system("[ -e /usr/local/lib/" $1 " ]") == 0 { next } { print "so:" $1 }' \
)"; \
apk add --no-cache --virtual .api-phpexts-rundeps $runDeps; \
\
apk del .build-deps
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
RUN ln -s $PHP_INI_DIR/php.ini-production $PHP_INI_DIR/php.ini
COPY docker/php/conf.d/api-platform.ini $PHP_INI_DIR/conf.d/api-platform.ini
# https://getcomposer.org/doc/03-cli.md#composer-allow-superuser
ENV COMPOSER_ALLOW_SUPERUSER=1
# install Symfony Flex globally to speed up download of Composer packages (parallelized prefetching)
RUN set -eux; \
composer global require "symfony/flex" --prefer-dist --no-progress --no-suggest --classmap-authoritative; \
composer clear-cache
ENV PATH="${PATH}:/root/.composer/vendor/bin"
WORKDIR /srv/api
# build for production
ARG APP_ENV=prod
# prevent the reinstallation of vendors at every changes in the source code
COPY composer.json composer.lock symfony.lock ./
RUN set -eux; \
composer install --prefer-dist --no-dev --no-scripts --no-progress --no-suggest; \
composer clear-cache
# do not use .env files in production
COPY .env ./
RUN composer dump-env prod; \
rm .env
# copy only specifically what we need
COPY bin bin/
COPY config config/
COPY public public/
COPY src src/
RUN set -eux; \
mkdir -p var/cache var/log; \
composer dump-autoload --classmap-authoritative --no-dev; \
composer run-script --no-dev post-install-cmd; \
chmod +x bin/console; sync
VOLUME /srv/api/var
COPY docker/php/docker-entrypoint.sh /usr/local/bin/docker-entrypoint
RUN chmod +x /usr/local/bin/docker-entrypoint
ENTRYPOINT ["docker-entrypoint"]
CMD ["php-fpm"]
# "nginx" stage
# depends on the "php" stage above
FROM nginx:${NGINX_VERSION}-alpine AS api_platform_nginx
COPY docker/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf
WORKDIR /srv/api
COPY --from=api_platform_php /srv/api/public public/
# "varnish" stage
# does not depend on any of the above stages, but placed here to keep everything in one Dockerfile
FROM cooptilleuls/varnish:${VARNISH_VERSION}-alpine AS api_platform_varnish
COPY docker/varnish/conf/default.vcl /usr/local/etc/varnish/default.vcl

42
api/bin/console Executable file
View File

@@ -0,0 +1,42 @@
#!/usr/bin/env php
<?php
use App\Kernel;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Debug\Debug;
if (false === in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true)) {
echo 'Warning: The console should be invoked via the CLI version of PHP, not the '.\PHP_SAPI.' SAPI'.\PHP_EOL;
}
set_time_limit(0);
require dirname(__DIR__).'/vendor/autoload.php';
if (!class_exists(Application::class)) {
throw new RuntimeException('You need to add "symfony/framework-bundle" as a Composer dependency.');
}
$input = new ArgvInput();
if (null !== $env = $input->getParameterOption(['--env', '-e'], null, true)) {
putenv('APP_ENV='.$_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = $env);
}
if ($input->hasParameterOption('--no-debug', true)) {
putenv('APP_DEBUG='.$_SERVER['APP_DEBUG'] = $_ENV['APP_DEBUG'] = '0');
}
require dirname(__DIR__).'/config/bootstrap.php';
if ($_SERVER['APP_DEBUG']) {
umask(0000);
if (class_exists(Debug::class)) {
Debug::enable();
}
}
$kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
$application = new Application($kernel);
$application->run($input);

67
api/composer.json Normal file
View File

@@ -0,0 +1,67 @@
{
"type": "project",
"license": "proprietary",
"require": {
"php": "^7.1.3",
"ext-ctype": "*",
"ext-iconv": "*",
"api-platform/api-pack": "^1.1",
"guzzlehttp/guzzle": "^6.3",
"symfony/console": "4.3.*",
"symfony/dotenv": "4.3.*",
"symfony/flex": "^1.1",
"symfony/framework-bundle": "4.3.*",
"symfony/mercure-bundle": "*",
"symfony/yaml": "4.3.*"
},
"require-dev": {
"api-platform/schema-generator": "^2.1",
"symfony/maker-bundle": "^1.11",
"symfony/profiler-pack": "^1.0"
},
"config": {
"preferred-install": {
"*": "dist"
},
"sort-packages": true
},
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"App\\Tests\\": "tests/"
}
},
"replace": {
"paragonie/random_compat": "2.*",
"symfony/polyfill-ctype": "*",
"symfony/polyfill-iconv": "*",
"symfony/polyfill-php71": "*",
"symfony/polyfill-php70": "*",
"symfony/polyfill-php56": "*"
},
"scripts": {
"auto-scripts": {
"cache:clear": "symfony-cmd",
"assets:install %PUBLIC_DIR%": "symfony-cmd"
},
"post-install-cmd": [
"@auto-scripts"
],
"post-update-cmd": [
"@auto-scripts"
]
},
"conflict": {
"symfony/symfony": "*"
},
"extra": {
"symfony": {
"allow-contrib": false,
"require": "4.3.*"
}
}
}

5874
api/composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

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

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

14
api/config/bundles.php Normal file
View File

@@ -0,0 +1,14 @@
<?php
return [
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
Symfony\Bundle\MercureBundle\MercureBundle::class => ['all' => true],
Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
Doctrine\Bundle\DoctrineCacheBundle\DoctrineCacheBundle::class => ['all' => true],
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
ApiPlatform\Core\Bridge\Symfony\Bundle\ApiPlatformBundle::class => ['all' => true],
Nelmio\CorsBundle\NelmioCorsBundle::class => ['all' => true],
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
];

View File

@@ -0,0 +1,24 @@
parameters:
# Adds a fallback VARNISH_URL if the env var is not set.
# This allows you to run cache:warmup even if your
# environment variables are not available yet.
# You should not need to change this value.
env(VARNISH_URL): ''
api_platform:
mapping:
paths: ['%kernel.project_dir%/src/Entity']
title: Hello API Platform
version: 1.0.0
# Varnish integration, remove if unwanted
http_cache:
invalidation:
enabled: true
varnish_urls: ['%env(VARNISH_URL)%']
max_age: 0
shared_max_age: 3600
vary: ['Content-Type', 'Authorization']
public: true
# Mercure integration, remove if unwanted
mercure:
hub_url: '%env(MERCURE_SUBSCRIBE_URL)%'

View File

@@ -0,0 +1,19 @@
framework:
cache:
# Put the unique name of your app here: the prefix seed
# is used to compute stable namespaces for cache keys.
#prefix_seed: your_vendor_name/app_name
# The app cache caches to the filesystem by default.
# Other options include:
# Redis
#app: cache.adapter.redis
#default_redis_provider: redis://localhost
# APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues)
#app: cache.adapter.apcu
# Namespaced pools use the above "app" backend by default
#pools:
#my.dedicated.cache: ~

View File

@@ -0,0 +1,3 @@
framework:
router:
strict_requirements: true

View File

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

View File

@@ -0,0 +1,18 @@
doctrine:
dbal:
# configure these for your database server
driver: 'pdo_pgsql'
server_version: '10'
url: '%env(resolve:DATABASE_URL)%'
orm:
auto_generate_proxy_classes: true
naming_strategy: doctrine.orm.naming_strategy.underscore
auto_mapping: true
mappings:
App:
is_bundle: false
type: annotation
dir: '%kernel.project_dir%/src/Entity'
prefix: 'App\Entity'
alias: App

View File

@@ -0,0 +1,16 @@
framework:
secret: '%env(APP_SECRET)%'
#csrf_protection: true
#http_method_override: true
# Enables session support. Note that the session will ONLY be started if you read or write from it.
# Remove or comment this section to explicitly disable session support.
session:
handler_id: ~
cookie_secure: auto
cookie_samesite: lax
#esi: true
#fragments: true
php_errors:
log: true

View File

@@ -0,0 +1,5 @@
mercure:
hubs:
default:
url: '%env(MERCURE_PUBLISH_URL)%'
jwt: '%env(MERCURE_JWT_SECRET)%'

View File

@@ -0,0 +1,10 @@
nelmio_cors:
defaults:
origin_regex: true
allow_origin: ['%env(CORS_ALLOW_ORIGIN)%']
allow_methods: ['GET', 'OPTIONS', 'POST', 'PUT', 'PATCH', 'DELETE']
allow_headers: ['Content-Type', 'Authorization']
expose_headers: ['Link']
max_age: 3600
paths:
'^/': ~

View File

@@ -0,0 +1,32 @@
doctrine:
orm:
auto_generate_proxy_classes: false
metadata_cache_driver:
type: service
id: doctrine.system_cache_provider
query_cache_driver:
type: service
id: doctrine.system_cache_provider
result_cache_driver:
type: service
id: doctrine.result_cache_provider
services:
doctrine.result_cache_provider:
class: Symfony\Component\Cache\DoctrineProvider
public: false
arguments:
- '@doctrine.result_cache_pool'
doctrine.system_cache_provider:
class: Symfony\Component\Cache\DoctrineProvider
public: false
arguments:
- '@doctrine.system_cache_pool'
framework:
cache:
pools:
doctrine.result_cache_pool:
adapter: cache.app
doctrine.system_cache_pool:
adapter: cache.system

View File

@@ -0,0 +1,4 @@
framework:
router:
strict_requirements: ~
utf8: true

View File

@@ -0,0 +1,22 @@
security:
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
providers:
in_memory: { memory: ~ }
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: true
# activate different ways to authenticate
# https://symfony.com/doc/current/security.html#firewalls-authentication
# https://symfony.com/doc/current/security/impersonating_user.html
# switch_user: true
# Easy way to control access for large sections of your site
# Note: Only the *first* access control that matches will be used
access_control:
# - { path: ^/admin, roles: ROLE_ADMIN }
# - { path: ^/profile, roles: ROLE_USER }

View File

@@ -0,0 +1,4 @@
framework:
test: true
session:
storage_id: session.storage.mock_file

View File

@@ -0,0 +1,3 @@
framework:
router:
strict_requirements: true

View File

@@ -0,0 +1,3 @@
framework:
validation:
not_compromised_password: false

View File

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

View File

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

View File

@@ -0,0 +1,8 @@
framework:
validation:
email_validation_mode: html5
# Enables validator auto-mapping support.
# For instance, basic validation constraints will be inferred from Doctrine's metadata.
#auto_mapping:
# App\Entity\: []

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

@@ -0,0 +1,3 @@
#index:
# path: /
# controller: App\Controller\DefaultController::index

View File

@@ -0,0 +1,3 @@
controllers:
resource: ../../src/Controller/
type: annotation

View File

@@ -0,0 +1,3 @@
api_platform:
resource: .
type: api_platform

View File

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

View File

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

27
api/config/services.yaml Normal file
View File

@@ -0,0 +1,27 @@
# This file is the entry point to configure your own services.
# Files in the packages/ subdirectory configure your dependencies.
# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
parameters:
services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/*'
exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'
# controllers are imported separately to make sure services can be injected
# as action arguments even if you don't extend any base controller class
App\Controller\:
resource: '../src/Controller'
tags: ['controller.service_arguments']
# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones

View File

@@ -0,0 +1,38 @@
server {
root /srv/api/public;
location / {
# try to serve file directly, fallback to index.php
try_files $uri /index.php$is_args$args;
}
location ~ ^/index\.php(/|$) {
# Comment the next line and uncomment the next to enable dynamic resolution (incompatible with Kubernetes)
fastcgi_pass php:9000;
#resolver 127.0.0.11;
#set $upstream_host php;
#fastcgi_pass $upstream_host:9000;
fastcgi_split_path_info ^(.+\.php)(/.*)$;
include fastcgi_params;
# When you are using symlinks to link the document root to the
# current version of your application, you should pass the real
# application path instead of the path to the symlink to PHP
# FPM.
# Otherwise, PHP's OPcache may not properly detect changes to
# your PHP files (see https://github.com/zendtech/ZendOptimizerPlus/issues/126
# for more information).
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $realpath_root;
# Prevents URIs that include the front controller. This will 404:
# http://domain.tld/index.php/some-path
# Remove the internal directive to allow URIs like this
internal;
}
# return 404 for all other php files not matching the front controller
# this prevents access to other php files you don't want to be accessible.
location ~ \.php$ {
return 404;
}
}

View File

@@ -0,0 +1,11 @@
apc.enable_cli = 1
date.timezone = UTC
session.auto_start = Off
short_open_tag = Off
# http://symfony.com/doc/current/performance.html
opcache.interned_strings_buffer = 16
opcache.max_accelerated_files = 20000
opcache.memory_consumption = 256
realpath_cache_size = 4096K
realpath_cache_ttl = 600

View File

@@ -0,0 +1,34 @@
#!/bin/sh
set -e
# first arg is `-f` or `--some-option`
if [ "${1#-}" != "$1" ]; then
set -- php-fpm "$@"
fi
if [ "$1" = 'php-fpm' ] || [ "$1" = 'php' ] || [ "$1" = 'bin/console' ]; then
PHP_INI_RECOMMENDED="$PHP_INI_DIR/php.ini-production"
if [ "$APP_ENV" != 'prod' ]; then
PHP_INI_RECOMMENDED="$PHP_INI_DIR/php.ini-development"
fi
ln -sf "$PHP_INI_RECOMMENDED" "$PHP_INI_DIR/php.ini"
mkdir -p var/cache var/log
setfacl -R -m u:www-data:rwX -m u:"$(whoami)":rwX var
setfacl -dR -m u:www-data:rwX -m u:"$(whoami)":rwX var
if [ "$APP_ENV" != 'prod' ]; then
composer install --prefer-dist --no-progress --no-suggest --no-interaction
fi
echo "Waiting for db to be ready..."
until bin/console doctrine:query:sql "SELECT 1" > /dev/null 2>&1; do
sleep 1
done
if [ "$APP_ENV" != 'prod' ]; then
bin/console doctrine:schema:update --force --no-interaction
fi
fi
exec docker-php-entrypoint "$@"

View File

@@ -0,0 +1,95 @@
vcl 4.0;
import std;
backend default {
.host = "api";
.port = "80";
# Health check
#.probe = {
# .url = "/";
# .timeout = 5s;
# .interval = 10s;
# .window = 5;
# .threshold = 3;
#}
}
# Hosts allowed to send BAN requests
acl invalidators {
"localhost";
"php";
# local Kubernetes network
"10.0.0.0"/8;
"172.16.0.0"/12;
"192.168.0.0"/16;
}
sub vcl_recv {
if (req.restarts > 0) {
set req.hash_always_miss = true;
}
# Remove the "Forwarded" HTTP header if exists (security)
unset req.http.forwarded;
# To allow API Platform to ban by cache tags
if (req.method == "BAN") {
if (client.ip !~ invalidators) {
return (synth(405, "Not allowed"));
}
if (req.http.ApiPlatform-Ban-Regex) {
ban("obj.http.Cache-Tags ~ " + req.http.ApiPlatform-Ban-Regex);
return (synth(200, "Ban added"));
}
return (synth(400, "ApiPlatform-Ban-Regex HTTP header must be set."));
}
# For health checks
if (req.method == "GET" && req.url == "/healthz") {
return (synth(200, "OK"));
}
}
sub vcl_hit {
if (obj.ttl >= 0s) {
# A pure unadulterated hit, deliver it
return (deliver);
}
if (std.healthy(req.backend_hint)) {
# The backend is healthy
# Fetch the object from the backend
return (restart);
}
# No fresh object and the backend is not healthy
if (obj.ttl + obj.grace > 0s) {
# Deliver graced object
# Automatically triggers a background fetch
return (deliver);
}
# No valid object to deliver
# No healthy backend to handle request
# Return error
return (synth(503, "API is down"));
}
sub vcl_deliver {
# Don't send cache tags related headers to the client
unset resp.http.url;
# Comment the following line to send the "Cache-Tags" header to the client (e.g. to use CloudFlare cache tags)
unset resp.http.Cache-Tags;
}
sub vcl_backend_response {
# Ban lurker friendly header
set beresp.http.url = bereq.url;
# Add a grace in case the backend is down
set beresp.grace = 1h;
}

22
api/helm/api/.helmignore Normal file
View File

@@ -0,0 +1,22 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/

7
api/helm/api/Chart.yaml Normal file
View File

@@ -0,0 +1,7 @@
apiVersion: v1
appVersion: 0.1.0
description: A Helm chart for an API Platform API
name: api
version: 0.1.0
home: https://api-platform.com
icon: https://api-platform.com/logo-250x250.png

View File

@@ -0,0 +1,9 @@
dependencies:
- name: postgresql
repository: https://kubernetes-charts.storage.googleapis.com/
version: 3.9.5
- name: mercure
repository: https://kubernetes-charts.storage.googleapis.com/
version: 1.0.0
digest: sha256:6466d376987cc08b70fe36945dd93474836aab05edc155955951bcb0774a0896
generated: 2019-03-26T18:14:32.4074+01:00

View File

@@ -0,0 +1,9 @@
dependencies:
- name: postgresql
version: ~3.9.0
repository: https://kubernetes-charts.storage.googleapis.com/
condition: postgresql.enabled
- name: mercure
version: ~1.0.0
repository: https://kubernetes-charts.storage.googleapis.com/
condition: mercure.enabled

View File

@@ -0,0 +1,5 @@
API deployed.
To get the ingress's IP:
kubectl --namespace {{ .Release.Namespace }} get ingress -o jsonpath="{.items[0].status.loadBalancer.ingress[0].ip}"

View File

@@ -0,0 +1,27 @@
{{/* vim: set filetype=mustache: */}}
{{/*
Expand the name of the chart.
*/}}
{{- define "name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
*/}}
{{- define "fullname" -}}
{{- $name := default .Chart.Name .Values.nameOverride -}}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- define "postgresql.fullname" -}}
{{- printf "%s-%s" .Release.Name "postgresql" | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
{{- end -}}

View File

@@ -0,0 +1,19 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ template "fullname" . }}
labels:
app.kubernetes.io/name: {{ include "name" . }}
app.kubernetes.io/part-of: {{ include "name" . }}
helm.sh/chart: {{ include "chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
data:
env: {{ .Values.php.env | quote }}
debug: {{ .Values.php.debug | quote }}
cors-allow-origin: {{ .Values.php.corsAllowOrigin | quote }}
varnish-url: {{ if .Values.varnish.enabled }}http://varnish{{ else }}{{ .Values.varnish.url | quote }}{{ end }}
trusted-hosts: {{ .Values.php.trustedHosts | quote }}
trusted-proxies: {{ join "," .Values.php.trustedProxies }}
mercure-publish-url: {{ .Values.mercure.publishUrl | quote }}
mercure-subscribe-url: {{ .Values.mercure.subscribeUrl | quote }}

View File

@@ -0,0 +1,35 @@
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: {{ template "fullname" . }}
labels:
app.kubernetes.io/name: {{ include "name" . }}-ingress
app.kubernetes.io/part-of: {{ include "name" . }}
helm.sh/chart: {{ include "chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if .Values.ingress.tls }}
tls:
{{- range .Values.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ .host | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
- path: /*
backend:
serviceName: {{ .serviceName }}
servicePort: {{ .servicePort | default 80 }}
{{- end }}

View File

@@ -0,0 +1,33 @@
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: {{ template "fullname" . }}-nginx
labels:
app.kubernetes.io/name: {{ include "name" . }}-nginx
app.kubernetes.io/part-of: {{ include "name" . }}
helm.sh/chart: {{ include "chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
spec:
replicas: {{ .Values.nginx.replicaCount }}
template:
metadata:
labels:
app.kubernetes.io/name: {{ include "name" . }}-nginx
app.kubernetes.io/part-of: {{ include "name" . }}
helm.sh/chart: {{ include "chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
spec:
containers:
- name: {{ .Chart.Name }}-nginx
image: "{{ .Values.nginx.repository }}:{{ .Values.nginx.tag }}"
imagePullPolicy: {{ .Values.nginx.pullPolicy }}
ports:
- containerPort: 80
resources:
{{ toYaml .Values.resources | indent 12 }}
{{- if .Values.nodeSelector }}
nodeSelector:
{{ toYaml .Values.nodeSelector | indent 8 }}
{{- end }}

View File

@@ -0,0 +1,19 @@
apiVersion: v1
kind: Service
metadata:
name: api
labels:
app.kubernetes.io/name: {{ include "name" . }}-nginx
app.kubernetes.io/part-of: {{ include "name" . }}
helm.sh/chart: {{ include "chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
spec:
type: NodePort
ports:
- port: 80
targetPort: 80
protocol: TCP
selector:
app.kubernetes.io/name: {{ include "name" . }}-nginx
app.kubernetes.io/instance: {{ .Release.Name }}

View File

@@ -0,0 +1,89 @@
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: {{ template "fullname" . }}-php
labels:
app.kubernetes.io/name: {{ include "name" . }}-php
app.kubernetes.io/part-of: {{ include "name" . }}
helm.sh/chart: {{ include "chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
spec:
replicas: {{ .Values.php.replicaCount }}
template:
metadata:
labels:
app.kubernetes.io/name: {{ include "name" . }}-php
app.kubernetes.io/part-of: {{ include "name" . }}
helm.sh/chart: {{ include "chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
spec:
containers:
- name: {{ .Chart.Name }}-php
image: "{{ .Values.php.repository }}:{{ .Values.php.tag }}"
imagePullPolicy: {{ .Values.php.pullPolicy }}
ports:
- containerPort: 9000
env:
- name: TRUSTED_HOSTS
valueFrom:
configMapKeyRef:
name: {{ template "fullname" . }}
key: trusted-hosts
- name: TRUSTED_PROXIES
valueFrom:
configMapKeyRef:
name: {{ template "fullname" . }}
key: trusted-proxies
- name: APP_ENV
valueFrom:
configMapKeyRef:
name: {{ template "fullname" . }}
key: env
- name: APP_DEBUG
valueFrom:
configMapKeyRef:
name: {{ template "fullname" . }}
key: debug
- name: CORS_ALLOW_ORIGIN
valueFrom:
configMapKeyRef:
name: {{ template "fullname" . }}
key: cors-allow-origin
- name: VARNISH_URL
valueFrom:
configMapKeyRef:
name: {{ template "fullname" . }}
key: varnish-url
- name: APP_SECRET
valueFrom:
secretKeyRef:
name: {{ template "fullname" . }}
key: secret
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: {{ template "fullname" . }}
key: database-url
- name: MERCURE_PUBLISH_URL
valueFrom:
configMapKeyRef:
name: {{ template "fullname" . }}
key: mercure-publish-url
- name: MERCURE_SUBSCRIBE_URL
valueFrom:
configMapKeyRef:
name: {{ template "fullname" . }}
key: mercure-subscribe-url
- name: MERCURE_JWT_SECRET
valueFrom:
secretKeyRef:
name: {{ template "fullname" . }}
key: mercure-jwt-secret
resources:
{{ toYaml .Values.resources | indent 12 }}
{{- if .Values.nodeSelector }}
nodeSelector:
{{ toYaml .Values.nodeSelector | indent 8 }}
{{- end }}

View File

@@ -0,0 +1,17 @@
apiVersion: v1
kind: Service
metadata:
name: php
labels:
app.kubernetes.io/name: {{ include "name" . }}-php
app.kubernetes.io/part-of: {{ include "name" . }}
helm.sh/chart: {{ include "chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
spec:
type: ClusterIP
ports:
- port: 9000
selector:
app.kubernetes.io/name: {{ include "name" . }}-php
app.kubernetes.io/instance: {{ .Release.Name }}

View File

@@ -0,0 +1,20 @@
{{- $postgresqlServiceName := include "postgresql.fullname" . -}}
apiVersion: v1
kind: Secret
metadata:
name: {{ template "fullname" . }}
labels:
app.kubernetes.io/name: {{ include "name" . }}
app.kubernetes.io/part-of: {{ include "name" . }}
helm.sh/chart: {{ include "chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
type: Opaque
data:
{{ if .Values.postgresql.enabled }}
database-url: {{ printf "pgsql://%s:%s@%s/%s?serverVersion=9.6" .Values.postgresql.postgresqlUsername .Values.postgresql.postgresqlPassword $postgresqlServiceName .Values.postgresql.postgresqlDatabase | b64enc | quote }}
{{ else }}
database-url: {{ .Values.postgresql.url | b64enc | quote }}
{{ end }}
secret: {{ .Values.php.secret | default (randAlphaNum 40) | b64enc | quote }}
mercure-jwt-secret: {{ .Values.php.mercure.jwtSecret | b64enc | quote }}

View File

@@ -0,0 +1,44 @@
{{- if .Values.varnish.enabled -}}
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: {{ template "fullname" . }}-varnish
labels:
app.kubernetes.io/name: {{ include "name" . }}-varnish
app.kubernetes.io/part-of: {{ include "name" . }}
helm.sh/chart: {{ include "chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
spec:
replicas: {{ .Values.varnish.replicaCount }}
template:
metadata:
labels:
app.kubernetes.io/name: {{ include "name" . }}-varnish
helm.sh/chart: {{ include "chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
spec:
containers:
- name: {{ .Chart.Name }}-varnish
image: "{{ .Values.varnish.repository }}:{{ .Values.varnish.tag }}"
imagePullPolicy: {{ .Values.varnish.pullPolicy }}
command: ["varnishd"]
args: ["-F", "-f", "/usr/local/etc/varnish/default.vcl", "-p", "http_resp_hdr_len=65536", "-p", "http_resp_size=98304"]
ports:
- containerPort: 80
livenessProbe:
httpGet:
path: /healthz
port: 80
readinessProbe:
httpGet:
path: /healthz
port: 80
resources:
{{ toYaml .Values.resources | indent 12 }}
{{- if .Values.nodeSelector }}
nodeSelector:
{{ toYaml .Values.nodeSelector | indent 8 }}
{{- end }}
{{- end -}}

View File

@@ -0,0 +1,21 @@
{{- if .Values.varnish.enabled -}}
apiVersion: v1
kind: Service
metadata:
name: varnish
labels:
app.kubernetes.io/name: {{ include "name" . }}-varnish
app.kubernetes.io/part-of: {{ include "name" . }}
helm.sh/chart: {{ include "chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
spec:
type: NodePort
ports:
- port: 80
targetPort: 80
protocol: TCP
selector:
app.kubernetes.io/name: {{ include "name" . }}-varnish
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end -}}

92
api/helm/api/values.yaml Normal file
View File

@@ -0,0 +1,92 @@
# Default values for api.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
php:
repository: quay.io/api-platform/php
tag: latest
pullPolicy: Always
replicaCount: 1
mercure:
jwtSecret: ""
env: prod
debug: '0'
secret: ""
corsAllowOrigin: "^https?://.*?\\.example\\.com$"
trustedHosts: "^.*\\.example\\.com$"
trustedProxies:
- 10.0.0.0/8
- 172.16.0.0/12
- 192.168.0.0/16
nginx:
repository: quay.io/api-platform/nginx
tag: latest
pullPolicy: Always
replicaCount: 1
varnish:
enabled: true
#url: https://example.com
repository: quay.io/api-platform/varnish
tag: latest
pullPolicy: Always
replicaCount: 1
postgresql:
enabled: true
imageTag: 10-alpine
# If bringing your own PostgreSQL, the full uri to use
#url: pgsql://api-platform:!ChangeMe!@example.com/api?serverVersion=10.1
postgresqlUsername: "example"
postgresqlPassword: "!ChangeMe!"
postgresqlDatabase: "api"
# Persistent Volume Storage configuration.
# ref: https://kubernetes.io/docs/user-guide/persistent-volumes
persistence:
enabled: false
pullPolicy: IfNotPresent
# image:
# repository: postgres
# tag: alpine
mercure:
enabled: true
publishUrl: http://mercure/hub
subscribeUrl: https://mercure.example.com/hub
allowAnonymous: "1"
corsAllowedOrigins: "^https?://.*?\\.example\\.com$"
acmeHosts: "" # TODO: Fix the Mercure chart
service:
type: NodePort
port: 80
ingress:
annotations:
# kubernetes.io/ingress.global-static-ip-name: chart-ip
# kubernetes.io/ingress.class: gce
# kubernetes.io/tls-acme: "true"
tls:
# Secrets must be manually created in the namespace, you can also use cert-manager.
# - hosts:
# - example.com
# - mercure.example.com
hosts:
api:
host: example.com
serviceName: varnish
mercure:
host: mercure.example.com
serviceName: mercure
resources: {}
# We usually recommend not to specify default resources and to leave this as a conscious
# choice for the user. This also increases chances charts run on environments with little
# resources, such as Minikube. If you do want to specify resources, uncomment the following
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
# limits:
# cpu: 100m
# memory: 128Mi
# requests:
# cpu: 100m
# memory: 128Mi

BIN
api/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

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

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

0
api/src/Controller/.gitignore vendored Normal file
View File

0
api/src/Entity/.gitignore vendored Normal file
View File

View File

@@ -0,0 +1,38 @@
<?php
namespace App\Entity;
use ApiPlatform\Core\Annotation\ApiResource;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* This is a dummy entity. Remove it!
*
* @ApiResource
* @ORM\Entity
*/
class Greeting
{
/**
* @var int The entity Id
*
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private $id;
/**
* @var string A nice person
*
* @ORM\Column
* @Assert\NotBlank
*/
public $name = '';
public function getId(): int
{
return $this->id;
}
}

53
api/src/Kernel.php Normal file
View File

@@ -0,0 +1,53 @@
<?php
namespace App;
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\Config\Resource\FileResource;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
use Symfony\Component\Routing\RouteCollectionBuilder;
class Kernel extends BaseKernel
{
use MicroKernelTrait;
private const CONFIG_EXTS = '.{php,xml,yaml,yml}';
public function registerBundles(): iterable
{
$contents = require $this->getProjectDir().'/config/bundles.php';
foreach ($contents as $class => $envs) {
if ($envs[$this->environment] ?? $envs['all'] ?? false) {
yield new $class();
}
}
}
public function getProjectDir(): string
{
return \dirname(__DIR__);
}
protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader): void
{
$container->addResource(new FileResource($this->getProjectDir().'/config/bundles.php'));
$container->setParameter('container.dumper.inline_class_loader', true);
$confDir = $this->getProjectDir().'/config';
$loader->load($confDir.'/{packages}/*'.self::CONFIG_EXTS, 'glob');
$loader->load($confDir.'/{packages}/'.$this->environment.'/**/*'.self::CONFIG_EXTS, 'glob');
$loader->load($confDir.'/{services}'.self::CONFIG_EXTS, 'glob');
$loader->load($confDir.'/{services}_'.$this->environment.self::CONFIG_EXTS, 'glob');
}
protected function configureRoutes(RouteCollectionBuilder $routes): void
{
$confDir = $this->getProjectDir().'/config';
$routes->import($confDir.'/{routes}/'.$this->environment.'/**/*'.self::CONFIG_EXTS, '/', 'glob');
$routes->import($confDir.'/{routes}/*'.self::CONFIG_EXTS, '/', 'glob');
$routes->import($confDir.'/{routes}'.self::CONFIG_EXTS, '/', 'glob');
}
}

0
api/src/Repository/.gitignore vendored Normal file
View File

425
api/symfony.lock Normal file
View File

@@ -0,0 +1,425 @@
{
"api-platform/api-pack": {
"version": "v1.2.0"
},
"api-platform/core": {
"version": "2.1",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "master",
"version": "2.1",
"ref": "18727d8f229306860b46955f438e1897421da689"
},
"files": [
"config/packages/api_platform.yaml",
"config/routes/api_platform.yaml",
"src/Entity/.gitignore"
]
},
"api-platform/schema-generator": {
"version": "v2.1.0"
},
"composer/semver": {
"version": "1.5.0"
},
"composer/xdebug-handler": {
"version": "1.3.3"
},
"doctrine/annotations": {
"version": "1.0",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "master",
"version": "1.0",
"ref": "cb4152ebcadbe620ea2261da1a1c5a9b8cea7672"
},
"files": [
"config/routes/annotations.yaml"
]
},
"doctrine/cache": {
"version": "v1.8.0"
},
"doctrine/collections": {
"version": "v1.6.2"
},
"doctrine/common": {
"version": "v2.10.0"
},
"doctrine/dbal": {
"version": "v2.9.2"
},
"doctrine/doctrine-bundle": {
"version": "1.6",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "master",
"version": "1.6",
"ref": "02bc9e7994b70f4fda004131a0c78b7b1bf09789"
},
"files": [
"config/packages/doctrine.yaml",
"config/packages/prod/doctrine.yaml",
"src/Entity/.gitignore",
"src/Repository/.gitignore"
]
},
"doctrine/doctrine-cache-bundle": {
"version": "1.3.5"
},
"doctrine/event-manager": {
"version": "v1.0.0"
},
"doctrine/inflector": {
"version": "v1.3.0"
},
"doctrine/instantiator": {
"version": "1.2.0"
},
"doctrine/lexer": {
"version": "1.0.2"
},
"doctrine/orm": {
"version": "v2.6.3"
},
"doctrine/persistence": {
"version": "1.1.1"
},
"doctrine/reflection": {
"version": "v1.0.0"
},
"easyrdf/easyrdf": {
"version": "0.9.1"
},
"fig/link-util": {
"version": "1.0.0"
},
"friendsofphp/php-cs-fixer": {
"version": "2.2",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "master",
"version": "2.2",
"ref": "cc05ab6abf6894bddb9bbd6a252459010ebe040b"
},
"files": [
".php_cs.dist"
]
},
"guzzlehttp/guzzle": {
"version": "6.3.3"
},
"guzzlehttp/promises": {
"version": "v1.3.1"
},
"guzzlehttp/psr7": {
"version": "1.5.2"
},
"jdorn/sql-formatter": {
"version": "v1.2.17"
},
"league/html-to-markdown": {
"version": "4.8.1"
},
"nelmio/cors-bundle": {
"version": "1.5",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "master",
"version": "1.5",
"ref": "f0436fc35fca88eada758311f8de43bfb61f1980"
},
"files": [
"config/packages/nelmio_cors.yaml"
]
},
"nikic/php-parser": {
"version": "v4.2.2"
},
"php-cs-fixer/diff": {
"version": "v1.3.0"
},
"phpdocumentor/reflection-common": {
"version": "1.0.1"
},
"phpdocumentor/reflection-docblock": {
"version": "4.3.1"
},
"phpdocumentor/type-resolver": {
"version": "0.4.0"
},
"psr/cache": {
"version": "1.0.1"
},
"psr/container": {
"version": "1.0.0"
},
"psr/http-message": {
"version": "1.0.1"
},
"psr/link": {
"version": "1.0.0"
},
"psr/log": {
"version": "1.1.0"
},
"ralouphie/getallheaders": {
"version": "2.0.5"
},
"symfony/asset": {
"version": "v4.3.1"
},
"symfony/cache": {
"version": "v4.3.1"
},
"symfony/config": {
"version": "v4.3.1"
},
"symfony/console": {
"version": "3.3",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "master",
"version": "3.3",
"ref": "482d233eb8de91ebd042992077bbd5838858890c"
},
"files": [
"bin/console",
"config/bootstrap.php"
]
},
"symfony/contracts": {
"version": "v1.1.3"
},
"symfony/debug": {
"version": "v4.3.1"
},
"symfony/dependency-injection": {
"version": "v4.3.1"
},
"symfony/doctrine-bridge": {
"version": "v4.3.1"
},
"symfony/dotenv": {
"version": "v4.3.1"
},
"symfony/event-dispatcher": {
"version": "v4.3.1"
},
"symfony/expression-language": {
"version": "v4.3.1"
},
"symfony/filesystem": {
"version": "v4.3.1"
},
"symfony/finder": {
"version": "v4.3.1"
},
"symfony/flex": {
"version": "1.0",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "master",
"version": "1.0",
"ref": "dc3fc2e0334a4137c47cfd5a3ececc601fa61a0b"
},
"files": [
".env"
]
},
"symfony/framework-bundle": {
"version": "4.2",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "master",
"version": "4.2",
"ref": "0dfae0b1cd8349ae47659b35602f772b4a7e2280"
},
"files": [
"config/bootstrap.php",
"config/packages/cache.yaml",
"config/packages/framework.yaml",
"config/packages/test/framework.yaml",
"config/services.yaml",
"public/index.php",
"src/Controller/.gitignore",
"src/Kernel.php"
]
},
"symfony/http-client": {
"version": "v4.3.1"
},
"symfony/http-foundation": {
"version": "v4.3.1"
},
"symfony/http-kernel": {
"version": "v4.3.1"
},
"symfony/inflector": {
"version": "v4.3.1"
},
"symfony/maker-bundle": {
"version": "1.0",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "master",
"version": "1.0",
"ref": "fadbfe33303a76e25cb63401050439aa9b1a9c7f"
}
},
"symfony/mercure": {
"version": "v0.2.0"
},
"symfony/mercure-bundle": {
"version": "0.1",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "master",
"version": "0.1",
"ref": "c78ab1f56e700004fc5cd675c26b3c1c26be281d"
},
"files": [
"config/packages/mercure.yaml"
]
},
"symfony/mime": {
"version": "v4.3.1"
},
"symfony/options-resolver": {
"version": "v4.3.1"
},
"symfony/polyfill-intl-idn": {
"version": "v1.11.0"
},
"symfony/polyfill-mbstring": {
"version": "v1.11.0"
},
"symfony/polyfill-php72": {
"version": "v1.11.0"
},
"symfony/polyfill-php73": {
"version": "v1.11.0"
},
"symfony/process": {
"version": "v4.3.1"
},
"symfony/profiler-pack": {
"version": "v1.0.4"
},
"symfony/property-access": {
"version": "v4.3.1"
},
"symfony/property-info": {
"version": "v4.3.1"
},
"symfony/routing": {
"version": "4.2",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "master",
"version": "4.2",
"ref": "5374e24d508ba8fd6ba9eb15170255fdb778316a"
},
"files": [
"config/packages/dev/routing.yaml",
"config/packages/routing.yaml",
"config/packages/test/routing.yaml",
"config/routes.yaml"
]
},
"symfony/security-bundle": {
"version": "3.3",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "master",
"version": "3.3",
"ref": "9a2034eca6d83d9cda632014e06995b8d9d9fd09"
},
"files": [
"config/packages/security.yaml"
]
},
"symfony/security-core": {
"version": "v4.3.1"
},
"symfony/security-csrf": {
"version": "v4.3.1"
},
"symfony/security-guard": {
"version": "v4.3.1"
},
"symfony/security-http": {
"version": "v4.3.1"
},
"symfony/serializer": {
"version": "v4.3.1"
},
"symfony/stopwatch": {
"version": "v4.3.1"
},
"symfony/twig-bridge": {
"version": "v4.3.1"
},
"symfony/twig-bundle": {
"version": "3.3",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "master",
"version": "3.3",
"ref": "369b5b29dc52b2c190002825ae7ec24ab6f962dd"
},
"files": [
"config/packages/twig.yaml",
"config/routes/dev/twig.yaml",
"templates/base.html.twig"
]
},
"symfony/validator": {
"version": "4.3",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "master",
"version": "4.3",
"ref": "d902da3e4952f18d3bf05aab29512eb61cabd869"
},
"files": [
"config/packages/test/validator.yaml",
"config/packages/validator.yaml"
]
},
"symfony/var-dumper": {
"version": "v4.3.1"
},
"symfony/var-exporter": {
"version": "v4.3.1"
},
"symfony/web-link": {
"version": "v4.3.1"
},
"symfony/web-profiler-bundle": {
"version": "3.3",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "master",
"version": "3.3",
"ref": "6bdfa1a95f6b2e677ab985cd1af2eae35d62e0f6"
},
"files": [
"config/packages/dev/web_profiler.yaml",
"config/packages/test/web_profiler.yaml",
"config/routes/dev/web_profiler.yaml"
]
},
"symfony/yaml": {
"version": "v4.3.1"
},
"twig/twig": {
"version": "v2.11.3"
},
"webmozart/assert": {
"version": "1.4.0"
},
"willdurand/negotiation": {
"version": "v2.3.1"
}
}

View File

@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{% block title %}Welcome!{% endblock %}</title>
{% block stylesheets %}{% endblock %}
</head>
<body>
{% block body %}{% endblock %}
{% block javascripts %}{% endblock %}
</body>
</html>

14
client/.dockerignore Normal file
View File

@@ -0,0 +1,14 @@
**/*.log
**/*.md
**/._*
**/.dockerignore
**/.DS_Store
**/.git/
**/.gitattributes
**/.gitignore
**/.gitmodules
**/Dockerfile*
**/Thumbs.db
.env*
build/
node_modules/

3
client/.env Normal file
View File

@@ -0,0 +1,3 @@
REACT_APP_API_ENTRYPOINT=https://localhost:8443
API_PLATFORM_CLIENT_GENERATOR_ENTRYPOINT=http://api
API_PLATFORM_CLIENT_GENERATOR_OUTPUT=src

20
client/.gitignore vendored Normal file
View File

@@ -0,0 +1,20 @@
# See https://help.github.com/ignore-files/ for more about ignoring files.
# dependencies
/node_modules
# testing
/coverage
# production
/build
# misc
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

15
client/Dockerfile Normal file
View File

@@ -0,0 +1,15 @@
FROM node:11.5-alpine
RUN mkdir -p /usr/src/client
WORKDIR /usr/src/client
RUN yarn global add @api-platform/client-generator
# Prevent the reinstallation of node modules at every changes in the source code
COPY package.json yarn.lock ./
RUN yarn install
COPY . ./
CMD yarn start

36
client/package.json Normal file
View File

@@ -0,0 +1,36 @@
{
"name": "client",
"version": "0.1.0",
"private": true,
"dependencies": {
"bootstrap": "^4.1.3",
"connected-react-router": "^5.0.1",
"font-awesome": "^4.7.0",
"jest-junit": "^5.2.0",
"lodash.get": "^4.4.2",
"lodash.has": "^4.5.2",
"lodash.mapvalues": "^4.6.0",
"prettier": "^1.14.3",
"prop-types": "^15.6.2",
"react": "^16.6.0",
"react-dom": "^16.6.0",
"react-redux": "^5.1.0",
"react-router-dom": "^4.3.1",
"react-scripts": "^2.1.1",
"redux": "^4.0.1",
"redux-form": "^7.4.2",
"redux-thunk": "^2.3.0"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"browserslist": [
">0.2%",
"not dead",
"not ie <= 11",
"not op_mini all"
]
}

BIN
client/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

40
client/public/index.html Normal file
View File

@@ -0,0 +1,40 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<!--
manifest.json provides metadata used when your web app is added to the
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>Welcome to API Platform</title>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

View File

@@ -0,0 +1,15 @@
{
"short_name": "API Platform",
"name": "Client",
"icons": [
{
"src": "favicon.ico",
"sizes": "192x192",
"type": "image/png"
}
],
"start_url": "./index.html",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

1801
client/src/Welcome.js Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
export const ENTRYPOINT = process.env.REACT_APP_API_ENTRYPOINT;

46
client/src/index.js Normal file
View File

@@ -0,0 +1,46 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore, combineReducers, applyMiddleware } from 'redux';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';
import { reducer as form } from 'redux-form';
import { Route, Switch } from 'react-router-dom';
import createBrowserHistory from 'history/createBrowserHistory';
import {
ConnectedRouter,
connectRouter,
routerMiddleware
} from 'connected-react-router';
import 'bootstrap/dist/css/bootstrap.css';
import 'font-awesome/css/font-awesome.css';
import * as serviceWorker from './serviceWorker';
// Import your reducers and routes here
import Welcome from './Welcome';
const history = createBrowserHistory();
const store = createStore(
combineReducers({
router: connectRouter(history),
form,
/* Add your reducers here */
}),
applyMiddleware(routerMiddleware(history), thunk)
);
ReactDOM.render(
<Provider store={store}>
<ConnectedRouter history={history}>
<Switch>
<Route path="/" component={Welcome} strict={true} exact={true}/>
{/* Add your routes here */}
<Route render={() => <h1>Not Found</h1>} />
</Switch>
</ConnectedRouter>
</Provider>,
document.getElementById('root')
);
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: http://bit.ly/CRA-PWA
serviceWorker.unregister();

135
client/src/serviceWorker.js Normal file
View File

@@ -0,0 +1,135 @@
// This optional code is used to register a service worker.
// register() is not called by default.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on subsequent visits to a page, after all the
// existing tabs open on the page have been closed, since previously cached
// resources are updated in the background.
// To learn more about the benefits of this model and instructions on how to
// opt-in, read http://bit.ly/CRA-PWA
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.1/8 is considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
export function register(config) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
return;
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config);
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit http://bit.ly/CRA-PWA'
);
});
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
});
}
}
function registerValidSW(swUrl, config) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
return;
}
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the updated precached content has been fetched,
// but the previous service worker will still serve the older
// content until all client tabs are closed.
console.log(
'New content is available and will be used when all ' +
'tabs for this page are closed. See http://bit.ly/CRA-PWA.'
);
// Execute callback
if (config && config.onUpdate) {
config.onUpdate(registration);
}
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.');
// Execute callback
if (config && config.onSuccess) {
config.onSuccess(registration);
}
}
}
};
};
})
.catch(error => {
console.error('Error during service worker registration:', error);
});
}
function checkValidServiceWorker(swUrl, config) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl)
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type');
if (
response.status === 404 ||
(contentType != null && contentType.indexOf('javascript') === -1)
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload();
});
});
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl, config);
}
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.'
);
});
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(registration => {
registration.unregister();
});
}
}

312
client/src/welcome.css Normal file
View File

@@ -0,0 +1,312 @@
@import url('https://fonts.googleapis.com/css?family=Open+Sans:400,700|Roboto+Slab:300,700');
body {
margin: 0;
}
/***** GLOBAL *****/
.welcome {
height: 100vh;
width: 100vw;
text-align: center;
color: #1d1e1c;
font-family: 'Open Sans', sans-serif;
font-size: 14px;
overflow: auto;
background-color: #ececec;
}
.welcome a {
text-decoration: none;
color: #38a9b4;
font-weight: bold;
}
.welcome h1 {
font-family: 'Roboto Slab', serif;
font-weight: 300;
font-size: 36px;
margin: 0 0 10px;
line-height: 30px;
}
.welcome h1 strong {
font-weight: 700;
color: #38a9b4;
}
.welcome h2 {
text-transform: uppercase;
font-size: 18px;
font-weight: bold;
margin: 25px 0 5px;
}
.welcome h3 {
text-transform: uppercase;
font-weight: 500;
color: #38a9b4;
font-size: 16px;
margin: 0 0 5px;
display: block;
}
/***** TOP *****/
.welcome__top {
background-color: #67cece;
padding-bottom: 40px;
}
.welcome__flag {
transform: rotate(30deg);
position: fixed;
right: -190px;
top: 65px;
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.2);
z-index: 5;
}
/***** MAIN *****/
.welcome__main {
box-shadow: 0 6px 10px 0 rgba(0, 0, 0, 0.14),
0 1px 18px 0 rgba(0, 0, 0, 0.12), 0 3px 5px -1px rgba(0, 0, 0, 0.3);
width: 80%;
max-width: 1100px;
margin-left: auto;
margin-right: auto;
transform: translateY(-50px);
background-color: white;
display: flex;
}
.main__aside {
background-color: #afe5e5;
width: 30%;
position: relative;
overflow: hidden;
}
.aside__circle,
.main__aside svg {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
.aside__circle {
background-color: white;
border-radius: 50%;
width: 90%;
height: 0;
padding-bottom: 90%;
}
.aside__circle:after {
content: '';
width: 4px;
left: calc(50% - 5px);
top: -50%;
position: absolute;
height: 100%;
background-color: #1d1e1c;
}
.main__aside svg {
width: 100%;
}
.main__content {
padding: 30px;
text-align: left;
flex: auto;
}
.other__bloc {
display: inline-flex;
align-items: center;
border: 4px solid #afe5e5;
padding: 10px 20px;
margin: 10px 0;
height: 170px;
box-sizing: border-box;
text-align:left;
}
.other__bloc:not(:last-of-type) {
margin-right: 10px;
}
.other__bloc h3:not(:first-child) {
margin-top: 15px;
padding-top: 5px;
}
.other__circle {
width: 110px;
height: 110px;
background-color: #afe5e5;
border-radius: 50%;
margin-right:20px;
}
.other__circle svg{
width: 110px;
}
.buttons__group {
display: inline-flex;
vertical-align: center;
}
.buttons__group .buttons__or {
width: 4px;
position: relative;
text-align:center;
}
.buttons__group .buttons__or:before {
content: 'or';
font-size: 12px;
color: #aaa;
line-height: 18px;
position: absolute;
border-radius: 50%;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: white;
width: 18px;
height: 18px;
}
.buttons__group .other__button:first-child {
border-radius: 5px 0 0 5px;
padding-right: 15px;
}
.buttons__group .other__button:last-child {
border-radius: 0 5px 5px 0;
padding-left: 15px;
}
a.other__button {
background-color: #e0e1e2;
font-size: 11px;
color: #686e63;
cursor: pointer;
padding: 5px 10px;
display: inline-block;
transition: all ease 0.2s;
text-transform: uppercase;
}
.other__button:hover {
background-color: #afe5e5;
color: #339ba5;
}
.main__button {
display: inline-block;
padding: 10px 50px 10px 10px;
border: 3px solid #339ba5;
font-size: 22px;
color: #339ba5;
text-transform: uppercase;
margin: 15px 0;
overflow: hidden;
transition: all ease 0.3s;
cursor: pointer;
position: relative;
}
.main__button svg {
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
transition: transform ease 0.2s;
}
.main__button:hover {
background-color: #afe5e5;
}
.main__button:hover svg {
transform: translateY(-50%) rotate(35deg);
}
/***** HELP *****/
.welcome__help {
background-color: white;
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.2);
padding: 10px;
position: fixed;
right: -5px;
top: 50%;
transform: translateY(-50%);
border-radius: 5px;
text-align: center;
}
.welcome__help h2 {
color: #aaa;
font-size: 12px;
margin: 10px 0;
}
.help__circle {
width: 36px;
height: 36px;
border-radius: 50%;
border: 2px solid #ccc;
display: block;
margin: 10px auto;
transition: all ease 0.2s;
position:relative;
}
.help__circle svg {
position:absolute;
left: 50%;
top: 50%;
transform:translate(-50%, -50%);
}
.help__circle:hover {
border-color: #67cece;
background-color: #afe5e5;
}
/***** MEDIAS *****/
@media (max-width: 1200px) {
.main__aside,
.welcome__help {
display: none;
}
.main__content {
width: 100%;
text-align: center;
padding: 20px;
}
}
@media (max-width: 600px) {
.welcome__main {
width: calc(100% - 40px);
}
.welcome h1 {
display: none;
}
.welcome__flag,
.main__other {
display: none;
}
.main__content {
padding: 10px;
}
}

10446
client/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

128
docker-compose.yml Normal file
View File

@@ -0,0 +1,128 @@
version: '3.4'
x-cache:
&cache
cache_from:
- ${CONTAINER_REGISTRY_BASE}/php
- ${CONTAINER_REGISTRY_BASE}/nginx
- ${CONTAINER_REGISTRY_BASE}/varnish
services:
php:
image: ${CONTAINER_REGISTRY_BASE}/php
build:
context: ./api
target: api_platform_php
<<: *cache
depends_on:
- db
# Comment out these volumes in production
volumes:
- ./api:/srv/api:rw,cached
# If you develop on Linux, uncomment the following line to use a bind-mounted host directory instead
# - ./api/var:/srv/api/var:rw
api:
image: ${CONTAINER_REGISTRY_BASE}/nginx
build:
context: ./api
target: api_platform_nginx
<<: *cache
depends_on:
- php
# Comment out this volume in production
volumes:
- ./api/public:/srv/api/public:ro
ports:
- "8080:80"
cache-proxy:
image: ${CONTAINER_REGISTRY_BASE}/varnish
build:
context: ./api
target: api_platform_varnish
<<: *cache
depends_on:
- api
volumes:
- ./api/docker/varnish/conf:/usr/local/etc/varnish:ro
tmpfs:
- /usr/local/var/varnish:exec
ports:
- "8081:80"
db:
# In production, you may want to use a managed database service
image: postgres:10-alpine
environment:
- POSTGRES_DB=api
- POSTGRES_USER=api-platform
# You should definitely change the password in production
- POSTGRES_PASSWORD=!ChangeMe!
volumes:
- db-data:/var/lib/postgresql/data:rw
# You may use a bind-mounted host directory instead, so that it is harder to accidentally remove the volume and lose all your data!
# - ./docker/db/data:/var/lib/postgresql/data:rw
ports:
- "5432:5432"
mercure:
# In production, you may want to use the managed version of Mercure, https://mercure.rocks
image: dunglas/mercure
environment:
# You should definitely change all these values in production
- JWT_KEY=!UnsecureChangeMe!
- ALLOW_ANONYMOUS=1
- CORS_ALLOWED_ORIGINS=*
- PUBLISH_ALLOWED_ORIGINS=http://localhost:1337,https://localhost:1338
- DEMO=1
ports:
- "1337:80"
client:
# Use a static website hosting service in production
# See https://facebook.github.io/create-react-app/docs/deployment
image: ${CONTAINER_REGISTRY_BASE}/client
build:
context: ./client
cache_from:
- ${CONTAINER_REGISTRY_BASE}/client
env_file:
- ./client/.env
volumes:
- ./client:/usr/src/client:rw,cached
- /usr/src/client/node_modules
ports:
- "80:3000"
admin:
# Use a static website hosting service in production
# See https://facebook.github.io/create-react-app/docs/deployment
image: ${CONTAINER_REGISTRY_BASE}/admin
build:
context: ./admin
cache_from:
- ${CONTAINER_REGISTRY_BASE}/admin
volumes:
- ./admin:/usr/src/admin:rw,cached
- /usr/src/admin/node_modules
ports:
- "81:3000"
h2-proxy:
# Don't use this proxy in prod
build:
context: ./h2-proxy
depends_on:
- client
- admin
- api
- cache-proxy
ports:
- "443:443"
- "444:444"
- "8443:8443"
- "8444:8444"
volumes:
db-data: {}

17
h2-proxy/Dockerfile Normal file
View File

@@ -0,0 +1,17 @@
FROM alpine:latest
RUN apk add --no-cache openssl
# Use this self-generated certificate only in dev, IT IS NOT SECURE!
RUN openssl genrsa -des3 -passout pass:NotSecure -out cert.pass.key 2048
RUN openssl rsa -passin pass:NotSecure -in cert.pass.key -out cert.key
RUN rm cert.pass.key
RUN openssl req -new -passout pass:NotSecure -key cert.key -out cert.csr \
-subj '/C=SS/ST=SS/L=Gotham City/O=API Platform Dev/CN=localhost'
RUN openssl x509 -req -sha256 -days 365 -in cert.csr -signkey cert.key -out cert.crt
FROM nginx:1.15-alpine
RUN mkdir -p /etc/nginx/ssl/
COPY --from=0 cert.key cert.crt /etc/nginx/ssl/
COPY conf.d /etc/nginx/conf.d/

View File

@@ -0,0 +1,85 @@
# client
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
ssl_certificate /etc/nginx/ssl/cert.crt;
ssl_certificate_key /etc/nginx/ssl/cert.key;
location / {
proxy_pass http://client:3000;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
# admin
server {
listen 444 ssl http2;
listen [::]:444 ssl http2;
ssl_certificate /etc/nginx/ssl/cert.crt;
ssl_certificate_key /etc/nginx/ssl/cert.key;
location / {
proxy_pass http://admin:3000;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
# api
server {
listen 8443 ssl http2;
listen [::]:8443 ssl http2;
ssl_certificate /etc/nginx/ssl/cert.crt;
ssl_certificate_key /etc/nginx/ssl/cert.key;
location / {
proxy_pass http://api;
proxy_http_version 1.1;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Port 8443;
}
}
# cache-proxy
server {
listen 8444 ssl http2;
listen [::]:8444 ssl http2;
ssl_certificate /etc/nginx/ssl/cert.crt;
ssl_certificate_key /etc/nginx/ssl/cert.key;
location / {
proxy_pass http://cache-proxy;
proxy_http_version 1.1;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Port 8444;
}
}
# mercure
server {
listen 1338 ssl http2;
listen [::]:1338 ssl http2;
ssl_certificate /etc/nginx/ssl/cert.crt;
ssl_certificate_key /etc/nginx/ssl/cert.key;
location / {
proxy_pass http://mercure;
proxy_read_timeout 24h;
proxy_http_version 1.1;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Port 1338;
proxy_set_header Connection "";
}
}