Base du projet 'application ticketing'
This commit is contained in:
parent
2a72ac97ac
commit
afa734f96d
3
Makefile
3
Makefile
|
@ -9,3 +9,6 @@ backend-shell:
|
||||||
|
|
||||||
frontend-shell:
|
frontend-shell:
|
||||||
docker-compose exec frontend /bin/bash
|
docker-compose exec frontend /bin/bash
|
||||||
|
|
||||||
|
database-mysql:
|
||||||
|
docker-compose exec database mysql --default-character-set=utf8 -uroot -proot logo
|
24
README.md
24
README.md
|
@ -9,10 +9,10 @@ Squelette applicatif React/Symfony 4 pour la formation React personnalisée Logo
|
||||||
- [Docker](https://docs.docker.com/install/)
|
- [Docker](https://docs.docker.com/install/)
|
||||||
- [Docker Compose](https://docs.docker.com/compose/install/)
|
- [Docker Compose](https://docs.docker.com/compose/install/)
|
||||||
|
|
||||||
```
|
```bash
|
||||||
git clone https://forge.cadoles.com/wpetit/react-logo.git
|
git clone https://forge.cadoles.com/wpetit/react-logo.git
|
||||||
cd react-logo
|
cd react-logo
|
||||||
docker-compose up
|
make up # ou docker-compose up --build
|
||||||
```
|
```
|
||||||
|
|
||||||
Une fois la procédure d'initialisation terminée, les différentes parties de l'application devraient être disponibles aux adresses suivantes:
|
Une fois la procédure d'initialisation terminée, les différentes parties de l'application devraient être disponibles aux adresses suivantes:
|
||||||
|
@ -28,28 +28,32 @@ Une fois la procédure d'initialisation terminée, les différentes parties de l
|
||||||
|
|
||||||
Une fois l'application lancée, exécuter:
|
Une fois l'application lancée, exécuter:
|
||||||
|
|
||||||
```
|
```bash
|
||||||
docker-compose exec backend /bin/bash
|
make backend-shell # ou docker-compose exec backend /bin/bash
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Comment ouvrir un shell interactif dans le conteneur "frontend" ?
|
#### Comment ouvrir un shell interactif dans le conteneur "frontend" ?
|
||||||
|
|
||||||
Une fois l'application lancée, exécuter:
|
Une fois l'application lancée, exécuter:
|
||||||
|
|
||||||
```
|
```bash
|
||||||
docker-compose exec frontend /bin/bash
|
make frontend-shell # ou docker-compose exec frontend /bin/bash
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Comment ouvrir une console MySQL ?
|
#### Comment ouvrir une console MySQL ?
|
||||||
|
|
||||||
Une fois l'application lancée, exécuter:
|
Une fois l'application lancée, exécuter:
|
||||||
|
|
||||||
```
|
```bash
|
||||||
docker-compose exec database mysql -uroot -proot
|
make database-mysql # ou docker-compose exec database mysql -uroot -proot logo
|
||||||
```
|
```
|
||||||
|
|
||||||
### Comment réinitialiser l'environnement ?
|
### Comment réinitialiser l'environnement ?
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make down # ou docker-compose down -v
|
||||||
```
|
```
|
||||||
docker-compose down -v
|
|
||||||
```
|
## Cahier des charges
|
||||||
|
|
||||||
|
- [Application de suivi des demandes clients](./misc/projects/ticketing_app.md)
|
||||||
|
|
|
@ -27,3 +27,7 @@ APP_SECRET=bc3856916a3206bf0b23356f46c5cf7d
|
||||||
# IMPORTANT: You MUST configure your server version, either here or in config/packages/doctrine.yaml
|
# IMPORTANT: You MUST configure your server version, either here or in config/packages/doctrine.yaml
|
||||||
DATABASE_URL=mysql://db_user:db_password@127.0.0.1:3306/db_name?serverVersion=5.7
|
DATABASE_URL=mysql://db_user:db_password@127.0.0.1:3306/db_name?serverVersion=5.7
|
||||||
###< doctrine/doctrine-bundle ###
|
###< doctrine/doctrine-bundle ###
|
||||||
|
|
||||||
|
###> nelmio/cors-bundle ###
|
||||||
|
CORS_ALLOW_ORIGIN=^https?://(localhost|127\.0\.0\.1)(:[0-9]+)?$
|
||||||
|
###< nelmio/cors-bundle ###
|
||||||
|
|
|
@ -5,15 +5,19 @@
|
||||||
"php": "^7.1.3",
|
"php": "^7.1.3",
|
||||||
"ext-ctype": "*",
|
"ext-ctype": "*",
|
||||||
"ext-iconv": "*",
|
"ext-iconv": "*",
|
||||||
|
"nelmio/cors-bundle": "^2.0",
|
||||||
|
"sensio/framework-extra-bundle": "^5.5",
|
||||||
"symfony/console": "4.4.*",
|
"symfony/console": "4.4.*",
|
||||||
"symfony/dotenv": "4.4.*",
|
"symfony/dotenv": "4.4.*",
|
||||||
"symfony/flex": "^1.3.1",
|
"symfony/flex": "^1.3.1",
|
||||||
"symfony/framework-bundle": "4.4.*",
|
"symfony/framework-bundle": "4.4.*",
|
||||||
|
"symfony/inflector": "4.4.*",
|
||||||
"symfony/orm-pack": "^1.0",
|
"symfony/orm-pack": "^1.0",
|
||||||
"symfony/security-bundle": "4.4.*",
|
"symfony/security-bundle": "4.4.*",
|
||||||
"symfony/yaml": "4.4.*"
|
"symfony/yaml": "4.4.*"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
|
"doctrine/doctrine-fixtures-bundle": "^3.3",
|
||||||
"symfony/maker-bundle": "^1.14",
|
"symfony/maker-bundle": "^1.14",
|
||||||
"symfony/web-server-bundle": "4.4.*"
|
"symfony/web-server-bundle": "4.4.*"
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "6421c1affbb7f7322a6499bde9164df1",
|
"content-hash": "b8a24c5421258bb04d461c7dd0498820",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "doctrine/annotations",
|
"name": "doctrine/annotations",
|
||||||
|
@ -1369,6 +1369,63 @@
|
||||||
],
|
],
|
||||||
"time": "2020-01-07T22:58:31+00:00"
|
"time": "2020-01-07T22:58:31+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "nelmio/cors-bundle",
|
||||||
|
"version": "2.0.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/nelmio/NelmioCorsBundle.git",
|
||||||
|
"reference": "9683e6d30d000ef998919261329d825de7c53499"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/nelmio/NelmioCorsBundle/zipball/9683e6d30d000ef998919261329d825de7c53499",
|
||||||
|
"reference": "9683e6d30d000ef998919261329d825de7c53499",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"symfony/framework-bundle": "^4.3 || ^5.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"mockery/mockery": "^1.2",
|
||||||
|
"symfony/phpunit-bridge": "^4.3 || ^5.0"
|
||||||
|
},
|
||||||
|
"type": "symfony-bundle",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "2.0.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Nelmio\\CorsBundle\\": ""
|
||||||
|
},
|
||||||
|
"exclude-from-classmap": [
|
||||||
|
"/Tests/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Nelmio",
|
||||||
|
"homepage": "http://nelm.io"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://github.com/nelmio/NelmioCorsBundle/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Adds CORS (Cross-Origin Resource Sharing) headers support in your Symfony application",
|
||||||
|
"keywords": [
|
||||||
|
"api",
|
||||||
|
"cors",
|
||||||
|
"crossdomain"
|
||||||
|
],
|
||||||
|
"time": "2019-11-15T08:54:08+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "ocramius/package-versions",
|
"name": "ocramius/package-versions",
|
||||||
"version": "1.5.1",
|
"version": "1.5.1",
|
||||||
|
@ -1639,6 +1696,84 @@
|
||||||
],
|
],
|
||||||
"time": "2019-11-01T11:05:21+00:00"
|
"time": "2019-11-01T11:05:21+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "sensio/framework-extra-bundle",
|
||||||
|
"version": "v5.5.3",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/sensiolabs/SensioFrameworkExtraBundle.git",
|
||||||
|
"reference": "98f0807137b13d0acfdf3c255a731516e97015de"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/sensiolabs/SensioFrameworkExtraBundle/zipball/98f0807137b13d0acfdf3c255a731516e97015de",
|
||||||
|
"reference": "98f0807137b13d0acfdf3c255a731516e97015de",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"doctrine/annotations": "^1.0",
|
||||||
|
"php": ">=7.1.3",
|
||||||
|
"symfony/config": "^4.3|^5.0",
|
||||||
|
"symfony/dependency-injection": "^4.3|^5.0",
|
||||||
|
"symfony/framework-bundle": "^4.3|^5.0",
|
||||||
|
"symfony/http-kernel": "^4.3|^5.0"
|
||||||
|
},
|
||||||
|
"conflict": {
|
||||||
|
"doctrine/doctrine-cache-bundle": "<1.3.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"doctrine/doctrine-bundle": "^1.11|^2.0",
|
||||||
|
"doctrine/orm": "^2.5",
|
||||||
|
"nyholm/psr7": "^1.1",
|
||||||
|
"symfony/browser-kit": "^4.3|^5.0",
|
||||||
|
"symfony/dom-crawler": "^4.3|^5.0",
|
||||||
|
"symfony/expression-language": "^4.3|^5.0",
|
||||||
|
"symfony/finder": "^4.3|^5.0",
|
||||||
|
"symfony/monolog-bridge": "^4.0|^5.0",
|
||||||
|
"symfony/monolog-bundle": "^3.2",
|
||||||
|
"symfony/phpunit-bridge": "^4.3.5|^5.0",
|
||||||
|
"symfony/psr-http-message-bridge": "^1.1",
|
||||||
|
"symfony/security-bundle": "^4.3|^5.0",
|
||||||
|
"symfony/twig-bundle": "^4.3|^5.0",
|
||||||
|
"symfony/yaml": "^4.3|^5.0",
|
||||||
|
"twig/twig": "^1.34|^2.4|^3.0"
|
||||||
|
},
|
||||||
|
"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.5.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Sensio\\Bundle\\FrameworkExtraBundle\\": "src/"
|
||||||
|
},
|
||||||
|
"exclude-from-classmap": [
|
||||||
|
"/tests/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"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": "2019-12-27T08:57:19+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/cache",
|
"name": "symfony/cache",
|
||||||
"version": "v4.4.4",
|
"version": "v4.4.4",
|
||||||
|
@ -4017,6 +4152,137 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"packages-dev": [
|
"packages-dev": [
|
||||||
|
{
|
||||||
|
"name": "doctrine/data-fixtures",
|
||||||
|
"version": "1.4.2",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/doctrine/data-fixtures.git",
|
||||||
|
"reference": "39e9777c9089351a468f780b01cffa3cb0a42907"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/doctrine/data-fixtures/zipball/39e9777c9089351a468f780b01cffa3cb0a42907",
|
||||||
|
"reference": "39e9777c9089351a468f780b01cffa3cb0a42907",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"doctrine/common": "^2.11",
|
||||||
|
"doctrine/persistence": "^1.3.3",
|
||||||
|
"php": "^7.2"
|
||||||
|
},
|
||||||
|
"conflict": {
|
||||||
|
"doctrine/phpcr-odm": "<1.3.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"alcaeus/mongo-php-adapter": "^1.1",
|
||||||
|
"doctrine/coding-standard": "^6.0",
|
||||||
|
"doctrine/dbal": "^2.5.4",
|
||||||
|
"doctrine/mongodb-odm": "^1.3.0",
|
||||||
|
"doctrine/orm": "^2.7.0",
|
||||||
|
"phpunit/phpunit": "^7.0"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"alcaeus/mongo-php-adapter": "For using MongoDB ODM with PHP 7",
|
||||||
|
"doctrine/mongodb-odm": "For loading MongoDB ODM fixtures",
|
||||||
|
"doctrine/orm": "For loading ORM fixtures",
|
||||||
|
"doctrine/phpcr-odm": "For loading PHPCR ODM fixtures"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "1.4.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Doctrine\\Common\\DataFixtures\\": "lib/Doctrine/Common/DataFixtures"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Jonathan Wage",
|
||||||
|
"email": "jonwage@gmail.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Data Fixtures for all Doctrine Object Managers",
|
||||||
|
"homepage": "http://www.doctrine-project.org",
|
||||||
|
"keywords": [
|
||||||
|
"database"
|
||||||
|
],
|
||||||
|
"time": "2020-01-17T11:11:28+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "doctrine/doctrine-fixtures-bundle",
|
||||||
|
"version": "3.3.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/doctrine/DoctrineFixturesBundle.git",
|
||||||
|
"reference": "8f07fcfdac7f3591f3c4bf13a50cbae05f65ed70"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/doctrine/DoctrineFixturesBundle/zipball/8f07fcfdac7f3591f3c4bf13a50cbae05f65ed70",
|
||||||
|
"reference": "8f07fcfdac7f3591f3c4bf13a50cbae05f65ed70",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"doctrine/data-fixtures": "^1.3",
|
||||||
|
"doctrine/doctrine-bundle": "^1.11|^2.0",
|
||||||
|
"doctrine/orm": "^2.6.0",
|
||||||
|
"php": "^7.1",
|
||||||
|
"symfony/config": "^3.4|^4.3|^5.0",
|
||||||
|
"symfony/console": "^3.4|^4.3|^5.0",
|
||||||
|
"symfony/dependency-injection": "^3.4|^4.3|^5.0",
|
||||||
|
"symfony/doctrine-bridge": "^3.4|^4.1|^5.0",
|
||||||
|
"symfony/http-kernel": "^3.4|^4.3|^5.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"doctrine/coding-standard": "^6.0",
|
||||||
|
"phpunit/phpunit": "^7.4",
|
||||||
|
"symfony/phpunit-bridge": "^4.1|^5.0"
|
||||||
|
},
|
||||||
|
"type": "symfony-bundle",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "3.3.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Doctrine\\Bundle\\FixturesBundle\\": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Fabien Potencier",
|
||||||
|
"email": "fabien@symfony.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Doctrine Project",
|
||||||
|
"homepage": "http://www.doctrine-project.org"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "http://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Symfony DoctrineFixturesBundle",
|
||||||
|
"homepage": "http://www.doctrine-project.org",
|
||||||
|
"keywords": [
|
||||||
|
"Fixture",
|
||||||
|
"persistence"
|
||||||
|
],
|
||||||
|
"time": "2019-11-13T15:46:58+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "nikic/php-parser",
|
"name": "nikic/php-parser",
|
||||||
"version": "v4.3.0",
|
"version": "v4.3.0",
|
||||||
|
|
|
@ -7,4 +7,7 @@ return [
|
||||||
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
|
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
|
||||||
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
|
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
|
||||||
Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
|
Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
|
||||||
|
Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle::class => ['dev' => true, 'test' => true],
|
||||||
|
Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class => ['all' => true],
|
||||||
|
Nelmio\CorsBundle\NelmioCorsBundle::class => ['all' => true],
|
||||||
];
|
];
|
||||||
|
|
|
@ -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:
|
||||||
|
'^/': null
|
|
@ -1,13 +1,26 @@
|
||||||
security:
|
security:
|
||||||
|
encoders:
|
||||||
|
App\Entity\User:
|
||||||
|
algorithm: auto
|
||||||
|
|
||||||
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
|
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
|
||||||
providers:
|
providers:
|
||||||
in_memory: { memory: null }
|
# used to reload user from session & other features (e.g. switch_user)
|
||||||
|
app_user_provider:
|
||||||
|
entity:
|
||||||
|
class: App\Entity\User
|
||||||
|
property: username
|
||||||
firewalls:
|
firewalls:
|
||||||
dev:
|
dev:
|
||||||
pattern: ^/(_(profiler|wdt)|css|images|js)/
|
pattern: ^/(_(profiler|wdt)|css|images|js)/
|
||||||
security: false
|
security: false
|
||||||
main:
|
main:
|
||||||
anonymous: lazy
|
anonymous: ~
|
||||||
|
json_login:
|
||||||
|
check_path: /api/v1/login
|
||||||
|
logout:
|
||||||
|
path: /api/v1/logout
|
||||||
|
target: /api/v1
|
||||||
|
|
||||||
# activate different ways to authenticate
|
# activate different ways to authenticate
|
||||||
# https://symfony.com/doc/current/security.html#firewalls-authentication
|
# https://symfony.com/doc/current/security.html#firewalls-authentication
|
||||||
|
@ -18,5 +31,3 @@ security:
|
||||||
# Easy way to control access for large sections of your site
|
# Easy way to control access for large sections of your site
|
||||||
# Note: Only the *first* access control that matches will be used
|
# Note: Only the *first* access control that matches will be used
|
||||||
access_control:
|
access_control:
|
||||||
# - { path: ^/admin, roles: ROLE_ADMIN }
|
|
||||||
# - { path: ^/profile, roles: ROLE_USER }
|
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
sensio_framework_extra:
|
||||||
|
router:
|
||||||
|
annotations: false
|
|
@ -2,29 +2,9 @@
|
||||||
# path: /
|
# path: /
|
||||||
# controller: App\Controller\DefaultController::index
|
# controller: App\Controller\DefaultController::index
|
||||||
|
|
||||||
app_home:
|
login:
|
||||||
path: /
|
|
||||||
controller: Symfony\Bundle\FrameworkBundle\Controller\RedirectController::urlRedirectAction
|
|
||||||
defaults:
|
|
||||||
path: /api/v1
|
|
||||||
permanent: true
|
|
||||||
|
|
||||||
app_api_login:
|
|
||||||
path: /api/v1/login
|
path: /api/v1/login
|
||||||
methods: ['POST']
|
controller: App\Controller\SecurityController::login
|
||||||
controller: App\Controller\ApiController::login
|
|
||||||
|
|
||||||
app_api_logout:
|
logout:
|
||||||
path: /api/v1/logout
|
path: /api/v1/logout
|
||||||
methods: ['POST']
|
|
||||||
controller: App\Controller\ApiController::logout
|
|
||||||
|
|
||||||
app_api_show_info:
|
|
||||||
path: /api/v1
|
|
||||||
methods: ['GET']
|
|
||||||
controller: App\Controller\ApiController::showVersionInfo
|
|
||||||
|
|
||||||
app_api_list_users:
|
|
||||||
path: /api/v1/users
|
|
||||||
methods: ['GET']
|
|
||||||
controller: App\Controller\ApiController::listUsers
|
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
<?php
|
|
||||||
namespace App\Controller;
|
|
||||||
|
|
||||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
|
||||||
|
|
||||||
class ApiController
|
|
||||||
{
|
|
||||||
public function login()
|
|
||||||
{
|
|
||||||
return new JsonResponse([
|
|
||||||
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function logout()
|
|
||||||
{
|
|
||||||
return new JsonResponse([
|
|
||||||
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function showVersionInfo()
|
|
||||||
{
|
|
||||||
return new JsonResponse([
|
|
||||||
'version' => '1',
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function listUsers()
|
|
||||||
{
|
|
||||||
return new JsonResponse([]);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
<?php
|
||||||
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use App\Http\DataResponse;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
|
||||||
|
class IndexController
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/api/v1", name="api_v1_index")
|
||||||
|
*/
|
||||||
|
public function showAPIV1Index()
|
||||||
|
{
|
||||||
|
return new DataResponse([
|
||||||
|
'version' => '1',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use App\Http\DataResponse;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
|
||||||
|
class RequestController
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/api/v1/request", name="api_v1_list_requests")
|
||||||
|
*/
|
||||||
|
public function listRequests()
|
||||||
|
{
|
||||||
|
return new DataResponse([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
<?php
|
||||||
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use App\Http\DataResponse;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
|
||||||
|
class SecurityController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @Route("/api/v1/login", name="api_v1_login", methods={"POST"})
|
||||||
|
*/
|
||||||
|
public function login(Request $request)
|
||||||
|
{
|
||||||
|
$user = $this->getUser();
|
||||||
|
|
||||||
|
return new DataResponse([
|
||||||
|
'username' => $user->getUsername(),
|
||||||
|
'roles' => $user->getRoles(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
<?php
|
||||||
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use App\Entity\User;
|
||||||
|
use App\Http\DataResponse;
|
||||||
|
use App\Repository\UserRepository;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
|
||||||
|
|
||||||
|
class UserController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @Route("/api/v1/me", name="api_v1_users_me", methods={"GET"})
|
||||||
|
* @IsGranted("ROLE_USER")
|
||||||
|
*/
|
||||||
|
public function showCurrentUser()
|
||||||
|
{
|
||||||
|
/** @var User */
|
||||||
|
$user = $this->getUser();
|
||||||
|
|
||||||
|
return new DataResponse([
|
||||||
|
'username' => $user->getUsername(),
|
||||||
|
'roles' => $user->getRoles(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/api/v1/users", name="api_v1_list_users", methods={"GET"})
|
||||||
|
* @IsGranted("ROLE_DEVELOPER")
|
||||||
|
*/
|
||||||
|
public function listUsers()
|
||||||
|
{
|
||||||
|
/** @var array */
|
||||||
|
$users = $this->getDoctrine()
|
||||||
|
->getRepository(User::class)
|
||||||
|
->findAll()
|
||||||
|
;
|
||||||
|
|
||||||
|
$results = [];
|
||||||
|
foreach($users as $u) {
|
||||||
|
$results[] = [
|
||||||
|
'id' => $u->getId(),
|
||||||
|
'username' => $u->getUsername(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return new DataResponse([
|
||||||
|
'users' => $results,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/api/v1/users/{userId}", name="api_v1_get_user", methods={"GET"}, requirements={"userId"="\d+"})
|
||||||
|
* @IsGranted("ROLE_DEVELOPER")
|
||||||
|
*/
|
||||||
|
public function showUser($userId)
|
||||||
|
{
|
||||||
|
/** @var User */
|
||||||
|
$user = $this->getDoctrine()
|
||||||
|
->getRepository(User::class)
|
||||||
|
->find($userId)
|
||||||
|
;
|
||||||
|
|
||||||
|
return new DataResponse([
|
||||||
|
'user' => [
|
||||||
|
'id' => $user->getId(),
|
||||||
|
'username' => $user->getUsername(),
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\DataFixtures;
|
||||||
|
|
||||||
|
use Doctrine\Bundle\FixturesBundle\Fixture;
|
||||||
|
use Doctrine\Common\Persistence\ObjectManager;
|
||||||
|
|
||||||
|
class AppFixtures extends Fixture
|
||||||
|
{
|
||||||
|
public function load(ObjectManager $manager)
|
||||||
|
{
|
||||||
|
// $product = new Product();
|
||||||
|
// $manager->persist($product);
|
||||||
|
|
||||||
|
$manager->flush();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\DataFixtures;
|
||||||
|
|
||||||
|
use App\Entity\RequestStatus;
|
||||||
|
use Doctrine\Bundle\FixturesBundle\Fixture;
|
||||||
|
use Doctrine\Common\Persistence\ObjectManager;
|
||||||
|
|
||||||
|
class RequestStatusFixtures extends Fixture
|
||||||
|
{
|
||||||
|
public function load(ObjectManager $manager)
|
||||||
|
{
|
||||||
|
$statuses = [
|
||||||
|
'En attente',
|
||||||
|
'Pris en compte',
|
||||||
|
'En cours de traitement',
|
||||||
|
'Traité',
|
||||||
|
'Clos',
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach($statuses as $statusLabel) {
|
||||||
|
$status = new RequestStatus();
|
||||||
|
$status->setLabel($statusLabel);
|
||||||
|
$manager->persist($status);
|
||||||
|
}
|
||||||
|
|
||||||
|
$manager->flush();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\DataFixtures;
|
||||||
|
|
||||||
|
use App\Entity\User;
|
||||||
|
use Doctrine\Bundle\FixturesBundle\Fixture;
|
||||||
|
use Doctrine\Common\Persistence\ObjectManager;
|
||||||
|
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
|
||||||
|
|
||||||
|
class UserFixtures extends Fixture
|
||||||
|
{
|
||||||
|
private $passwordEncoder;
|
||||||
|
|
||||||
|
public function __construct(UserPasswordEncoderInterface $passwordEncoder)
|
||||||
|
{
|
||||||
|
$this->passwordEncoder = $passwordEncoder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function load(ObjectManager $manager)
|
||||||
|
{
|
||||||
|
// On créait l'utilisateur client1 et client2
|
||||||
|
$client1 = new User();
|
||||||
|
$client1->setUsername('client1');
|
||||||
|
$client1->setPassword($this->passwordEncoder->encodePassword(
|
||||||
|
$client1,
|
||||||
|
'client1'
|
||||||
|
));
|
||||||
|
$client1->setRoles(['ROLE_CLIENT']);
|
||||||
|
$manager->persist($client1);
|
||||||
|
|
||||||
|
$client2 = new User();
|
||||||
|
$client2->setUsername('client2');
|
||||||
|
$client2->setPassword($this->passwordEncoder->encodePassword(
|
||||||
|
$client2,
|
||||||
|
'client2'
|
||||||
|
));
|
||||||
|
$client2->setRoles(['ROLE_CLIENT']);
|
||||||
|
$manager->persist($client2);
|
||||||
|
|
||||||
|
// On créait l'utilisateur dev1
|
||||||
|
$dev1 = new User();
|
||||||
|
$dev1->setUsername('dev1');
|
||||||
|
$dev1->setPassword($this->passwordEncoder->encodePassword(
|
||||||
|
$dev1,
|
||||||
|
'dev1'
|
||||||
|
));
|
||||||
|
$dev1->setRoles(['ROLE_DEVELOPER']);
|
||||||
|
$manager->persist($dev1);
|
||||||
|
|
||||||
|
$manager->flush();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,93 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Entity(repositoryClass="App\Repository\CommentRepository")
|
||||||
|
*/
|
||||||
|
class Comment
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @ORM\Id()
|
||||||
|
* @ORM\GeneratedValue()
|
||||||
|
* @ORM\Column(type="integer")
|
||||||
|
*/
|
||||||
|
private $id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\ManyToOne(targetEntity="App\Entity\Request", inversedBy="comments")
|
||||||
|
* @ORM\JoinColumn(nullable=false)
|
||||||
|
*/
|
||||||
|
private $request;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="datetime")
|
||||||
|
*/
|
||||||
|
private $createdAt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="comments")
|
||||||
|
* @ORM\JoinColumn(nullable=false)
|
||||||
|
*/
|
||||||
|
private $author;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="text")
|
||||||
|
*/
|
||||||
|
private $text;
|
||||||
|
|
||||||
|
public function getId(): ?int
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRequest(): ?Request
|
||||||
|
{
|
||||||
|
return $this->request;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setRequest(?Request $request): self
|
||||||
|
{
|
||||||
|
$this->request = $request;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCreatedAt(): ?\DateTimeInterface
|
||||||
|
{
|
||||||
|
return $this->createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCreatedAt(\DateTimeInterface $createdAt): self
|
||||||
|
{
|
||||||
|
$this->createdAt = $createdAt;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAuthor(): ?User
|
||||||
|
{
|
||||||
|
return $this->author;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setAuthor(?User $author): self
|
||||||
|
{
|
||||||
|
$this->author = $author;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getText(): ?string
|
||||||
|
{
|
||||||
|
return $this->text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setText(string $text): self
|
||||||
|
{
|
||||||
|
$this->text = $text;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,115 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
|
use Doctrine\Common\Collections\Collection;
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Entity(repositoryClass="App\Repository\ProjectRepository")
|
||||||
|
*/
|
||||||
|
class Project
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @ORM\Id()
|
||||||
|
* @ORM\GeneratedValue()
|
||||||
|
* @ORM\Column(type="integer")
|
||||||
|
*/
|
||||||
|
private $id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\OneToMany(targetEntity="App\Entity\Request", mappedBy="project", orphanRemoval=true)
|
||||||
|
*/
|
||||||
|
private $request;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\ManyToMany(targetEntity="App\Entity\User", inversedBy="projects")
|
||||||
|
*/
|
||||||
|
private $users;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="string", length=255)
|
||||||
|
*/
|
||||||
|
private $name;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->request = new ArrayCollection();
|
||||||
|
$this->users = new ArrayCollection();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getId(): ?int
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection|Request[]
|
||||||
|
*/
|
||||||
|
public function getRequest(): Collection
|
||||||
|
{
|
||||||
|
return $this->request;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addRequest(Request $request): self
|
||||||
|
{
|
||||||
|
if (!$this->request->contains($request)) {
|
||||||
|
$this->request[] = $request;
|
||||||
|
$request->setProject($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeRequest(Request $request): self
|
||||||
|
{
|
||||||
|
if ($this->request->contains($request)) {
|
||||||
|
$this->request->removeElement($request);
|
||||||
|
// set the owning side to null (unless already changed)
|
||||||
|
if ($request->getProject() === $this) {
|
||||||
|
$request->setProject(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection|User[]
|
||||||
|
*/
|
||||||
|
public function getUsers(): Collection
|
||||||
|
{
|
||||||
|
return $this->users;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addUser(User $user): self
|
||||||
|
{
|
||||||
|
if (!$this->users->contains($user)) {
|
||||||
|
$this->users[] = $user;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeUser(User $user): self
|
||||||
|
{
|
||||||
|
if ($this->users->contains($user)) {
|
||||||
|
$this->users->removeElement($user);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName(): ?string
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setName(string $name): self
|
||||||
|
{
|
||||||
|
$this->name = $name;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,154 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
|
use Doctrine\Common\Collections\Collection;
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Entity(repositoryClass="App\Repository\RequestRepository")
|
||||||
|
*/
|
||||||
|
class Request
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @ORM\Id()
|
||||||
|
* @ORM\GeneratedValue()
|
||||||
|
* @ORM\Column(type="integer")
|
||||||
|
*/
|
||||||
|
private $id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="string", length=255)
|
||||||
|
*/
|
||||||
|
private $title;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="requests")
|
||||||
|
* @ORM\JoinColumn(nullable=false)
|
||||||
|
*/
|
||||||
|
private $author;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="datetime")
|
||||||
|
*/
|
||||||
|
private $createdAt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\ManyToOne(targetEntity="App\Entity\Project", inversedBy="request")
|
||||||
|
* @ORM\JoinColumn(nullable=false)
|
||||||
|
*/
|
||||||
|
private $project;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\OneToMany(targetEntity="App\Entity\Comment", mappedBy="request", orphanRemoval=true)
|
||||||
|
*/
|
||||||
|
private $comments;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\ManyToOne(targetEntity="App\Entity\RequestStatus", inversedBy="requests")
|
||||||
|
* @ORM\JoinColumn(nullable=true)
|
||||||
|
*/
|
||||||
|
private $status;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->comments = new ArrayCollection();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getId(): ?int
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTitle(): ?string
|
||||||
|
{
|
||||||
|
return $this->title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setTitle(string $title): self
|
||||||
|
{
|
||||||
|
$this->title = $title;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAuthor(): ?User
|
||||||
|
{
|
||||||
|
return $this->author;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setAuthor(?User $author): self
|
||||||
|
{
|
||||||
|
$this->author = $author;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCreatedAt(): ?\DateTimeInterface
|
||||||
|
{
|
||||||
|
return $this->createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCreatedAt(\DateTimeInterface $createdAt): self
|
||||||
|
{
|
||||||
|
$this->createdAt = $createdAt;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getProject(): ?Project
|
||||||
|
{
|
||||||
|
return $this->project;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setProject(?Project $project): self
|
||||||
|
{
|
||||||
|
$this->project = $project;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection|Comment[]
|
||||||
|
*/
|
||||||
|
public function getComments(): Collection
|
||||||
|
{
|
||||||
|
return $this->comments;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addComment(Comment $comment): self
|
||||||
|
{
|
||||||
|
if (!$this->comments->contains($comment)) {
|
||||||
|
$this->comments[] = $comment;
|
||||||
|
$comment->setRequest($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeComment(Comment $comment): self
|
||||||
|
{
|
||||||
|
if ($this->comments->contains($comment)) {
|
||||||
|
$this->comments->removeElement($comment);
|
||||||
|
// set the owning side to null (unless already changed)
|
||||||
|
if ($comment->getRequest() === $this) {
|
||||||
|
$comment->setRequest(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getStatus(): ?RequestStatus
|
||||||
|
{
|
||||||
|
return $this->status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setStatus(?RequestStatus $status): self
|
||||||
|
{
|
||||||
|
$this->status = $status;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
|
use Doctrine\Common\Collections\Collection;
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Entity(repositoryClass="App\Repository\RequestStatusRepository")
|
||||||
|
*/
|
||||||
|
class RequestStatus
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @ORM\Id()
|
||||||
|
* @ORM\GeneratedValue()
|
||||||
|
* @ORM\Column(type="integer")
|
||||||
|
*/
|
||||||
|
private $id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="string", length=64)
|
||||||
|
*/
|
||||||
|
private $label;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\OneToMany(targetEntity="App\Entity\Request", mappedBy="status")
|
||||||
|
*/
|
||||||
|
private $requests;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->requests = new ArrayCollection();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getId(): ?int
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLabel(): ?string
|
||||||
|
{
|
||||||
|
return $this->label;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setLabel(string $label): self
|
||||||
|
{
|
||||||
|
$this->label = $label;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection|Request[]
|
||||||
|
*/
|
||||||
|
public function getRequests(): Collection
|
||||||
|
{
|
||||||
|
return $this->requests;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addRequest(Request $request): self
|
||||||
|
{
|
||||||
|
if (!$this->requests->contains($request)) {
|
||||||
|
$this->requests[] = $request;
|
||||||
|
$request->setStatus($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeRequest(Request $request): self
|
||||||
|
{
|
||||||
|
if ($this->requests->contains($request)) {
|
||||||
|
$this->requests->removeElement($request);
|
||||||
|
// set the owning side to null (unless already changed)
|
||||||
|
if ($request->getStatus() === $this) {
|
||||||
|
$request->setStatus(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,222 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
|
use Doctrine\Common\Collections\Collection;
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
use Symfony\Component\Security\Core\User\UserInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Entity(repositoryClass="App\Repository\UserRepository")
|
||||||
|
*/
|
||||||
|
class User implements UserInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @ORM\Id()
|
||||||
|
* @ORM\GeneratedValue()
|
||||||
|
* @ORM\Column(type="integer")
|
||||||
|
*/
|
||||||
|
private $id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="string", length=180, unique=true)
|
||||||
|
*/
|
||||||
|
private $username;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="json")
|
||||||
|
*/
|
||||||
|
private $roles = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string The hashed password
|
||||||
|
* @ORM\Column(type="string")
|
||||||
|
*/
|
||||||
|
private $password;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\OneToMany(targetEntity="App\Entity\Request", mappedBy="author", orphanRemoval=true)
|
||||||
|
*/
|
||||||
|
private $requests;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\ManyToMany(targetEntity="App\Entity\Project", mappedBy="users")
|
||||||
|
*/
|
||||||
|
private $projects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\OneToMany(targetEntity="App\Entity\Comment", mappedBy="author", orphanRemoval=true)
|
||||||
|
*/
|
||||||
|
private $comments;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->requests = new ArrayCollection();
|
||||||
|
$this->projects = new ArrayCollection();
|
||||||
|
$this->comments = new ArrayCollection();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getId(): ?int
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A visual identifier that represents this user.
|
||||||
|
*
|
||||||
|
* @see UserInterface
|
||||||
|
*/
|
||||||
|
public function getUsername(): string
|
||||||
|
{
|
||||||
|
return (string) $this->username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setUsername(string $username): self
|
||||||
|
{
|
||||||
|
$this->username = $username;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see UserInterface
|
||||||
|
*/
|
||||||
|
public function getRoles(): array
|
||||||
|
{
|
||||||
|
$roles = $this->roles;
|
||||||
|
// guarantee every user at least has ROLE_USER
|
||||||
|
$roles[] = 'ROLE_USER';
|
||||||
|
|
||||||
|
return array_unique($roles);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setRoles(array $roles): self
|
||||||
|
{
|
||||||
|
$this->roles = $roles;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see UserInterface
|
||||||
|
*/
|
||||||
|
public function getPassword(): string
|
||||||
|
{
|
||||||
|
return (string) $this->password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setPassword(string $password): self
|
||||||
|
{
|
||||||
|
$this->password = $password;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see UserInterface
|
||||||
|
*/
|
||||||
|
public function getSalt()
|
||||||
|
{
|
||||||
|
// not needed when using the "bcrypt" algorithm in security.yaml
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see UserInterface
|
||||||
|
*/
|
||||||
|
public function eraseCredentials()
|
||||||
|
{
|
||||||
|
// If you store any temporary, sensitive data on the user, clear it here
|
||||||
|
// $this->plainPassword = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection|Request[]
|
||||||
|
*/
|
||||||
|
public function getRequests(): Collection
|
||||||
|
{
|
||||||
|
return $this->requests;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addRequest(Request $request): self
|
||||||
|
{
|
||||||
|
if (!$this->requests->contains($request)) {
|
||||||
|
$this->requests[] = $request;
|
||||||
|
$request->setAuthor($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeRequest(Request $request): self
|
||||||
|
{
|
||||||
|
if ($this->requests->contains($request)) {
|
||||||
|
$this->requests->removeElement($request);
|
||||||
|
// set the owning side to null (unless already changed)
|
||||||
|
if ($request->getAuthor() === $this) {
|
||||||
|
$request->setAuthor(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection|Project[]
|
||||||
|
*/
|
||||||
|
public function getProjects(): Collection
|
||||||
|
{
|
||||||
|
return $this->projects;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addProject(Project $project): self
|
||||||
|
{
|
||||||
|
if (!$this->projects->contains($project)) {
|
||||||
|
$this->projects[] = $project;
|
||||||
|
$project->addUser($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeProject(Project $project): self
|
||||||
|
{
|
||||||
|
if ($this->projects->contains($project)) {
|
||||||
|
$this->projects->removeElement($project);
|
||||||
|
$project->removeUser($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection|Comment[]
|
||||||
|
*/
|
||||||
|
public function getComments(): Collection
|
||||||
|
{
|
||||||
|
return $this->comments;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addComment(Comment $comment): self
|
||||||
|
{
|
||||||
|
if (!$this->comments->contains($comment)) {
|
||||||
|
$this->comments[] = $comment;
|
||||||
|
$comment->setAuthor($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeComment(Comment $comment): self
|
||||||
|
{
|
||||||
|
if ($this->comments->contains($comment)) {
|
||||||
|
$this->comments->removeElement($comment);
|
||||||
|
// set the owning side to null (unless already changed)
|
||||||
|
if ($comment->getAuthor() === $this) {
|
||||||
|
$comment->setAuthor(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
|
||||||
|
class DataResponse extends JsonResponse {
|
||||||
|
public function __construct($data = null, $status = 200, $headers = [], $json = false) {
|
||||||
|
parent::__construct(
|
||||||
|
['data' => $data],
|
||||||
|
$status,
|
||||||
|
$headers,
|
||||||
|
$json,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
|
||||||
|
class ErrorResponse extends JsonResponse {
|
||||||
|
public function __construct($code, $message, $data = null, $status = 400, $headers = [], $json = false) {
|
||||||
|
parent::__construct(
|
||||||
|
[
|
||||||
|
'error' => [
|
||||||
|
'code' => $code,
|
||||||
|
'message' => $message,
|
||||||
|
'data' => $data,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
$status,
|
||||||
|
$headers,
|
||||||
|
$json,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace DoctrineMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-generated Migration: Please modify to your needs!
|
||||||
|
*/
|
||||||
|
final class Version20200217203938 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription() : string
|
||||||
|
{
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema) : void
|
||||||
|
{
|
||||||
|
// this up() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
|
||||||
|
|
||||||
|
$this->addSql('CREATE TABLE user (id INT AUTO_INCREMENT NOT NULL, username VARCHAR(180) NOT NULL, roles LONGTEXT NOT NULL COMMENT \'(DC2Type:json)\', password VARCHAR(255) NOT NULL, UNIQUE INDEX UNIQ_8D93D649F85E0677 (username), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||||
|
$this->addSql('CREATE TABLE request_status (id INT AUTO_INCREMENT NOT NULL, label VARCHAR(64) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||||
|
$this->addSql('CREATE TABLE comment (id INT AUTO_INCREMENT NOT NULL, request_id INT NOT NULL, author_id INT NOT NULL, created_at DATETIME NOT NULL, text LONGTEXT NOT NULL, INDEX IDX_9474526C427EB8A5 (request_id), INDEX IDX_9474526CF675F31B (author_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||||
|
$this->addSql('CREATE TABLE project (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||||
|
$this->addSql('CREATE TABLE project_user (project_id INT NOT NULL, user_id INT NOT NULL, INDEX IDX_B4021E51166D1F9C (project_id), INDEX IDX_B4021E51A76ED395 (user_id), PRIMARY KEY(project_id, user_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||||
|
$this->addSql('CREATE TABLE request (id INT AUTO_INCREMENT NOT NULL, author_id INT NOT NULL, project_id INT NOT NULL, title VARCHAR(255) NOT NULL, created_at DATETIME NOT NULL, INDEX IDX_3B978F9FF675F31B (author_id), INDEX IDX_3B978F9F166D1F9C (project_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||||
|
$this->addSql('ALTER TABLE comment ADD CONSTRAINT FK_9474526C427EB8A5 FOREIGN KEY (request_id) REFERENCES request (id)');
|
||||||
|
$this->addSql('ALTER TABLE comment ADD CONSTRAINT FK_9474526CF675F31B FOREIGN KEY (author_id) REFERENCES user (id)');
|
||||||
|
$this->addSql('ALTER TABLE project_user ADD CONSTRAINT FK_B4021E51166D1F9C FOREIGN KEY (project_id) REFERENCES project (id) ON DELETE CASCADE');
|
||||||
|
$this->addSql('ALTER TABLE project_user ADD CONSTRAINT FK_B4021E51A76ED395 FOREIGN KEY (user_id) REFERENCES user (id) ON DELETE CASCADE');
|
||||||
|
$this->addSql('ALTER TABLE request ADD CONSTRAINT FK_3B978F9FF675F31B FOREIGN KEY (author_id) REFERENCES user (id)');
|
||||||
|
$this->addSql('ALTER TABLE request ADD CONSTRAINT FK_3B978F9F166D1F9C FOREIGN KEY (project_id) REFERENCES project (id)');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema) : void
|
||||||
|
{
|
||||||
|
// this down() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
|
||||||
|
|
||||||
|
$this->addSql('ALTER TABLE comment DROP FOREIGN KEY FK_9474526CF675F31B');
|
||||||
|
$this->addSql('ALTER TABLE project_user DROP FOREIGN KEY FK_B4021E51A76ED395');
|
||||||
|
$this->addSql('ALTER TABLE request DROP FOREIGN KEY FK_3B978F9FF675F31B');
|
||||||
|
$this->addSql('ALTER TABLE project_user DROP FOREIGN KEY FK_B4021E51166D1F9C');
|
||||||
|
$this->addSql('ALTER TABLE request DROP FOREIGN KEY FK_3B978F9F166D1F9C');
|
||||||
|
$this->addSql('ALTER TABLE comment DROP FOREIGN KEY FK_9474526C427EB8A5');
|
||||||
|
$this->addSql('DROP TABLE user');
|
||||||
|
$this->addSql('DROP TABLE request_status');
|
||||||
|
$this->addSql('DROP TABLE comment');
|
||||||
|
$this->addSql('DROP TABLE project');
|
||||||
|
$this->addSql('DROP TABLE project_user');
|
||||||
|
$this->addSql('DROP TABLE request');
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace DoctrineMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-generated Migration: Please modify to your needs!
|
||||||
|
*/
|
||||||
|
final class Version20200217211954 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription() : string
|
||||||
|
{
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema) : void
|
||||||
|
{
|
||||||
|
// this up() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
|
||||||
|
|
||||||
|
$this->addSql('ALTER TABLE request ADD status_id INT DEFAULT NULL');
|
||||||
|
$this->addSql('ALTER TABLE request ADD CONSTRAINT FK_3B978F9F6BF700BD FOREIGN KEY (status_id) REFERENCES request_status (id)');
|
||||||
|
$this->addSql('CREATE INDEX IDX_3B978F9F6BF700BD ON request (status_id)');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema) : void
|
||||||
|
{
|
||||||
|
// this down() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
|
||||||
|
|
||||||
|
$this->addSql('ALTER TABLE request DROP FOREIGN KEY FK_3B978F9F6BF700BD');
|
||||||
|
$this->addSql('DROP INDEX IDX_3B978F9F6BF700BD ON request');
|
||||||
|
$this->addSql('ALTER TABLE request DROP status_id');
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Repository;
|
||||||
|
|
||||||
|
use App\Entity\Comment;
|
||||||
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
|
use Doctrine\Common\Persistence\ManagerRegistry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @method Comment|null find($id, $lockMode = null, $lockVersion = null)
|
||||||
|
* @method Comment|null findOneBy(array $criteria, array $orderBy = null)
|
||||||
|
* @method Comment[] findAll()
|
||||||
|
* @method Comment[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
|
||||||
|
*/
|
||||||
|
class CommentRepository extends ServiceEntityRepository
|
||||||
|
{
|
||||||
|
public function __construct(ManagerRegistry $registry)
|
||||||
|
{
|
||||||
|
parent::__construct($registry, Comment::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * @return Comment[] Returns an array of Comment objects
|
||||||
|
// */
|
||||||
|
/*
|
||||||
|
public function findByExampleField($value)
|
||||||
|
{
|
||||||
|
return $this->createQueryBuilder('c')
|
||||||
|
->andWhere('c.exampleField = :val')
|
||||||
|
->setParameter('val', $value)
|
||||||
|
->orderBy('c.id', 'ASC')
|
||||||
|
->setMaxResults(10)
|
||||||
|
->getQuery()
|
||||||
|
->getResult()
|
||||||
|
;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
public function findOneBySomeField($value): ?Comment
|
||||||
|
{
|
||||||
|
return $this->createQueryBuilder('c')
|
||||||
|
->andWhere('c.exampleField = :val')
|
||||||
|
->setParameter('val', $value)
|
||||||
|
->getQuery()
|
||||||
|
->getOneOrNullResult()
|
||||||
|
;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Repository;
|
||||||
|
|
||||||
|
use App\Entity\Project;
|
||||||
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
|
use Doctrine\Common\Persistence\ManagerRegistry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @method Project|null find($id, $lockMode = null, $lockVersion = null)
|
||||||
|
* @method Project|null findOneBy(array $criteria, array $orderBy = null)
|
||||||
|
* @method Project[] findAll()
|
||||||
|
* @method Project[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
|
||||||
|
*/
|
||||||
|
class ProjectRepository extends ServiceEntityRepository
|
||||||
|
{
|
||||||
|
public function __construct(ManagerRegistry $registry)
|
||||||
|
{
|
||||||
|
parent::__construct($registry, Project::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * @return Project[] Returns an array of Project objects
|
||||||
|
// */
|
||||||
|
/*
|
||||||
|
public function findByExampleField($value)
|
||||||
|
{
|
||||||
|
return $this->createQueryBuilder('p')
|
||||||
|
->andWhere('p.exampleField = :val')
|
||||||
|
->setParameter('val', $value)
|
||||||
|
->orderBy('p.id', 'ASC')
|
||||||
|
->setMaxResults(10)
|
||||||
|
->getQuery()
|
||||||
|
->getResult()
|
||||||
|
;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
public function findOneBySomeField($value): ?Project
|
||||||
|
{
|
||||||
|
return $this->createQueryBuilder('p')
|
||||||
|
->andWhere('p.exampleField = :val')
|
||||||
|
->setParameter('val', $value)
|
||||||
|
->getQuery()
|
||||||
|
->getOneOrNullResult()
|
||||||
|
;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Repository;
|
||||||
|
|
||||||
|
use App\Entity\Request;
|
||||||
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
|
use Doctrine\Common\Persistence\ManagerRegistry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @method Request|null find($id, $lockMode = null, $lockVersion = null)
|
||||||
|
* @method Request|null findOneBy(array $criteria, array $orderBy = null)
|
||||||
|
* @method Request[] findAll()
|
||||||
|
* @method Request[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
|
||||||
|
*/
|
||||||
|
class RequestRepository extends ServiceEntityRepository
|
||||||
|
{
|
||||||
|
public function __construct(ManagerRegistry $registry)
|
||||||
|
{
|
||||||
|
parent::__construct($registry, Request::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * @return Request[] Returns an array of Request objects
|
||||||
|
// */
|
||||||
|
/*
|
||||||
|
public function findByExampleField($value)
|
||||||
|
{
|
||||||
|
return $this->createQueryBuilder('r')
|
||||||
|
->andWhere('r.exampleField = :val')
|
||||||
|
->setParameter('val', $value)
|
||||||
|
->orderBy('r.id', 'ASC')
|
||||||
|
->setMaxResults(10)
|
||||||
|
->getQuery()
|
||||||
|
->getResult()
|
||||||
|
;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
public function findOneBySomeField($value): ?Request
|
||||||
|
{
|
||||||
|
return $this->createQueryBuilder('r')
|
||||||
|
->andWhere('r.exampleField = :val')
|
||||||
|
->setParameter('val', $value)
|
||||||
|
->getQuery()
|
||||||
|
->getOneOrNullResult()
|
||||||
|
;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Repository;
|
||||||
|
|
||||||
|
use App\Entity\RequestStatus;
|
||||||
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
|
use Doctrine\Common\Persistence\ManagerRegistry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @method RequestStatus|null find($id, $lockMode = null, $lockVersion = null)
|
||||||
|
* @method RequestStatus|null findOneBy(array $criteria, array $orderBy = null)
|
||||||
|
* @method RequestStatus[] findAll()
|
||||||
|
* @method RequestStatus[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
|
||||||
|
*/
|
||||||
|
class RequestStatusRepository extends ServiceEntityRepository
|
||||||
|
{
|
||||||
|
public function __construct(ManagerRegistry $registry)
|
||||||
|
{
|
||||||
|
parent::__construct($registry, RequestStatus::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * @return RequestStatus[] Returns an array of RequestStatus objects
|
||||||
|
// */
|
||||||
|
/*
|
||||||
|
public function findByExampleField($value)
|
||||||
|
{
|
||||||
|
return $this->createQueryBuilder('r')
|
||||||
|
->andWhere('r.exampleField = :val')
|
||||||
|
->setParameter('val', $value)
|
||||||
|
->orderBy('r.id', 'ASC')
|
||||||
|
->setMaxResults(10)
|
||||||
|
->getQuery()
|
||||||
|
->getResult()
|
||||||
|
;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
public function findOneBySomeField($value): ?RequestStatus
|
||||||
|
{
|
||||||
|
return $this->createQueryBuilder('r')
|
||||||
|
->andWhere('r.exampleField = :val')
|
||||||
|
->setParameter('val', $value)
|
||||||
|
->getQuery()
|
||||||
|
->getOneOrNullResult()
|
||||||
|
;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Repository;
|
||||||
|
|
||||||
|
use App\Entity\User;
|
||||||
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
|
use Doctrine\Common\Persistence\ManagerRegistry;
|
||||||
|
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
|
||||||
|
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
|
||||||
|
use Symfony\Component\Security\Core\User\UserInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @method User|null find($id, $lockMode = null, $lockVersion = null)
|
||||||
|
* @method User|null findOneBy(array $criteria, array $orderBy = null)
|
||||||
|
* @method User[] findAll()
|
||||||
|
* @method User[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
|
||||||
|
*/
|
||||||
|
class UserRepository extends ServiceEntityRepository implements PasswordUpgraderInterface
|
||||||
|
{
|
||||||
|
public function __construct(ManagerRegistry $registry)
|
||||||
|
{
|
||||||
|
parent::__construct($registry, User::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to upgrade (rehash) the user's password automatically over time.
|
||||||
|
*/
|
||||||
|
public function upgradePassword(UserInterface $user, string $newEncodedPassword): void
|
||||||
|
{
|
||||||
|
if (!$user instanceof User) {
|
||||||
|
throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', \get_class($user)));
|
||||||
|
}
|
||||||
|
|
||||||
|
$user->setPassword($newEncodedPassword);
|
||||||
|
$this->_em->persist($user);
|
||||||
|
$this->_em->flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -20,6 +20,9 @@
|
||||||
"doctrine/common": {
|
"doctrine/common": {
|
||||||
"version": "2.12.0"
|
"version": "2.12.0"
|
||||||
},
|
},
|
||||||
|
"doctrine/data-fixtures": {
|
||||||
|
"version": "1.4.2"
|
||||||
|
},
|
||||||
"doctrine/dbal": {
|
"doctrine/dbal": {
|
||||||
"version": "v2.10.1"
|
"version": "v2.10.1"
|
||||||
},
|
},
|
||||||
|
@ -38,6 +41,18 @@
|
||||||
"src/Repository/.gitignore"
|
"src/Repository/.gitignore"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"doctrine/doctrine-fixtures-bundle": {
|
||||||
|
"version": "3.0",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "master",
|
||||||
|
"version": "3.0",
|
||||||
|
"ref": "fc52d86631a6dfd9fdf3381d0b7e3df2069e51b3"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"src/DataFixtures/AppFixtures.php"
|
||||||
|
]
|
||||||
|
},
|
||||||
"doctrine/doctrine-migrations-bundle": {
|
"doctrine/doctrine-migrations-bundle": {
|
||||||
"version": "1.2",
|
"version": "1.2",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
|
@ -87,6 +102,18 @@
|
||||||
"laminas/laminas-zendframework-bridge": {
|
"laminas/laminas-zendframework-bridge": {
|
||||||
"version": "1.0.1"
|
"version": "1.0.1"
|
||||||
},
|
},
|
||||||
|
"nelmio/cors-bundle": {
|
||||||
|
"version": "1.5",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "master",
|
||||||
|
"version": "1.5",
|
||||||
|
"ref": "6388de23860284db9acce0a7a5d9d13153bcb571"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"config/packages/nelmio_cors.yaml"
|
||||||
|
]
|
||||||
|
},
|
||||||
"nikic/php-parser": {
|
"nikic/php-parser": {
|
||||||
"version": "v4.3.0"
|
"version": "v4.3.0"
|
||||||
},
|
},
|
||||||
|
@ -108,6 +135,18 @@
|
||||||
"psr/log": {
|
"psr/log": {
|
||||||
"version": "1.1.2"
|
"version": "1.1.2"
|
||||||
},
|
},
|
||||||
|
"sensio/framework-extra-bundle": {
|
||||||
|
"version": "5.2",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "master",
|
||||||
|
"version": "5.2",
|
||||||
|
"ref": "fb7e19da7f013d0d422fa9bce16f5c510e27609b"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"config/packages/sensio_framework_extra.yaml"
|
||||||
|
]
|
||||||
|
},
|
||||||
"symfony/cache": {
|
"symfony/cache": {
|
||||||
"version": "v4.4.4"
|
"version": "v4.4.4"
|
||||||
},
|
},
|
||||||
|
|
|
@ -9,6 +9,9 @@ services:
|
||||||
MYSQL_PASSWORD: logo
|
MYSQL_PASSWORD: logo
|
||||||
ports:
|
ports:
|
||||||
- 3306:3306
|
- 3306:3306
|
||||||
|
command:
|
||||||
|
- "--character-set-server=utf8mb4"
|
||||||
|
- "--collation-server=utf8mb4_unicode_ci"
|
||||||
volumes:
|
volumes:
|
||||||
- db_data:/var/lib/mysql
|
- db_data:/var/lib/mysql
|
||||||
|
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
export const SEND_MESSAGE = 'SEND_MESSAGE'
|
|
||||||
export const SEND_MESSAGE_SUCCESS = 'SEND_MESSAGE_SUCCESS';
|
|
||||||
export const SEND_MESSAGE_FAILURE = 'SEND_MESSAGE_FAILURE';
|
|
||||||
|
|
||||||
export function sendMessage (channel, text) {
|
|
||||||
return { type: SEND_MESSAGE, channel, text }
|
|
||||||
}
|
|
||||||
|
|
||||||
export const FETCH_MESSAGES = 'FETCH_MESSAGES'
|
|
||||||
export const FETCH_MESSAGES_SUCCESS = 'FETCH_MESSAGES_SUCCESS';
|
|
||||||
export const FETCH_MESSAGES_FAILURE = 'FETCH_MESSAGES_FAILURE';
|
|
||||||
|
|
||||||
export function fetchMessages (channel) {
|
|
||||||
return { type: FETCH_MESSAGES, channel }
|
|
||||||
}
|
|
||||||
|
|
||||||
export const STREAM_EVENTS = 'STREAM_EVENTS'
|
|
||||||
export function streamEvents (channel) {
|
|
||||||
return { type: STREAM_EVENTS, channel }
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
export const LOGIN = 'LOGIN'
|
|
||||||
export const LOGIN_SUCCESS = 'LOGIN_SUCCESS';
|
|
||||||
export const LOGIN_FAILURE = 'LOGIN_FAILURE';
|
|
||||||
|
|
||||||
export function login (username, password) {
|
|
||||||
return { type: LOGIN, username, password }
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
export const ADD_PRODUCT = 'ADD_PRODUCT'
|
|
||||||
|
|
||||||
export function addProduct (name, price) {
|
|
||||||
return {type: ADD_PRODUCT, product: {name, price}}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const REMOVE_PRODUCT = 'REMOVE_PRODUCT'
|
|
||||||
|
|
||||||
export function removeProduct (name) {
|
|
||||||
return {type: REMOVE_PRODUCT, productName: name}
|
|
||||||
}
|
|
|
@ -1,12 +1,8 @@
|
||||||
|
|
||||||
import { Component, Fragment } from 'react'
|
import { Component, Fragment } from 'react'
|
||||||
import { hot } from 'react-hot-loader'
|
import { hot } from 'react-hot-loader'
|
||||||
import { HashRouter } from 'react-router-dom' // ou BrowserRouter
|
import { HashRouter } from 'react-router-dom' // ou BrowserRouter
|
||||||
import { Route, Switch, Redirect } from 'react-router'
|
import { Route, Switch, Redirect } from 'react-router'
|
||||||
import LoginPage from './pages/login';
|
import HomePage from './pages/home';
|
||||||
import ChatPage from './pages/chat';
|
|
||||||
|
|
||||||
require('bulma/css/bulma.min.css')
|
|
||||||
|
|
||||||
class App extends Component {
|
class App extends Component {
|
||||||
render () {
|
render () {
|
||||||
|
@ -14,10 +10,8 @@ class App extends Component {
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<HashRouter>
|
<HashRouter>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route path='/login' exact component={LoginPage} />
|
<Route path='/home' exact component={HomePage} />
|
||||||
<Route path='/chat/:channel' component={ChatPage} />
|
<Route component={() => <Redirect to="/home" />} />
|
||||||
<Route path='/chat' component={ChatPage} />
|
|
||||||
<Route component={() => <Redirect to="/login" />} />
|
|
||||||
</Switch>
|
</Switch>
|
||||||
</HashRouter>
|
</HashRouter>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`Counter snapshot 1`] = `
|
|
||||||
<div>
|
|
||||||
Count:
|
|
||||||
<span>
|
|
||||||
0
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<button
|
|
||||||
onClick={[Function]}
|
|
||||||
>
|
|
||||||
+1
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={[Function]}
|
|
||||||
>
|
|
||||||
-1
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
`;
|
|
|
@ -1,32 +0,0 @@
|
||||||
import React from 'react'
|
|
||||||
|
|
||||||
export default class Clock extends React.Component {
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props)
|
|
||||||
// Initialisation du "state" du composant
|
|
||||||
this.state = {
|
|
||||||
time: new Date(),
|
|
||||||
foo: "bar"
|
|
||||||
}
|
|
||||||
|
|
||||||
this.tick = this.tick.bind(this);
|
|
||||||
// On appelle la méthode tick() du composant
|
|
||||||
// toutes les secondes
|
|
||||||
setInterval(this.tick, this.props.interval);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Méthode de rendu du composant
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div>Time: { this.state.time.toString() }</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// La méthode tick() met à jour le state du composant avec
|
|
||||||
// la date courante
|
|
||||||
tick() {
|
|
||||||
this.setState({ time: new Date() });
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
import React from 'react'
|
|
||||||
|
|
||||||
export default class Counter extends React.Component {
|
|
||||||
constructor(props) {
|
|
||||||
super(props)
|
|
||||||
// Initialisation du "state" du composant
|
|
||||||
this.state = {
|
|
||||||
count: 0
|
|
||||||
}
|
|
||||||
// On "lie" les méthodes de la classe à l'instance
|
|
||||||
this.increment = this.increment.bind(this)
|
|
||||||
this.decrement = this.decrement.bind(this)
|
|
||||||
}
|
|
||||||
// Méthode de rendu du composant
|
|
||||||
render() {
|
|
||||||
console.log(this.props.match);
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
Count: <span>{ this.state.count }</span>
|
|
||||||
<button onClick={ this.increment }>+1</button>
|
|
||||||
<button onClick={ this.decrement }>-1</button>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
// La méthode increment() incrémente la valeur du compteur de 1
|
|
||||||
increment() {
|
|
||||||
this.setState(prevState => ({ count: prevState.count+1 }))
|
|
||||||
}
|
|
||||||
// La méthode decrement() décrémente la valeur du compteur de 1
|
|
||||||
decrement() {
|
|
||||||
this.setState(prevState => ({ count: prevState.count-1 }))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
/* globals test, expect */
|
|
||||||
import React from 'react';
|
|
||||||
import Counter from './counter'
|
|
||||||
import renderer from 'react-test-renderer'
|
|
||||||
|
|
||||||
test('Counter snapshot', () => {
|
|
||||||
|
|
||||||
const component = renderer.create(<Counter />)
|
|
||||||
|
|
||||||
let tree = component.toJSON()
|
|
||||||
|
|
||||||
// Vérifier que le composant n'a pas changé depuis le dernier
|
|
||||||
// snapshot.
|
|
||||||
// Voir https://facebook.github.io/jest/docs/en/snapshot-testing.html
|
|
||||||
// pour plus d'informations
|
|
||||||
expect(tree).toMatchSnapshot()
|
|
||||||
|
|
||||||
// L'API expect() de Jest est disponible à l'adresse
|
|
||||||
// https://facebook.github.io/jest/docs/en/expect.html
|
|
||||||
|
|
||||||
// Il est possible d'effectuer des vérifications plus avancées
|
|
||||||
// grâce au projet Enzyme (vérification du DOM, etc)
|
|
||||||
// Voir http://airbnb.io/enzyme/ et
|
|
||||||
// https://facebook.github.io/jest/docs/en/tutorial-react.html#dom-testing
|
|
||||||
|
|
||||||
})
|
|
|
@ -1,41 +0,0 @@
|
||||||
import React from 'react'
|
|
||||||
import { connect } from 'react-redux'
|
|
||||||
import { addProduct } from '../actions/products'
|
|
||||||
|
|
||||||
|
|
||||||
class MyForm extends React.Component {
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.state = {name: ''};
|
|
||||||
this.handleChange = this.handleChange.bind(this);
|
|
||||||
this.handleSubmit = this.handleSubmit.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleChange(evt) {
|
|
||||||
this.setState({ name: evt.target.value });
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSubmit(evt) {
|
|
||||||
console.log(`Votre nom est ${this.state.name}`);
|
|
||||||
evt.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.props.dispatch(addProduct('pomme', 10));
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<form onSubmit={this.handleSubmit}>
|
|
||||||
<label>
|
|
||||||
Nom:
|
|
||||||
<input type="text" value={this.state.name} onChange={this.handleChange} />
|
|
||||||
</label>
|
|
||||||
<input type="submit" value="Soumettre" />
|
|
||||||
</form>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect()(MyForm)
|
|
|
@ -2,7 +2,7 @@
|
||||||
<html>
|
<html>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>Insight</title>
|
<title>PleaseWait</title>
|
||||||
<% for (var css in htmlWebpackPlugin.files.css) { %>
|
<% for (var css in htmlWebpackPlugin.files.css) { %>
|
||||||
<link href="<%= htmlWebpackPlugin.files.css[css] %>" rel="stylesheet">
|
<link href="<%= htmlWebpackPlugin.files.css[css] %>" rel="stylesheet">
|
||||||
<% } %>
|
<% } %>
|
||||||
|
|
|
@ -2,6 +2,7 @@ import ReactDOM from 'react-dom'
|
||||||
import App from './app'
|
import App from './app'
|
||||||
import { configureStore } from './store/store'
|
import { configureStore } from './store/store'
|
||||||
import { Provider } from 'react-redux'
|
import { Provider } from 'react-redux'
|
||||||
|
import 'bulma/css/bulma.min.css';
|
||||||
|
|
||||||
const store = configureStore()
|
const store = configureStore()
|
||||||
|
|
||||||
|
|
|
@ -1,82 +0,0 @@
|
||||||
import React from 'react'
|
|
||||||
import Page from './page';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { fetchMessages, sendMessage, streamEvents } from '../actions/chat';
|
|
||||||
|
|
||||||
export class ChatPage extends React.Component {
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.state = {
|
|
||||||
message: ''
|
|
||||||
}
|
|
||||||
this.handleSendMessage = this.handleSendMessage.bind(this);
|
|
||||||
this.handleKeyDown = this.handleKeyDown.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const channel = "default";
|
|
||||||
const { chat } = this.props;
|
|
||||||
const messages = channel in chat.messagesByChannel ? chat.messagesByChannel[channel] : [];
|
|
||||||
const users = [];
|
|
||||||
return (
|
|
||||||
<Page>
|
|
||||||
<div className="columns">
|
|
||||||
<div className="column is-8 is-offset-2">
|
|
||||||
<div className="box">
|
|
||||||
<div className="tile">
|
|
||||||
<div className="tile is-parent is-vertical is-10">
|
|
||||||
<div className="tile is-child">
|
|
||||||
<b>Chat</b>
|
|
||||||
<ul>
|
|
||||||
{
|
|
||||||
messages.map(msg => {
|
|
||||||
return (
|
|
||||||
<li key={msg.ID}><span className="is-uppercase">[{msg.Username}]</span> <span>{msg.Text}</span></li>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
<div className="field">
|
|
||||||
<div className="control">
|
|
||||||
<input className="input" type="text" placeholder="Hello everyone !"
|
|
||||||
value={this.state.message}
|
|
||||||
onChange={this.handleSendMessage}
|
|
||||||
onKeyDown={this.handleKeyDown} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="tile is-parent is-2">
|
|
||||||
<b>Utilisateurs</b>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Page>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.props.dispatch(fetchMessages("default"));
|
|
||||||
this.props.dispatch(streamEvents("default"));
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSendMessage(evt) {
|
|
||||||
this.setState({ message: evt.target.value });
|
|
||||||
}
|
|
||||||
|
|
||||||
handleKeyDown(evt) {
|
|
||||||
if (evt.keyCode !== 13) return;
|
|
||||||
this.props.dispatch(sendMessage("default", this.state.message));
|
|
||||||
this.setState({message: ""});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(state => {
|
|
||||||
return {
|
|
||||||
chat: state.chat
|
|
||||||
}
|
|
||||||
})(ChatPage)
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
import React from 'react'
|
||||||
|
import Page from './page';
|
||||||
|
|
||||||
|
export default class HomePage extends React.PureComponent {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Page title="home">
|
||||||
|
<div className="section">
|
||||||
|
<h1 className="title">Bienvenue sur PleaseWait !</h1>
|
||||||
|
<h2 className="subtitle">Le gestionnaire de ticket simplifié.</h2>
|
||||||
|
</div>
|
||||||
|
</Page>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,53 +0,0 @@
|
||||||
import React from 'react'
|
|
||||||
import Page from './page';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { login } from '../actions/login';
|
|
||||||
|
|
||||||
export class LoginPage extends React.Component {
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.login = this.login.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<Page>
|
|
||||||
<div className="columns">
|
|
||||||
<div className="column is-4 is-offset-4">
|
|
||||||
<div className="box">
|
|
||||||
<div className="field">
|
|
||||||
<label className="label">Login</label>
|
|
||||||
<div className="control">
|
|
||||||
<input type="text" className="input" placeholder="My login..." />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="field">
|
|
||||||
<label className="label">Password</label>
|
|
||||||
<div className="control">
|
|
||||||
<input className="input" type="password" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button className="button is-primary" onClick={this.login}>Login</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Page>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate() {
|
|
||||||
if (this.props.user.isLoggedIn) this.props.history.push("/chat");
|
|
||||||
}
|
|
||||||
|
|
||||||
login() {
|
|
||||||
this.props.dispatch(login("foo", "bar"))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(state => {
|
|
||||||
return {
|
|
||||||
user: state.user
|
|
||||||
}
|
|
||||||
})(LoginPage)
|
|
|
@ -1,11 +1,13 @@
|
||||||
import React from 'react'
|
import React, { useEffect } from 'react'
|
||||||
|
|
||||||
|
export default function Page({ title, children }) {
|
||||||
|
useEffect(() => {
|
||||||
|
document.title = title ? `${title } - PleaseWait` : 'PleaseWait';
|
||||||
|
});
|
||||||
|
|
||||||
export default class Page extends React.PureComponent {
|
|
||||||
render() {
|
|
||||||
return (
|
return (
|
||||||
<div className="container-fluid">
|
<div className="container-fluid">
|
||||||
{ this.props.children }
|
{ children }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,35 +0,0 @@
|
||||||
import { LOGIN_SUCCESS, LOGIN_FAILURE } from '../actions/login';
|
|
||||||
import { FETCH_MESSAGES_SUCCESS } from '../actions/chat';
|
|
||||||
|
|
||||||
const defaultState = {
|
|
||||||
messagesByChannel: {},
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function chatReducer(state = defaultState, action) {
|
|
||||||
switch (action.type) {
|
|
||||||
case FETCH_MESSAGES_SUCCESS:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
messagesByChannel: {
|
|
||||||
...state.messagesByChannel,
|
|
||||||
[action.channel]: [...action.data.Messages]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
case 'CHANNEL_EVENT':
|
|
||||||
switch(action.event) {
|
|
||||||
case "message":
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
messagesByChannel: {
|
|
||||||
...state.messagesByChannel,
|
|
||||||
[action.data.Channel]: [
|
|
||||||
...state.messagesByChannel[action.data.Channel],
|
|
||||||
action.data.Message
|
|
||||||
]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
return state;
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
import { LOGIN_SUCCESS, LOGIN_FAILURE } from '../actions/login';
|
|
||||||
|
|
||||||
const defaultState = {
|
|
||||||
isLoggedIn: false,
|
|
||||||
username: null,
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function loginReducer(state = defaultState, action) {
|
|
||||||
switch (action.type) {
|
|
||||||
case LOGIN_SUCCESS:
|
|
||||||
return { ...state, isLoggedIn: true, username: action.data.Username };
|
|
||||||
case LOGIN_FAILURE:
|
|
||||||
return { ...state, isLoggedIn: false, username: null };
|
|
||||||
}
|
|
||||||
return state;
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
import { ADD_PRODUCT, REMOVE_PRODUCT } from '../actions/products';
|
|
||||||
|
|
||||||
export const rootReducer = (state, action) => {
|
|
||||||
|
|
||||||
console.log(`Action: ${JSON.stringify(action)}`)
|
|
||||||
|
|
||||||
switch (action.type) {
|
|
||||||
|
|
||||||
case ADD_PRODUCT:
|
|
||||||
// L'action est de type ADD_PRODUCT
|
|
||||||
// On ajoute le produit dans la liste et
|
|
||||||
// on retourne un nouvel état modifié
|
|
||||||
return {
|
|
||||||
products: [...state.products, action.product]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
case REMOVE_PRODUCT:
|
|
||||||
// L'action est de type REMOVE_PRODUCT
|
|
||||||
// On filtre la liste des produits et on
|
|
||||||
// retourne un nouvel état modifié
|
|
||||||
return {
|
|
||||||
products: state.products.filter(p => p.name !== action.productName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Si l'action n'est pas gérée, on retourne l'état
|
|
||||||
// sans le modifier
|
|
||||||
return state
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,98 +0,0 @@
|
||||||
import { call, put, take } from 'redux-saga/effects';
|
|
||||||
import { eventChannel } from 'redux-saga'
|
|
||||||
import {
|
|
||||||
SEND_MESSAGE_FAILURE, SEND_MESSAGE_SUCCESS,
|
|
||||||
FETCH_MESSAGES_FAILURE, FETCH_MESSAGES_SUCCESS
|
|
||||||
} from '../actions/chat';
|
|
||||||
|
|
||||||
export function* sendMessageSaga(action) {
|
|
||||||
|
|
||||||
let result;
|
|
||||||
try {
|
|
||||||
result = yield call(sendMessage, action.channel, action.text);
|
|
||||||
} catch(err) {
|
|
||||||
yield put({ type: SEND_MESSAGE_FAILURE, err });
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('Error' in result) {
|
|
||||||
yield put({type: SEND_MESSAGE_FAILURE, err: result.Error});
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
yield put({type: SEND_MESSAGE_SUCCESS, data: result.Data });
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function sendMessage(channel, text) {
|
|
||||||
return fetch(`http://192.168.0.126:3000/channels/${channel}`, {
|
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify({
|
|
||||||
Text: text,
|
|
||||||
}),
|
|
||||||
mode: 'cors',
|
|
||||||
credentials: 'include'
|
|
||||||
})
|
|
||||||
.then(res => res.json())
|
|
||||||
}
|
|
||||||
|
|
||||||
export function* fetchMessagesSaga(action) {
|
|
||||||
|
|
||||||
let result;
|
|
||||||
try {
|
|
||||||
result = yield call(fetchMessages, action.channel);
|
|
||||||
} catch(err) {
|
|
||||||
yield put({ type: FETCH_MESSAGES_FAILURE, err });
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('Error' in result) {
|
|
||||||
yield put({type: FETCH_MESSAGES_FAILURE, err: result.Error});
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
yield put({type: FETCH_MESSAGES_SUCCESS, channel: action.channel, data: result.Data });
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function fetchMessages(channel) {
|
|
||||||
return fetch(`http://192.168.0.126:3000/channels/${channel}`, {
|
|
||||||
mode: 'cors',
|
|
||||||
credentials: 'include'
|
|
||||||
})
|
|
||||||
.then(res => res.json())
|
|
||||||
}
|
|
||||||
|
|
||||||
function channelEvents(channel) {
|
|
||||||
return eventChannel(emitter => {
|
|
||||||
|
|
||||||
const eventSource = new EventSource(
|
|
||||||
`http://192.168.0.126:3000/channels/${channel}/stream`,
|
|
||||||
{ withCredentials: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
const emit = evt => {
|
|
||||||
emitter({type: evt.type, data: evt.data});
|
|
||||||
};
|
|
||||||
|
|
||||||
eventSource.addEventListener("joined", emit);
|
|
||||||
eventSource.addEventListener("left", emit);
|
|
||||||
eventSource.addEventListener("message", emit);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
eventSource.removeEventListener("joined", emit)
|
|
||||||
eventSource.removeEventListener("left", emit)
|
|
||||||
eventSource.removeEventListener("message", emit)
|
|
||||||
eventSource.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function* streamEventsSaga(action) {
|
|
||||||
const stream = yield call(channelEvents, action.channel)
|
|
||||||
while (true) {
|
|
||||||
let event = yield take(stream)
|
|
||||||
yield put({ type: 'CHANNEL_EVENT', event: event.type, data: JSON.parse(event.data) });
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
import { call, put } from 'redux-saga/effects';
|
|
||||||
import { LOGIN_FAILURE, LOGIN_SUCCESS } from '../actions/login';
|
|
||||||
|
|
||||||
export default function* loginSaga(action) {
|
|
||||||
|
|
||||||
let result;
|
|
||||||
try {
|
|
||||||
result = yield call(doLogin, action.username, action.password);
|
|
||||||
} catch(err) {
|
|
||||||
yield put({ type: LOGIN_FAILURE, err });
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('Error' in result) {
|
|
||||||
yield put({type: LOGIN_FAILURE, err: result.Error});
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
yield put({type: LOGIN_SUCCESS, data: result.Data });
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function doLogin(username, password) {
|
|
||||||
return fetch('http://192.168.0.126:3000/login', {
|
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify({
|
|
||||||
Username: username,
|
|
||||||
Password: password
|
|
||||||
}),
|
|
||||||
mode: 'cors',
|
|
||||||
credentials: 'include'
|
|
||||||
})
|
|
||||||
.then(res => res.json())
|
|
||||||
}
|
|
|
@ -1,14 +1,7 @@
|
||||||
import { all, takeLatest } from 'redux-saga/effects';
|
import { all, takeLatest } from 'redux-saga/effects';
|
||||||
import loginSaga from './login';
|
|
||||||
import { LOGIN } from '../actions/login';
|
|
||||||
import { SEND_MESSAGE, FETCH_MESSAGES, STREAM_EVENTS } from '../actions/chat';
|
|
||||||
import { sendMessageSaga, fetchMessagesSaga, streamEventsSaga } from './chat';
|
|
||||||
|
|
||||||
export default function* rootSaga() {
|
export default function* rootSaga() {
|
||||||
yield all([
|
yield all([
|
||||||
takeLatest(LOGIN, loginSaga),
|
|
||||||
takeLatest(SEND_MESSAGE, sendMessageSaga),
|
|
||||||
takeLatest(FETCH_MESSAGES, fetchMessagesSaga),
|
|
||||||
takeLatest(STREAM_EVENTS, streamEventsSaga)
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,11 @@
|
||||||
import { createStore, applyMiddleware, combineReducers, compose } from 'redux'
|
import { createStore, applyMiddleware, combineReducers, compose } from 'redux'
|
||||||
import loginReducer from '../reducers/login'
|
|
||||||
import rootSaga from '../sagas/root'
|
|
||||||
import createSagaMiddleware from 'redux-saga'
|
import createSagaMiddleware from 'redux-saga'
|
||||||
import chatReducer from '../reducers/chat';
|
import rootSaga from '../sagas/root'
|
||||||
|
|
||||||
const sagaMiddleware = createSagaMiddleware()
|
const sagaMiddleware = createSagaMiddleware()
|
||||||
|
|
||||||
const rootReducer = combineReducers({
|
const rootReducer = combineReducers({
|
||||||
user: loginReducer,
|
// Ajouter vos reducers ici
|
||||||
chat: chatReducer,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
|
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
|
||||||
|
|
|
@ -1,39 +0,0 @@
|
||||||
/* globals test, expect, jest */
|
|
||||||
import { addProduct, removeProduct } from '../actions/products'
|
|
||||||
import { configureStore } from './store'
|
|
||||||
|
|
||||||
test('Ajout/suppression des produits', () => {
|
|
||||||
// On crée une instance de notre store
|
|
||||||
// avec le state par défaut
|
|
||||||
const store = configureStore()
|
|
||||||
|
|
||||||
// On crée un "faux" subscriber
|
|
||||||
// pour vérifier que l'état du store
|
|
||||||
// a bien été modifié le nombre de fois voulu
|
|
||||||
const subscriber = jest.fn()
|
|
||||||
|
|
||||||
// On attache notre faux subscriber
|
|
||||||
// au store
|
|
||||||
store.subscribe(subscriber)
|
|
||||||
|
|
||||||
// On "dispatch" nos actions
|
|
||||||
store.dispatch(addProduct('pomme', 5))
|
|
||||||
store.dispatch(addProduct('orange', 7))
|
|
||||||
store.dispatch(addProduct('orange', 10))
|
|
||||||
store.dispatch(removeProduct('pomme'))
|
|
||||||
|
|
||||||
// On s'assure que notre subscriber a bien été
|
|
||||||
// appelé
|
|
||||||
expect(subscriber).toHaveBeenCalledTimes(4)
|
|
||||||
|
|
||||||
const state = store.getState()
|
|
||||||
|
|
||||||
// On s'assure que l'état du store correspond
|
|
||||||
// à ce qu'on attend
|
|
||||||
expect(state).toMatchObject({
|
|
||||||
products: [
|
|
||||||
{name: 'orange', price: 7}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
})
|
|
|
@ -12,6 +12,9 @@ if [ ! -e "$FIRST_RUN_FLAG_FILE" ]; then
|
||||||
echo "Applying database migrations. Please wait..."
|
echo "Applying database migrations. Please wait..."
|
||||||
bin/console doctrine:migrations:migrate --no-interaction
|
bin/console doctrine:migrations:migrate --no-interaction
|
||||||
|
|
||||||
|
echo "Loading fixtures. Please wait..."
|
||||||
|
bin/console doctrine:fixtures:load --no-interaction
|
||||||
|
|
||||||
touch "$FIRST_RUN_FLAG_FILE"
|
touch "$FIRST_RUN_FLAG_FILE"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
@baseURL = http://localhost:8001/api/v1
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
GET {{baseURL}}
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
// Login as "client1"
|
||||||
|
|
||||||
|
POST {{baseURL}}/login
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{ "username": "client1", "password": "client1" }
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
// Login as "dev1"
|
||||||
|
|
||||||
|
POST {{baseURL}}/login
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{ "username": "dev1", "password": "dev1" }
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
// Logout
|
||||||
|
|
||||||
|
GET {{baseURL}}/logout
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
// Get current user info
|
||||||
|
|
||||||
|
GET {{baseURL}}/me
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
// List users
|
||||||
|
|
||||||
|
GET {{baseURL}}/users
|
||||||
|
Content-Type: application/json
|
|
@ -0,0 +1,71 @@
|
||||||
|
# Application de suivi des demandes client
|
||||||
|
|
||||||
|
## Objectif
|
||||||
|
|
||||||
|
Implémenter une application de suivi des demandes client proposant les fonctionnalités suivantes:
|
||||||
|
|
||||||
|
- Authentification par identifiant et mot de passe
|
||||||
|
- Autorisation par rôles:
|
||||||
|
- "client"
|
||||||
|
- "développeur"
|
||||||
|
- Interfaces pour le rôle "client":
|
||||||
|
- Une interface de visualisation des projets qui lui sont associés
|
||||||
|
- Une interface de création d'une nouvelle demande pour un projet donné
|
||||||
|
- Une interface de visualisation des demandes en cours/cloturée pour un projet donné
|
||||||
|
- Une interface de visualisation de détails d'une demande et des commentaires associés, avec possibilité d'ajouter un commentaire
|
||||||
|
- Une interface de création d'une nouvelle demande pour un projet donné
|
||||||
|
- Interfaces pour le rôle "développeur":
|
||||||
|
- Une interface de création d'un nouveau projet
|
||||||
|
- Une interface de création d'un nouveau compte client
|
||||||
|
- Une interface de visualisation des clients (listing)
|
||||||
|
- Une interface de visualisation des projets (listing)
|
||||||
|
- Une interface de visualisation des dernières demandes ouvertes (multi-projet)
|
||||||
|
- Une interface de visualisation de détails d'une demande et des commentaires associés, avec possibilité d'ajouter un commentaire et de changer l'état d'une demande
|
||||||
|
|
||||||
|
## Modèle de données
|
||||||
|
|
||||||
|
### User (`User`)
|
||||||
|
|
||||||
|
#### Attributs
|
||||||
|
|
||||||
|
- `id:int (unique)` Identifiant (clé primaire) de l'utilisateur
|
||||||
|
- `username:string (unique)` Nom de l'utilisateur
|
||||||
|
- `password:string` Mot de passe de l'utilisateur
|
||||||
|
- `projects:[]Project` Projets associés à l'utilisateur
|
||||||
|
|
||||||
|
### Projet (`Project`)
|
||||||
|
|
||||||
|
#### Attributs
|
||||||
|
|
||||||
|
- `id:int (unique)` Identifiant (clé primaire) du projet
|
||||||
|
- `name:string` Nom du projet
|
||||||
|
- `users:[]User` Utilisateurs attachés au projet
|
||||||
|
|
||||||
|
### Demande (`Request`)
|
||||||
|
|
||||||
|
#### Attributs
|
||||||
|
|
||||||
|
- `id:int (unique)` Identifiant (clé primaire) de la demande
|
||||||
|
- `title:string` Titre associé à la demande
|
||||||
|
- `author:User` Auteur de la demande (clé étrangère)
|
||||||
|
- `project:Project` Identifiant du projet associé (clé étrangère)
|
||||||
|
- `status:RequestStatus` Identifiant du statut courant de la demande
|
||||||
|
- `comments:[]Comment` Commentaire associé à la demande
|
||||||
|
- `createdAt:Date` Date de la création du commentaire
|
||||||
|
|
||||||
|
### Statut de demande (`RequestStatus`)
|
||||||
|
|
||||||
|
#### Attributs
|
||||||
|
|
||||||
|
- `id:int (unique)` Identifiant (clé primaire) du statut de requête
|
||||||
|
- `label:string` Label associé au statut
|
||||||
|
|
||||||
|
### Commentaire (`Comment`)
|
||||||
|
|
||||||
|
#### Attributs
|
||||||
|
|
||||||
|
- `id:int (unique)` Identifiant (clé primaire) du commentaires
|
||||||
|
- `request:Request` Identifiant de la demande associé au commentaire (clé étrangère)
|
||||||
|
- `text:string` Texte du commentaire
|
||||||
|
- `createdAt:Date` Date de la création du commentaire
|
||||||
|
- `author:User` Auteur du commentaire (clé étrangère)
|
Binary file not shown.
Loading…
Reference in New Issue