commit
923854fa31
|
@ -10,3 +10,4 @@ node_modules
|
|||
###> symfony/web-server-bundle ###
|
||||
/.web-server-pid
|
||||
###< symfony/web-server-bundle ###
|
||||
/logs/
|
|
@ -0,0 +1,19 @@
|
|||
sudo: required
|
||||
|
||||
env:
|
||||
DOCKER_COMPOSE_VERSION: 1.16.1
|
||||
|
||||
services:
|
||||
- docker
|
||||
|
||||
before_install:
|
||||
- curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > docker-compose
|
||||
- chmod +x docker-compose
|
||||
- sudo mv docker-compose /usr/local/bin
|
||||
- docker-compose -v
|
||||
- docker -v
|
||||
- mkdir symfony
|
||||
|
||||
script:
|
||||
- docker-compose -f docker-compose.travis.yml up -d
|
||||
- docker-compose -f docker-compose.travis.yml ps
|
|
@ -6,8 +6,10 @@
|
|||
"ext-iconv": "*",
|
||||
"api-platform/api-pack": "^1.1",
|
||||
"doctrine/doctrine-fixtures-bundle": "^3.0",
|
||||
"sensio/framework-extra-bundle": "^5.1",
|
||||
"symfony/console": "^4.0",
|
||||
"symfony/flex": "^1.0",
|
||||
"symfony/form": "^4.0",
|
||||
"symfony/framework-bundle": "^4.0",
|
||||
"symfony/lts": "^4@dev",
|
||||
"symfony/maker-bundle": "^1.4",
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "6fb85b3fa57133c433a33fb80ed9c9c6",
|
||||
"content-hash": "390029222132b498c5dda4068ec3b022",
|
||||
"packages": [
|
||||
{
|
||||
"name": "api-platform/api-pack",
|
||||
|
@ -1821,6 +1821,75 @@
|
|||
],
|
||||
"time": "2017-10-23T01:57:42+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sensio/framework-extra-bundle",
|
||||
"version": "v5.1.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sensiolabs/SensioFrameworkExtraBundle.git",
|
||||
"reference": "bf4940572e43af679aaa13be98f3446a1c237bd8"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sensiolabs/SensioFrameworkExtraBundle/zipball/bf4940572e43af679aaa13be98f3446a1c237bd8",
|
||||
"reference": "bf4940572e43af679aaa13be98f3446a1c237bd8",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"doctrine/common": "^2.2",
|
||||
"symfony/config": "^3.3|^4.0",
|
||||
"symfony/dependency-injection": "^3.3|^4.0",
|
||||
"symfony/framework-bundle": "^3.3|^4.0",
|
||||
"symfony/http-kernel": "^3.3|^4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/doctrine-bundle": "^1.6",
|
||||
"doctrine/orm": "^2.5",
|
||||
"symfony/browser-kit": "^3.3|^4.0",
|
||||
"symfony/dom-crawler": "^3.3|^4.0",
|
||||
"symfony/expression-language": "^3.3|^4.0",
|
||||
"symfony/finder": "^3.3|^4.0",
|
||||
"symfony/phpunit-bridge": "^3.3|^4.0",
|
||||
"symfony/psr-http-message-bridge": "^0.3",
|
||||
"symfony/security-bundle": "^3.3|^4.0",
|
||||
"symfony/twig-bundle": "^3.3|^4.0",
|
||||
"symfony/yaml": "^3.3|^4.0",
|
||||
"twig/twig": "~1.12|~2.0",
|
||||
"zendframework/zend-diactoros": "^1.3"
|
||||
},
|
||||
"suggest": {
|
||||
"symfony/expression-language": "",
|
||||
"symfony/psr-http-message-bridge": "To use the PSR-7 converters",
|
||||
"symfony/security-bundle": ""
|
||||
},
|
||||
"type": "symfony-bundle",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "5.1.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Sensio\\Bundle\\FrameworkExtraBundle\\": ""
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Fabien Potencier",
|
||||
"email": "fabien@symfony.com"
|
||||
}
|
||||
],
|
||||
"description": "This bundle provides a way to configure your controllers with annotations",
|
||||
"keywords": [
|
||||
"annotations",
|
||||
"controllers"
|
||||
],
|
||||
"time": "2018-02-14T08:40:54+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/asset",
|
||||
"version": "v4.1.0",
|
||||
|
@ -2542,6 +2611,87 @@
|
|||
],
|
||||
"time": "2018-05-02T19:08:56+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/form",
|
||||
"version": "v4.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/form.git",
|
||||
"reference": "95f8237303e1f7101fee0e72d6ba630a3e4c5178"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/form/zipball/95f8237303e1f7101fee0e72d6ba630a3e4c5178",
|
||||
"reference": "95f8237303e1f7101fee0e72d6ba630a3e4c5178",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.1.3",
|
||||
"symfony/event-dispatcher": "~3.4|~4.0",
|
||||
"symfony/intl": "~3.4|~4.0",
|
||||
"symfony/options-resolver": "~3.4|~4.0",
|
||||
"symfony/polyfill-ctype": "~1.8",
|
||||
"symfony/polyfill-mbstring": "~1.0",
|
||||
"symfony/property-access": "~3.4|~4.0"
|
||||
},
|
||||
"conflict": {
|
||||
"phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0",
|
||||
"symfony/dependency-injection": "<3.4",
|
||||
"symfony/doctrine-bridge": "<3.4",
|
||||
"symfony/framework-bundle": "<3.4",
|
||||
"symfony/http-kernel": "<3.4",
|
||||
"symfony/twig-bridge": "<3.4.5|<4.0.5,>=4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/collections": "~1.0",
|
||||
"symfony/config": "~3.4|~4.0",
|
||||
"symfony/console": "~3.4|~4.0",
|
||||
"symfony/dependency-injection": "~3.4|~4.0",
|
||||
"symfony/http-foundation": "~3.4|~4.0",
|
||||
"symfony/http-kernel": "~3.4|~4.0",
|
||||
"symfony/security-csrf": "~3.4|~4.0",
|
||||
"symfony/translation": "~3.4|~4.0",
|
||||
"symfony/validator": "~3.4|~4.0",
|
||||
"symfony/var-dumper": "~3.4|~4.0"
|
||||
},
|
||||
"suggest": {
|
||||
"symfony/framework-bundle": "For templating with PHP.",
|
||||
"symfony/security-csrf": "For protecting forms against CSRF attacks.",
|
||||
"symfony/twig-bridge": "For templating with Twig.",
|
||||
"symfony/validator": "For form validation."
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "4.1-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Component\\Form\\": ""
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Fabien Potencier",
|
||||
"email": "fabien@symfony.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony Form Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2018-05-30T07:26:09+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/framework-bundle",
|
||||
"version": "v4.1.0",
|
||||
|
@ -2856,6 +3006,81 @@
|
|||
],
|
||||
"time": "2018-05-01T23:02:13+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/intl",
|
||||
"version": "v4.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/intl.git",
|
||||
"reference": "e2a48225f7d525b23a6e34caaa7320205abcf179"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/intl/zipball/e2a48225f7d525b23a6e34caaa7320205abcf179",
|
||||
"reference": "e2a48225f7d525b23a6e34caaa7320205abcf179",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.1.3",
|
||||
"symfony/polyfill-intl-icu": "~1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/filesystem": "~3.4|~4.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-intl": "to use the component with locales other than \"en\""
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "4.1-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Component\\Intl\\": ""
|
||||
},
|
||||
"classmap": [
|
||||
"Resources/stubs"
|
||||
],
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Bernhard Schussek",
|
||||
"email": "bschussek@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Eriksen Costa",
|
||||
"email": "eriksen.costa@infranology.com.br"
|
||||
},
|
||||
{
|
||||
"name": "Igor Wiedler",
|
||||
"email": "igor@wiedler.ch"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "A PHP replacement layer for the C intl extension that includes additional data from the ICU library.",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"i18n",
|
||||
"icu",
|
||||
"internationalization",
|
||||
"intl",
|
||||
"l10n",
|
||||
"localization"
|
||||
],
|
||||
"time": "2018-05-30T07:26:09+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/lts",
|
||||
"version": "dev-master",
|
||||
|
@ -3014,6 +3239,60 @@
|
|||
],
|
||||
"time": "2018-05-17T19:26:29+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/options-resolver",
|
||||
"version": "v4.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/options-resolver.git",
|
||||
"reference": "9b9ab6043c57c8c5571bc846e6ebfd27dff3b589"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/options-resolver/zipball/9b9ab6043c57c8c5571bc846e6ebfd27dff3b589",
|
||||
"reference": "9b9ab6043c57c8c5571bc846e6ebfd27dff3b589",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.1.3"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "4.1-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Component\\OptionsResolver\\": ""
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Fabien Potencier",
|
||||
"email": "fabien@symfony.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony OptionsResolver Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"config",
|
||||
"configuration",
|
||||
"options"
|
||||
],
|
||||
"time": "2018-05-30T07:26:09+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/orm-pack",
|
||||
"version": "v1.0.5",
|
||||
|
@ -3097,6 +3376,64 @@
|
|||
],
|
||||
"time": "2018-04-30T19:57:29+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-intl-icu",
|
||||
"version": "v1.8.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-intl-icu.git",
|
||||
"reference": "80ee17ae83c10cd513e5144f91a73607a21edb4e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/80ee17ae83c10cd513e5144f91a73607a21edb4e",
|
||||
"reference": "80ee17ae83c10cd513e5144f91a73607a21edb4e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.3",
|
||||
"symfony/intl": "~2.3|~3.0|~4.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-intl": "For best performance"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.8-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nicolas Grekas",
|
||||
"email": "p@tchwork.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony polyfill for intl's ICU-related data and classes",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"compatibility",
|
||||
"icu",
|
||||
"intl",
|
||||
"polyfill",
|
||||
"portable",
|
||||
"shim"
|
||||
],
|
||||
"time": "2018-04-25T14:53:50+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-mbstring",
|
||||
"version": "v1.8.0",
|
||||
|
|
|
@ -13,4 +13,5 @@ return [
|
|||
ApiPlatform\Core\Bridge\Symfony\Bundle\ApiPlatformBundle::class => ['all' => true],
|
||||
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
|
||||
Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle::class => ['dev' => true, 'test' => true],
|
||||
Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class => ['all' => true],
|
||||
];
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
version: '2'
|
||||
services:
|
||||
db:
|
||||
image: mysql
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: root
|
||||
MYSQL_DATABASE: symfony
|
||||
MYSQL_USER: symfony
|
||||
MYSQL_PASSWORD: symfony
|
||||
php:
|
||||
build: ./php-fpm
|
||||
expose:
|
||||
- "9000"
|
||||
volumes:
|
||||
- ./symfony:/var/www/symfony
|
||||
- ./logs/symfony:/var/www/symfony/var/logs
|
||||
links:
|
||||
- db
|
||||
nginx:
|
||||
build: ./nginx
|
||||
ports:
|
||||
- "80:80"
|
||||
links:
|
||||
- php
|
||||
volumes_from:
|
||||
- php
|
||||
volumes:
|
||||
- ./logs/nginx/:/var/log/nginx
|
||||
elk:
|
||||
image: willdurand/elk
|
||||
ports:
|
||||
- "81:80"
|
||||
volumes:
|
||||
- ./elk/logstash:/etc/logstash
|
||||
- ./elk/logstash/patterns:/opt/logstash/patterns
|
||||
volumes_from:
|
||||
- php
|
||||
- nginx
|
|
@ -0,0 +1,53 @@
|
|||
version: '2'
|
||||
|
||||
services:
|
||||
db:
|
||||
image: mysql
|
||||
ports:
|
||||
- "3311:3306"
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: root
|
||||
MYSQL_DATABASE: symfony
|
||||
MYSQL_USER: symfony
|
||||
MYSQL_PASSWORD: symfony
|
||||
php:
|
||||
build: ./docker/php-fpm
|
||||
ports:
|
||||
- "9001:9000"
|
||||
#volumes:
|
||||
# - ./symfony:/var/www/symfony:cached
|
||||
# - ./logs/symfony:/var/www/symfony/var/logs:cached
|
||||
links:
|
||||
- db
|
||||
nginx:
|
||||
build: ./docker/nginx
|
||||
ports:
|
||||
- "81:80"
|
||||
links:
|
||||
- php
|
||||
volumes_from:
|
||||
- php
|
||||
volumes:
|
||||
- ./logs/nginx/:/var/log/nginx:cached
|
||||
elk:
|
||||
image: willdurand/elk
|
||||
ports:
|
||||
- "82:80"
|
||||
volumes:
|
||||
- ./docker/elk/logstash:/etc/logstash:cached
|
||||
- ./docker/elk/logstash/patterns:/opt/logstash/patterns:cached
|
||||
volumes_from:
|
||||
- php
|
||||
- nginx
|
||||
react-app:
|
||||
container_name: react-app
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./docker/react/Dockerfile
|
||||
volumes:
|
||||
- '.:/usr/src/app'
|
||||
- '/usr/src/app/node_modules'
|
||||
ports:
|
||||
- '3000:3000'
|
||||
environment:
|
||||
- NODE_ENV=development
|
|
@ -0,0 +1,39 @@
|
|||
input {
|
||||
file {
|
||||
type => "nginx_access"
|
||||
path => "/var/log/nginx/symfony_access.log"
|
||||
start_position => beginning
|
||||
}
|
||||
file {
|
||||
type => "symfony_dev"
|
||||
path => "/var/www/symfony/var/log/dev.log"
|
||||
start_position => beginning
|
||||
}
|
||||
file {
|
||||
type => "symfony_prod"
|
||||
path => "/var/www/symfony/var/log/prod.log"
|
||||
start_position => beginning
|
||||
}
|
||||
}
|
||||
|
||||
filter {
|
||||
if [type] == "nginx_access" {
|
||||
grok {
|
||||
patterns_dir => "./patterns"
|
||||
match => { "message" => "%{NGINXACCESS}"}
|
||||
}
|
||||
}
|
||||
else if [type] in ["symfony_dev", "symfony_prod"] {
|
||||
grok {
|
||||
patterns_dir => "./patterns"
|
||||
match => { "message" => "%{SYMFONY}"}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
output {
|
||||
elasticsearch {
|
||||
host => "localhost"
|
||||
cluster => "logstash"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
USERNAME [a-zA-Z0-9._-]+
|
||||
USER %{USERNAME}
|
||||
INT (?:[+-]?(?:[0-9]+))
|
||||
BASE10NUM (?<![0-9.+-])(?>[+-]?(?:(?:[0-9]+(?:\.[0-9]+)?)|(?:\.[0-9]+)))
|
||||
NUMBER (?:%{BASE10NUM})
|
||||
BASE16NUM (?<![0-9A-Fa-f])(?:[+-]?(?:0x)?(?:[0-9A-Fa-f]+))
|
||||
BASE16FLOAT \b(?<![0-9A-Fa-f.])(?:[+-]?(?:0x)?(?:(?:[0-9A-Fa-f]+(?:\.[0-9A-Fa-f]*)?)|(?:\.[0-9A-Fa-f]+)))\b
|
||||
|
||||
POSINT \b(?:[1-9][0-9]*)\b
|
||||
NONNEGINT \b(?:[0-9]+)\b
|
||||
WORD \b\w+\b
|
||||
NOTSPACE \S+
|
||||
SPACE \s*
|
||||
DATA .*?
|
||||
GREEDYDATA .*
|
||||
QUOTEDSTRING (?>(?<!\\)(?>"(?>\\.|[^\\"]+)+"|""|(?>'(?>\\.|[^\\']+)+')|''|(?>`(?>\\.|[^\\`]+)+`)|``))
|
||||
UUID [A-Fa-f0-9]{8}-(?:[A-Fa-f0-9]{4}-){3}[A-Fa-f0-9]{12}
|
||||
# Networking
|
||||
MAC (?:%{CISCOMAC}|%{WINDOWSMAC}|%{COMMONMAC})
|
||||
CISCOMAC (?:(?:[A-Fa-f0-9]{4}\.){2}[A-Fa-f0-9]{4})
|
||||
WINDOWSMAC (?:(?:[A-Fa-f0-9]{2}-){5}[A-Fa-f0-9]{2})
|
||||
COMMONMAC (?:(?:[A-Fa-f0-9]{2}:){5}[A-Fa-f0-9]{2})
|
||||
IPV6 ((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?
|
||||
IPV4 (?<![0-9])(?:(?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2}))(?![0-9])
|
||||
IP (?:%{IPV6}|%{IPV4})
|
||||
HOSTNAME \b(?:[0-9A-Za-z][0-9A-Za-z-]{0,62})(?:\.(?:[0-9A-Za-z][0-9A-Za-z-]{0,62}))*(\.?|\b)
|
||||
HOST %{HOSTNAME}
|
||||
IPORHOST (?:%{HOSTNAME}|%{IP})
|
||||
HOSTPORT %{IPORHOST}:%{POSINT}
|
||||
# paths
|
||||
PATH (?:%{UNIXPATH}|%{WINPATH})
|
||||
UNIXPATH (?>/(?>[\w_%!$@:.,-]+|\\.)*)+
|
||||
TTY (?:/dev/(pts|tty([pq])?)(\w+)?/?(?:[0-9]+))
|
||||
WINPATH (?>[A-Za-z]+:|\\)(?:\\[^\\?*]*)+
|
||||
URIPROTO [A-Za-z]+(\+[A-Za-z+]+)?
|
||||
URIHOST %{IPORHOST}(?::%{POSINT:port})?
|
||||
# uripath comes loosely from RFC1738, but mostly from what Firefox
|
||||
# doesn't turn into %XX
|
||||
URIPATH (?:/[A-Za-z0-9$.+!*'(){},~:;=@#%_\-]*)+
|
||||
#URIPARAM \?(?:[A-Za-z0-9]+(?:=(?:[^&]*))?(?:&(?:[A-Za-z0-9]+(?:=(?:[^&]*))?)?)*)?
|
||||
URIPARAM \?[A-Za-z0-9$.+!*'|(){},~@#%&/=:;_?\-\[\]]*
|
||||
URIPATHPARAM %{URIPATH}(?:%{URIPARAM})?
|
||||
URI %{URIPROTO}://(?:%{USER}(?::[^@]*)?@)?(?:%{URIHOST})?(?:%{URIPATHPARAM})?
|
||||
# Months: January, Feb, 3, 03, 12, December
|
||||
MONTH \b(?:Jan(?:uary)?|Feb(?:ruary)?|Mar(?:ch)?|Apr(?:il)?|May|Jun(?:e)?|Jul(?:y)?|Aug(?:ust)?|Sep(?:tember)?|Oct(?:ober)?|Nov(?:ember)?|Dec(?:ember)?)\b
|
||||
MONTHNUM (?:0?[1-9]|1[0-2])
|
||||
MONTHNUM2 (?:0[1-9]|1[0-2])
|
||||
MONTHDAY (?:(?:0[1-9])|(?:[12][0-9])|(?:3[01])|[1-9])
|
||||
# Days: Monday, Tue, Thu, etc...
|
||||
DAY (?:Mon(?:day)?|Tue(?:sday)?|Wed(?:nesday)?|Thu(?:rsday)?|Fri(?:day)?|Sat(?:urday)?|Sun(?:day)?)
|
||||
# Years?
|
||||
YEAR (?>\d\d){1,2}
|
||||
HOUR (?:2[0123]|[01]?[0-9])
|
||||
MINUTE (?:[0-5][0-9])
|
||||
# '60' is a leap second in most time standards and thus is valid.
|
||||
SECOND (?:(?:[0-5]?[0-9]|60)(?:[:.,][0-9]+)?)
|
||||
TIME (?!<[0-9])%{HOUR}:%{MINUTE}(?::%{SECOND})(?![0-9])
|
||||
# datestamp is YYYY/MM/DD-HH:MM:SS.UUUU (or something like it)
|
||||
DATE_US %{MONTHNUM}[/-]%{MONTHDAY}[/-]%{YEAR}
|
||||
DATE_EU %{MONTHDAY}[./-]%{MONTHNUM}[./-]%{YEAR}
|
||||
ISO8601_TIMEZONE (?:Z|[+-]%{HOUR}(?::?%{MINUTE}))
|
||||
ISO8601_SECOND (?:%{SECOND}|60)
|
||||
TIMESTAMP_ISO8601 %{YEAR}-%{MONTHNUM}-%{MONTHDAY}[T ]%{HOUR}:?%{MINUTE}(?::?%{SECOND})?%{ISO8601_TIMEZONE}?
|
||||
DATE %{DATE_US}|%{DATE_EU}
|
||||
DATESTAMP %{DATE}[- ]%{TIME}
|
||||
TZ (?:[PMCE][SD]T|UTC)
|
||||
DATESTAMP_RFC822 %{DAY} %{MONTH} %{MONTHDAY} %{YEAR} %{TIME} %{TZ}
|
||||
DATESTAMP_RFC2822 %{DAY}, %{MONTHDAY} %{MONTH} %{YEAR} %{TIME} %{ISO8601_TIMEZONE}
|
||||
DATESTAMP_OTHER %{DAY} %{MONTH} %{MONTHDAY} %{TIME} %{TZ} %{YEAR}
|
||||
DATESTAMP_EVENTLOG %{YEAR}%{MONTHNUM2}%{MONTHDAY}%{HOUR}%{MINUTE}%{SECOND}
|
||||
# Syslog Dates: Month Day HH:MM:SS
|
||||
SYSLOGTIMESTAMP %{MONTH} +%{MONTHDAY} %{TIME}
|
||||
PROG (?:[\w._/%-]+)
|
||||
SYSLOGPROG %{PROG:program}(?:\[%{POSINT:pid}\])?
|
||||
SYSLOGHOST %{IPORHOST}
|
||||
SYSLOGFACILITY <%{NONNEGINT:facility}.%{NONNEGINT:priority}>
|
||||
HTTPDATE %{MONTHDAY}/%{MONTH}/%{YEAR}:%{TIME} %{INT}
|
||||
# Shortcuts
|
||||
QS %{QUOTEDSTRING}
|
||||
# Log formats
|
||||
SYSLOGBASE %{SYSLOGTIMESTAMP:timestamp} (?:%{SYSLOGFACILITY} )?%{SYSLOGHOST:logsource} %{SYSLOGPROG}:
|
||||
COMMONAPACHELOG %{IPORHOST:clientip} %{USER:ident} %{USER:auth} \[%{HTTPDATE:timestamp}\] "(?:%{WORD:verb} %{NOTSPACE:request}(?: HTTP/%{NUMBER:httpversion})?|%{DATA:rawrequest})" %{NUMBER:response} (?:%{NUMBER:bytes}|-)
|
||||
COMBINEDAPACHELOG %{COMMONAPACHELOG} %{QS:referrer} %{QS:agent}
|
||||
# Log Levels
|
||||
LOGLEVEL ([Aa]lert|ALERT|[Tt]race|TRACE|[Dd]ebug|DEBUG|[Nn]otice|NOTICE|[Ii]nfo|INFO|[Ww]arn?(?:ing)?|WARN?(?:ING)?|[Ee]rr?(?:or)?|ERR?(?:OR)?|[Cc]rit?(?:ical)?|CRIT?(?:ICAL)?|[Ff]atal|FATAL|[Ss]evere|SEVERE|EMERG(?:ENCY)?|[Ee]merg(?:ency)?)
|
|
@ -0,0 +1 @@
|
|||
NGINXACCESS %{IPORHOST:clientip} %{USER:ident} %{USER:auth} \[%{HTTPDATE:timestamp}\] "(?:%{WORD:verb} %{URIPATHPARAM:request}(?: HTTP/%{NUMBER:httpversion})?|-)" %{NUMBER:response} (?:%{NUMBER:bytes}|-) "(?:%{URI:referrer}|-)" %{QS:agent} %{NUMBER:request_time} %{NUMBER:upstream_response_time} %{NUMBER:gzip_ratio} (?:%{WORD:cache_hit}|-)%{GREEDYDATA}
|
|
@ -0,0 +1,34 @@
|
|||
VERYGREEDYDATA (.|\n)*
|
||||
|
||||
SYMFONY_EXCEPTION [^:]*
|
||||
|
||||
SYMFONY_LOG_TYPE request|security|app|profiler|doctrine|event
|
||||
SYMFONY_LOG_LEVEL DEBUG|INFO|WARNING|ERROR|CRITICAL|ALERT
|
||||
SYMFONY_LOG %{SYMFONY_LOG_TYPE:log_type}\.%{SYMFONY_LOG_LEVEL:log_level}
|
||||
|
||||
SYMFONY_PARAMETER "[^"]*":( )?"[^"]*"
|
||||
SYMFONY_PARAMETERS (%{SYMFONY_PARAMETER}(, )?)*
|
||||
SYMFONY_CONTEXT {.*}
|
||||
SYMFONY_REQUEST_METHOD GET|POST|PUT|DELETE|HEAD|OPTIONS|CONNECT
|
||||
SYMFONY_REQUEST_PARAMETERS {"url":"%{GREEDYDATA:request_url}","ip":"%{IP:request_ip}","http_method":"%{SYMFONY_REQUEST_METHOD:request_method}"}
|
||||
|
||||
SYMFONY_REQUEST_INFO Matched route "%{GREEDYDATA:route}" \(parameters: %{SYMFONY_PARAMETERS:parameters}\)
|
||||
SYMFONY_REQUEST_UNCAUGHT_EXCEPTION %{SYMFONY_EXCEPTION:exception}: %{VERYGREEDYDATA:exception_message} \(uncaught exception\) at %{VERYGREEDYDATA:exception_file} line %{NUMBER:exception_file_line}
|
||||
SYMFONY_REQUEST_CRITICAL Exception thrown when handling an exception \(ErrorException: %{GREEDYDATA:exception_message} in %{GREEDYDATA:exception_file} line %{NUMBER:exception_file_line}\)
|
||||
SYMFONY_SECURITY_WARNING_USER_MISSING Username "%{GREEDYDATA:user}" could not be found.
|
||||
SYMFONY_SECURITY_INFO_USER_AUTHENTICATED User "%{GREEDYDATA:user}" has been authenticated successfully
|
||||
SYMFONY_SECURITY_INFO_AUTHENTICATION_FAILED Authentication request failed: %{GREEDYDATA:authentication_fail_reason}
|
||||
SYMFONY_SECURITY_DEBUG Username "%{GREEDYDATA:user}" was reloaded from user provider.
|
||||
SYMFONY_EVENT_DEBUG_NOTIFICATION Notified event "%{GREEDYDATA:event}" to listener "%{GREEDYDATA:listener}".
|
||||
SYMFONY_EVENT_DEBUG_PROPAGATION_STOP Listener "%{GREEDYDATA:listener}" stopped propagation of the event "%{GREEDYDATA:event}".
|
||||
SYMFONY_DOCTRINE_DEBUG (?<=doctrine.DEBUG: ).*
|
||||
|
||||
SYMFONY_REQUEST %{SYMFONY_REQUEST_INFO}|%{SYMFONY_REQUEST_UNCAUGHT_EXCEPTION}|%{SYMFONY_REQUEST_CRITICAL}
|
||||
SYMFONY_SECURITY %{SYMFONY_SECURITY_WARNING_USER_MISSING}|%{SYMFONY_SECURITY_INFO_USER_AUTHENTICATED}|%{SYMFONY_SECURITY_DEBUG}|%{SYMFONY_SECURITY_INFO_AUTHENTICATION_FAILED}
|
||||
SYMFONY_EVENT %{SYMFONY_EVENT_DEBUG_NOTIFICATION}|%{SYMFONY_EVENT_DEBUG_PROPAGATION_STOP}
|
||||
SYMFONY_DOCTRINE %{SYMFONY_DOCTRINE_DEBUG:doctrine_sql_query}
|
||||
SYMFONY_VARIOUS_INFO Write SecurityContext in the session|Reloading user from user provider.|Read SecurityContext from the session|Populated SecurityContext with an anonymous Token|Access is denied (and user is neither anonymous, nor remember-me)|Unable to store the profiler information.|Remember-me cookie accepted.
|
||||
|
||||
SYMFONY_LOG_MESSAGE %{SYMFONY_REQUEST}|%{SYMFONY_SECURITY}|%{SYMFONY_EVENT}|%{SYMFONY_DOCTRINE}|%{SYMFONY_VARIOUS_INFO:log_various_info}|%{VERYGREEDYDATA:log_unparsed_message}
|
||||
|
||||
SYMFONY ^\[%{TIMESTAMP_ISO8601:date}\] %{SYMFONY_LOG}: %{SYMFONY_LOG_MESSAGE:log_message} (\[\]|%{SYMFONY_CONTEXT:log_context}) (\[\]|%{SYMFONY_REQUEST_PARAMETERS:log_request})
|
|
@ -0,0 +1,18 @@
|
|||
FROM alpine:3.6
|
||||
|
||||
MAINTAINER Vincent Composieux <vincent.composieux@gmail.com>
|
||||
|
||||
RUN apk add --update nginx
|
||||
RUN rm -rf /var/cache/apk/* && rm -rf /tmp/*
|
||||
|
||||
ADD nginx.conf /etc/nginx/
|
||||
ADD symfony.conf /etc/nginx/conf.d/
|
||||
|
||||
RUN echo "upstream php-upstream { server php:9001; }" > /etc/nginx/conf.d/upstream.conf
|
||||
|
||||
RUN adduser -D -g '' -G www-data www-data
|
||||
|
||||
CMD ["nginx"]
|
||||
|
||||
EXPOSE 80
|
||||
EXPOSE 443
|
|
@ -0,0 +1,29 @@
|
|||
user www-data;
|
||||
worker_processes 4;
|
||||
pid /run/nginx.pid;
|
||||
|
||||
events {
|
||||
worker_connections 2048;
|
||||
multi_accept on;
|
||||
use epoll;
|
||||
}
|
||||
|
||||
http {
|
||||
server_tokens off;
|
||||
sendfile on;
|
||||
tcp_nopush on;
|
||||
tcp_nodelay on;
|
||||
keepalive_timeout 15;
|
||||
types_hash_max_size 2048;
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
access_log off;
|
||||
error_log off;
|
||||
gzip on;
|
||||
gzip_disable "msie6";
|
||||
include /etc/nginx/conf.d/*.conf;
|
||||
include /etc/nginx/sites-enabled/*;
|
||||
open_file_cache max=100;
|
||||
}
|
||||
|
||||
daemon off;
|
|
@ -0,0 +1,23 @@
|
|||
server {
|
||||
server_name localhost;
|
||||
root /var/www/symfony/public;
|
||||
|
||||
location / {
|
||||
try_files $uri @rewriteapp;
|
||||
}
|
||||
|
||||
location @rewriteapp {
|
||||
rewrite ^(.*)$ /index.php/$1 last;
|
||||
}
|
||||
|
||||
location ~ ^/index\.php(/|$) {
|
||||
fastcgi_pass php-upstream;
|
||||
fastcgi_split_path_info ^(.+\.php)(/.*)$;
|
||||
include fastcgi_params;
|
||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||
fastcgi_param HTTPS off;
|
||||
}
|
||||
|
||||
error_log /var/log/nginx/symfony_error.log;
|
||||
access_log /var/log/nginx/symfony_access.log;
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
FROM alpine:3.6
|
||||
|
||||
LABEL maintainer="Vincent Composieux <vincent.composieux@gmail.com>"
|
||||
|
||||
RUN apk add --update \
|
||||
php7-fpm \
|
||||
php7-apcu \
|
||||
php7-ctype \
|
||||
php7-curl \
|
||||
php7-dom \
|
||||
php7-gd \
|
||||
php7-iconv \
|
||||
php7-imagick \
|
||||
php7-json \
|
||||
php7-intl \
|
||||
php7-mcrypt \
|
||||
php7-mbstring \
|
||||
php7-opcache \
|
||||
php7-openssl \
|
||||
php7-pdo \
|
||||
php7-pdo_mysql \
|
||||
php7-mysqli \
|
||||
php7-xml \
|
||||
php7-zlib \
|
||||
php7-phar \
|
||||
php7-tokenizer \
|
||||
php7-session \
|
||||
php7-simplexml \
|
||||
make \
|
||||
curl
|
||||
|
||||
RUN rm -rf /var/cache/apk/* && rm -rf /tmp/*
|
||||
|
||||
RUN curl --insecure https://getcomposer.org/composer.phar -o /usr/bin/composer && chmod +x /usr/bin/composer
|
||||
|
||||
ADD symfony.ini /etc/php7/php-fpm.d/
|
||||
ADD symfony.ini /etc/php7/cli/conf.d/
|
||||
|
||||
ADD symfony.pool.conf /etc/php7/php-fpm.d/
|
||||
|
||||
CMD ["php-fpm7", "-F"]
|
||||
|
||||
WORKDIR /var/www/symfony
|
||||
EXPOSE 9000
|
|
@ -0,0 +1 @@
|
|||
date.timezone = UTC
|
|
@ -0,0 +1,81 @@
|
|||
; Start a new pool named 'symfony'.
|
||||
; the variable $pool can be used in any directive and will be replaced by the
|
||||
; pool name ('symfony' here)
|
||||
[symfony]
|
||||
|
||||
; Unix user/group of processes
|
||||
; Note: The user is mandatory. If the group is not set, the default user's group
|
||||
; will be used.
|
||||
user = nobody
|
||||
group = nobody
|
||||
|
||||
; The address on which to accept FastCGI requests.
|
||||
; Valid syntaxes are:
|
||||
; 'ip.add.re.ss:port' - to listen on a TCP socket to a specific address on
|
||||
; a specific port;
|
||||
; 'port' - to listen on a TCP socket to all addresses on a
|
||||
; specific port;
|
||||
; '/path/to/unix/socket' - to listen on a unix socket.
|
||||
; Note: This value is mandatory.
|
||||
listen = 0.0.0.0:9001
|
||||
|
||||
; Choose how the process manager will control the number of child processes.
|
||||
; Possible Values:
|
||||
; static - a fixed number (pm.max_children) of child processes;
|
||||
; dynamic - the number of child processes are set dynamically based on the
|
||||
; following directives. With this process management, there will be
|
||||
; always at least 1 children.
|
||||
; pm.max_children - the maximum number of children that can
|
||||
; be alive at the same time.
|
||||
; pm.start_servers - the number of children created on startup.
|
||||
; pm.min_spare_servers - the minimum number of children in 'idle'
|
||||
; state (waiting to process). If the number
|
||||
; of 'idle' processes is less than this
|
||||
; number then some children will be created.
|
||||
; pm.max_spare_servers - the maximum number of children in 'idle'
|
||||
; state (waiting to process). If the number
|
||||
; of 'idle' processes is greater than this
|
||||
; number then some children will be killed.
|
||||
; ondemand - no children are created at startup. Children will be forked when
|
||||
; new requests will connect. The following parameter are used:
|
||||
; pm.max_children - the maximum number of children that
|
||||
; can be alive at the same time.
|
||||
; pm.process_idle_timeout - The number of seconds after which
|
||||
; an idle process will be killed.
|
||||
; Note: This value is mandatory.
|
||||
pm = dynamic
|
||||
|
||||
; The number of child processes to be created when pm is set to 'static' and the
|
||||
; maximum number of child processes when pm is set to 'dynamic' or 'ondemand'.
|
||||
; This value sets the limit on the number of simultaneous requests that will be
|
||||
; served. Equivalent to the ApacheMaxClients directive with mpm_prefork.
|
||||
; Equivalent to the PHP_FCGI_CHILDREN environment variable in the original PHP
|
||||
; CGI. The below defaults are based on a server without much resources. Don't
|
||||
; forget to tweak pm.* to fit your needs.
|
||||
; Note: Used when pm is set to 'static', 'dynamic' or 'ondemand'
|
||||
; Note: This value is mandatory.
|
||||
pm.max_children = 20
|
||||
|
||||
; The number of child processes created on startup.
|
||||
; Note: Used only when pm is set to 'dynamic'
|
||||
; Default Value: min_spare_servers + (max_spare_servers - min_spare_servers) / 2
|
||||
pm.start_servers = 2
|
||||
|
||||
; The desired minimum number of idle server processes.
|
||||
; Note: Used only when pm is set to 'dynamic'
|
||||
; Note: Mandatory when pm is set to 'dynamic'
|
||||
pm.min_spare_servers = 1
|
||||
|
||||
; The desired maximum number of idle server processes.
|
||||
; Note: Used only when pm is set to 'dynamic'
|
||||
; Note: Mandatory when pm is set to 'dynamic'
|
||||
pm.max_spare_servers = 3
|
||||
|
||||
;---------------------
|
||||
|
||||
; Make specific Docker environment variables available to PHP
|
||||
env[DB_1_ENV_MYSQL_DATABASE] = $DB_1_ENV_MYSQL_DATABASE
|
||||
env[DB_1_ENV_MYSQL_USER] = $DB_1_ENV_MYSQL_USER
|
||||
env[DB_1_ENV_MYSQL_PASSWORD] = $DB_1_ENV_MYSQL_PASSWORD
|
||||
|
||||
catch_workers_output = yes
|
|
@ -0,0 +1,17 @@
|
|||
# base image
|
||||
FROM node:9.6.1
|
||||
|
||||
# set working directory
|
||||
RUN mkdir /usr/src/app
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# add `/usr/src/app/node_modules/.bin` to $PATH
|
||||
ENV PATH /usr/src/app/node_modules/.bin:$PATH
|
||||
|
||||
# install and cache app dependencies
|
||||
COPY client/package.json /usr/src/app/package.json
|
||||
RUN npm install --silent
|
||||
#RUN npm install react-scripts@1.1.1 -g --silent
|
||||
|
||||
# start app
|
||||
CMD ["make", "front-run"]
|
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\Question;
|
||||
use App\Form\QuestionType;
|
||||
use App\Repository\QuestionRepository;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
/**
|
||||
* @Route("/admin/question")
|
||||
*/
|
||||
class QuestionController extends Controller
|
||||
{
|
||||
/**
|
||||
* @Route("/", name="question_index", methods="GET")
|
||||
*/
|
||||
public function index(QuestionRepository $questionRepository): Response
|
||||
{
|
||||
return $this->render('question/index.html.twig', ['questions' => $questionRepository->findAll()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/new", name="question_new", methods="GET|POST")
|
||||
*/
|
||||
public function new(Request $request): Response
|
||||
{
|
||||
$question = new Question();
|
||||
$form = $this->createForm(QuestionType::class, $question);
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$em = $this->getDoctrine()->getManager();
|
||||
$em->persist($question);
|
||||
$em->flush();
|
||||
|
||||
return $this->redirectToRoute('question_index');
|
||||
}
|
||||
|
||||
return $this->render('question/new.html.twig', [
|
||||
'question' => $question,
|
||||
'form' => $form->createView(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{id}", name="question_show", methods="GET")
|
||||
*/
|
||||
public function show(Question $question): Response
|
||||
{
|
||||
return $this->render('question/show.html.twig', ['question' => $question]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{id}/edit", name="question_edit", methods="GET|POST")
|
||||
*/
|
||||
public function edit(Request $request, Question $question): Response
|
||||
{
|
||||
$form = $this->createForm(QuestionType::class, $question);
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$this->getDoctrine()->getManager()->flush();
|
||||
|
||||
return $this->redirectToRoute('question_edit', ['id' => $question->getId()]);
|
||||
}
|
||||
|
||||
return $this->render('question/edit.html.twig', [
|
||||
'question' => $question,
|
||||
'form' => $form->createView(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{id}", name="question_delete", methods="DELETE")
|
||||
*/
|
||||
public function delete(Request $request, Question $question): Response
|
||||
{
|
||||
if ($this->isCsrfTokenValid('delete'.$question->getId(), $request->request->get('_token'))) {
|
||||
$em = $this->getDoctrine()->getManager();
|
||||
$em->remove($question);
|
||||
$em->flush();
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('question_index');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\Response;
|
||||
use App\Form\ResponseType;
|
||||
use App\Repository\ResponseRepository;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response as HttpResponse;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
/**
|
||||
* @Route("/admin/response")
|
||||
*/
|
||||
class ResponseController extends Controller
|
||||
{
|
||||
/**
|
||||
* @Route("/", name="response_index", methods="GET")
|
||||
*/
|
||||
public function index(ResponseRepository $responseRepository): HttpResponse
|
||||
{
|
||||
return $this->render('response/index.html.twig', ['responses' => $responseRepository->findAll()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/new", name="response_new", methods="GET|POST")
|
||||
*/
|
||||
public function new(Request $request): HttpResponse
|
||||
{
|
||||
$response = new Response();
|
||||
$form = $this->createForm(ResponseType::class, $response);
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$em = $this->getDoctrine()->getManager();
|
||||
$em->persist($response);
|
||||
$em->flush();
|
||||
|
||||
return $this->redirectToRoute('response_index');
|
||||
}
|
||||
|
||||
return $this->render('response/new.html.twig', [
|
||||
'response' => $response,
|
||||
'form' => $form->createView(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{id}", name="response_show", methods="GET")
|
||||
*/
|
||||
public function show(Response $response): HttpResponse
|
||||
{
|
||||
return $this->render('response/show.html.twig', ['response' => $response]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{id}/edit", name="response_edit", methods="GET|POST")
|
||||
*/
|
||||
public function edit(Request $request, Response $response): HttpResponse
|
||||
{
|
||||
$form = $this->createForm(ResponseType::class, $response);
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$this->getDoctrine()->getManager()->flush();
|
||||
|
||||
return $this->redirectToRoute('response_edit', ['id' => $response->getId()]);
|
||||
}
|
||||
|
||||
return $this->render('response/edit.html.twig', [
|
||||
'response' => $response,
|
||||
'form' => $form->createView(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{id}", name="response_delete", methods="DELETE")
|
||||
*/
|
||||
public function delete(Request $request, Response $response): HttpResponse
|
||||
{
|
||||
if ($this->isCsrfTokenValid('delete'.$response->getId(), $request->request->get('_token'))) {
|
||||
$em = $this->getDoctrine()->getManager();
|
||||
$em->remove($response);
|
||||
$em->flush();
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('response_index');
|
||||
}
|
||||
}
|
|
@ -12,21 +12,26 @@ namespace App\DataFixtures;
|
|||
|
||||
use App\Entity\Question;
|
||||
use App\Entity\Response;
|
||||
use App\Repository\ResponseRepository;
|
||||
use Doctrine\Bundle\FixturesBundle\Fixture;
|
||||
use Doctrine\Common\Persistence\ObjectManager;
|
||||
use Doctrine\Common\DataFixtures\FixtureInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
class AppFixtures extends Fixture
|
||||
{
|
||||
private $container;
|
||||
|
||||
public function __construct()
|
||||
public function __construct(ContainerInterface $container = null)
|
||||
{
|
||||
//
|
||||
$this->container = $container;
|
||||
}
|
||||
|
||||
public function load(ObjectManager $manager)
|
||||
{
|
||||
$this->loadQuestions($manager);
|
||||
//$this->loadResponses($manager);
|
||||
$this->loadResponses($manager);
|
||||
}
|
||||
|
||||
private function loadQuestions(ObjectManager $manager)
|
||||
|
@ -34,18 +39,15 @@ class AppFixtures extends Fixture
|
|||
foreach ($this->getData() as $data) {
|
||||
$question = new Question();
|
||||
$question->setText($data['text']);
|
||||
$question->setToken($data['token']);
|
||||
$manager->persist($question);
|
||||
|
||||
foreach ($data['responses'] as $response) {
|
||||
$newResponse = new Response();
|
||||
$newResponse->setText($response['text']);
|
||||
$newResponse->setQuestion($question->getId());
|
||||
$newResponse->setQuestion($question);
|
||||
$manager->persist($newResponse);
|
||||
|
||||
$question->addResponse($newResponse);
|
||||
}
|
||||
|
||||
$manager->persist($question);
|
||||
//$this->addReference($username, $user);
|
||||
}
|
||||
|
||||
$manager->flush();
|
||||
|
@ -53,35 +55,57 @@ class AppFixtures extends Fixture
|
|||
|
||||
private function loadResponses(ObjectManager $manager)
|
||||
{
|
||||
//
|
||||
$container = $this->container->get('doctrine');
|
||||
$responseRepository = $container->getRepository('App:Response');
|
||||
$questionRepository = $container->getRepository('App:Question');
|
||||
|
||||
foreach ($this->getData() as $data) {
|
||||
foreach ($data['responses'] as $response) {
|
||||
if (null !== $response['child']) {
|
||||
$newResponse = $responseRepository->findOneBy(['text' => $response['text']]);
|
||||
$question = $questionRepository->findByToken($response['child']);
|
||||
|
||||
if (null !== $newResponse && null !== $question) {
|
||||
$newResponse->setChild($question);
|
||||
$manager->flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function getData()
|
||||
{
|
||||
return [
|
||||
[
|
||||
"token" => "aNs9skL",
|
||||
"text" => "ma question 1",
|
||||
"responses" => [
|
||||
[
|
||||
"text" => "option1",
|
||||
"child" => 1,
|
||||
"child" => "0ks6dkP",
|
||||
],
|
||||
[
|
||||
"text" => "option2",
|
||||
"child" => null,
|
||||
],
|
||||
]
|
||||
],
|
||||
[
|
||||
"token" => "0ks6dkP",
|
||||
"text" => "ma question 2",
|
||||
"responses" => [
|
||||
[
|
||||
"text" => "option1",
|
||||
"child" => null,
|
||||
],
|
||||
[
|
||||
"text" => "option2",
|
||||
"child" => null,
|
||||
],
|
||||
[
|
||||
"text" => "option3",
|
||||
"child" => null,
|
||||
],
|
||||
]
|
||||
]
|
||||
|
|
|
@ -6,18 +6,13 @@ use Doctrine\Common\Collections\ArrayCollection;
|
|||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use ApiPlatform\Core\Annotation\ApiResource;
|
||||
use Doctrine\ORM\Query;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
use ApiPlatform\Core\Annotation\ApiSubresource;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
|
||||
use Symfony\Component\Serializer\Serializer;
|
||||
use Symfony\Component\Serializer\Encoder\XmlEncoder;
|
||||
use Symfony\Component\Serializer\Encoder\JsonEncoder;
|
||||
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
|
||||
|
||||
/**
|
||||
* @ApiResource()
|
||||
* @ApiResource(
|
||||
* collectionOperations={"get"={"method"="GET"}},
|
||||
* itemOperations={"get"={"method"="GET"}}
|
||||
* )
|
||||
* @ORM\Table(name="question")
|
||||
* @ORM\Entity(repositoryClass="App\Repository\QuestionRepository")
|
||||
*/
|
||||
|
@ -60,6 +55,11 @@ class Question
|
|||
return $question;
|
||||
}*/
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return $this->getText();
|
||||
}
|
||||
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
|
|
|
@ -9,7 +9,10 @@ use Symfony\Component\Validator\Constraints as Assert;
|
|||
use ApiPlatform\Core\Annotation\ApiSubresource;
|
||||
|
||||
/**
|
||||
* @ApiResource()
|
||||
* @ApiResource(
|
||||
* collectionOperations={"get"={"method"="GET"}},
|
||||
* itemOperations={"get"={"method"="GET"}}
|
||||
* )
|
||||
* @ORM\Table(name="response")
|
||||
* @ORM\Entity(repositoryClass="App\Repository\ResponseRepository")
|
||||
*/
|
||||
|
@ -36,6 +39,7 @@ class Response
|
|||
/**
|
||||
* @ORM\OneToOne(targetEntity="App\Entity\Question", cascade={"persist", "remove"})
|
||||
* @ORM\JoinTable(name="question")
|
||||
* @ApiSubresource()
|
||||
*/
|
||||
private $child;
|
||||
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
namespace App\Form;
|
||||
|
||||
use App\Entity\Question;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class QuestionType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$builder
|
||||
->add('text')
|
||||
;
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => Question::class,
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace App\Form;
|
||||
|
||||
use App\Entity\Response;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class ResponseType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$builder
|
||||
->add('text')
|
||||
->add('question')
|
||||
->add('child')
|
||||
;
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => Response::class,
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -19,22 +19,19 @@ class QuestionRepository extends ServiceEntityRepository
|
|||
parent::__construct($registry, Question::class);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @return Question[] Returns an array of Question objects
|
||||
// */
|
||||
/*
|
||||
public function findByExampleField($value)
|
||||
/**
|
||||
* @return Question[] Returns an array of Question objects
|
||||
*/
|
||||
public function findByToken($token)
|
||||
{
|
||||
return $this->createQueryBuilder('q')
|
||||
->andWhere('q.exampleField = :val')
|
||||
->setParameter('val', $value)
|
||||
->orderBy('q.id', 'ASC')
|
||||
->setMaxResults(10)
|
||||
->andWhere('q.token = :token')
|
||||
->setParameter('token', $token)
|
||||
->setMaxResults(1)
|
||||
->getQuery()
|
||||
->getResult()
|
||||
;
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
public function findOneBySomeField($value): ?Question
|
||||
|
|
21
symfony.lock
21
symfony.lock
|
@ -122,6 +122,15 @@
|
|||
"psr/simple-cache": {
|
||||
"version": "1.0.1"
|
||||
},
|
||||
"sensio/framework-extra-bundle": {
|
||||
"version": "4.0",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "master",
|
||||
"version": "4.0",
|
||||
"ref": "aaddfdf43cdecd4cf91f992052d76c2cadc04543"
|
||||
}
|
||||
},
|
||||
"symfony/asset": {
|
||||
"version": "v4.0.9"
|
||||
},
|
||||
|
@ -173,6 +182,9 @@
|
|||
"ref": "cc1afd81841db36fbef982fe56b48ade6716fac4"
|
||||
}
|
||||
},
|
||||
"symfony/form": {
|
||||
"version": "v4.1.0"
|
||||
},
|
||||
"symfony/framework-bundle": {
|
||||
"version": "3.3",
|
||||
"recipe": {
|
||||
|
@ -191,6 +203,9 @@
|
|||
"symfony/inflector": {
|
||||
"version": "v4.0.9"
|
||||
},
|
||||
"symfony/intl": {
|
||||
"version": "v4.1.0"
|
||||
},
|
||||
"symfony/lts": {
|
||||
"version": "4-dev"
|
||||
},
|
||||
|
@ -203,12 +218,18 @@
|
|||
"ref": "fadbfe33303a76e25cb63401050439aa9b1a9c7f"
|
||||
}
|
||||
},
|
||||
"symfony/options-resolver": {
|
||||
"version": "v4.1.0"
|
||||
},
|
||||
"symfony/orm-pack": {
|
||||
"version": "v1.0.5"
|
||||
},
|
||||
"symfony/polyfill-ctype": {
|
||||
"version": "v1.8.0"
|
||||
},
|
||||
"symfony/polyfill-intl-icu": {
|
||||
"version": "v1.8.0"
|
||||
},
|
||||
"symfony/polyfill-mbstring": {
|
||||
"version": "v1.8.0"
|
||||
},
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
<form method="post" action="{{ path('question_delete', {'id': question.id}) }}" onsubmit="return confirm('Are you sure you want to delete this item?');">
|
||||
<input type="hidden" name="_method" value="DELETE">
|
||||
<input type="hidden" name="_token" value="{{ csrf_token('delete' ~ question.id) }}">
|
||||
<button class="btn">Delete</button>
|
||||
</form>
|
|
@ -0,0 +1,4 @@
|
|||
{{ form_start(form) }}
|
||||
{{ form_widget(form) }}
|
||||
<button class="btn">{{ button_label|default('Save') }}</button>
|
||||
{{ form_end(form) }}
|
|
@ -0,0 +1,13 @@
|
|||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block title %}Edit Question{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<h1>Edit Question</h1>
|
||||
|
||||
{{ include('question/_form.html.twig', {'button_label': 'Update'}) }}
|
||||
|
||||
<a href="{{ path('question_index') }}">back to list</a>
|
||||
|
||||
{{ include('question/_delete_form.html.twig') }}
|
||||
{% endblock %}
|
|
@ -0,0 +1,37 @@
|
|||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block title %}Question index{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<h1>Question index</h1>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Id</th>
|
||||
<th>Text</th>
|
||||
<th>Date</th>
|
||||
<th>actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for question in questions %}
|
||||
<tr>
|
||||
<td>{{ question.id }}</td>
|
||||
<td>{{ question.text }}</td>
|
||||
<td>{{ question.date ? question.date|date('Y-m-d H:i:s') : '' }}</td>
|
||||
<td>
|
||||
<a href="{{ path('question_show', {'id': question.id}) }}">show</a>
|
||||
<a href="{{ path('question_edit', {'id': question.id}) }}">edit</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="5">no records found</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<a href="{{ path('question_new') }}">Create new</a>
|
||||
{% endblock %}
|
|
@ -0,0 +1,11 @@
|
|||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block title %}New Question{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<h1>Create new Question</h1>
|
||||
|
||||
{{ include('question/_form.html.twig') }}
|
||||
|
||||
<a href="{{ path('question_index') }}">back to list</a>
|
||||
{% endblock %}
|
|
@ -0,0 +1,30 @@
|
|||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block title %}Question{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<h1>Question</h1>
|
||||
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Id</th>
|
||||
<td>{{ question.id }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Text</th>
|
||||
<td>{{ question.text }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<td>{{ question.date ? question.date|date('Y-m-d H:i:s') : '' }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<a href="{{ path('question_index') }}">back to list</a>
|
||||
|
||||
<a href="{{ path('question_edit', {'id': question.id}) }}">edit</a>
|
||||
|
||||
{{ include('question/_delete_form.html.twig') }}
|
||||
{% endblock %}
|
|
@ -0,0 +1,5 @@
|
|||
<form method="post" action="{{ path('response_delete', {'id': response.id}) }}" onsubmit="return confirm('Are you sure you want to delete this item?');">
|
||||
<input type="hidden" name="_method" value="DELETE">
|
||||
<input type="hidden" name="_token" value="{{ csrf_token('delete' ~ response.id) }}">
|
||||
<button class="btn">Delete</button>
|
||||
</form>
|
|
@ -0,0 +1,4 @@
|
|||
{{ form_start(form) }}
|
||||
{{ form_widget(form) }}
|
||||
<button class="btn">{{ button_label|default('Save') }}</button>
|
||||
{{ form_end(form) }}
|
|
@ -0,0 +1,13 @@
|
|||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block title %}Edit Response{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<h1>Edit Response</h1>
|
||||
|
||||
{{ include('response/_form.html.twig', {'button_label': 'Update'}) }}
|
||||
|
||||
<a href="{{ path('response_index') }}">back to list</a>
|
||||
|
||||
{{ include('response/_delete_form.html.twig') }}
|
||||
{% endblock %}
|
|
@ -0,0 +1,35 @@
|
|||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block title %}Response index{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<h1>Response index</h1>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Id</th>
|
||||
<th>Text</th>
|
||||
<th>actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for response in responses %}
|
||||
<tr>
|
||||
<td>{{ response.id }}</td>
|
||||
<td>{{ response.text }}</td>
|
||||
<td>
|
||||
<a href="{{ path('response_show', {'id': response.id}) }}">show</a>
|
||||
<a href="{{ path('response_edit', {'id': response.id}) }}">edit</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="3">no records found</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<a href="{{ path('response_new') }}">Create new</a>
|
||||
{% endblock %}
|
|
@ -0,0 +1,11 @@
|
|||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block title %}New Response{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<h1>Create new Response</h1>
|
||||
|
||||
{{ include('response/_form.html.twig') }}
|
||||
|
||||
<a href="{{ path('response_index') }}">back to list</a>
|
||||
{% endblock %}
|
|
@ -0,0 +1,26 @@
|
|||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block title %}Response{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<h1>Response</h1>
|
||||
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Id</th>
|
||||
<td>{{ response.id }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Text</th>
|
||||
<td>{{ response.text }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<a href="{{ path('response_index') }}">back to list</a>
|
||||
|
||||
<a href="{{ path('response_edit', {'id': response.id}) }}">edit</a>
|
||||
|
||||
{{ include('response/_delete_form.html.twig') }}
|
||||
{% endblock %}
|
Loading…
Reference in New Issue