From a4aeee4961119ef523d837bdbe983cbe92dc3b36 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Agneray?= <francois.agneray@lam.fr>
Date: Mon, 9 Dec 2019 17:36:04 +0100
Subject: [PATCH 01/31] Migration to slim v4 and refactoring

---
 .env.dist                                     |    1 -
 .gitignore                                    |    4 +-
 .gitlab-ci.yml                                |   74 +-
 CHANGELOG.md                                  |   24 -
 Dockerfile                                    |    4 +-
 LICENSE                                       |    9 +-
 Makefile                                      |   44 +-
 README.md                                     |  123 +-
 VERSION                                       |    2 +-
 anis-server.yaml                              | 1009 -----------
 app/constants.php                             |   18 +
 app/dependencies.php                          |  113 ++
 app/middlewares.php                           |   14 +
 app/routes.php                                |   29 +
 app/settings.php                              |   33 +
 cli-config.php                                |   12 +-
 composer.json                                 |   29 +-
 composer.lock                                 | 1508 +++++++++--------
 conf-dev/Dockerfile                           |    7 +-
 conf-dev/anis_init.sh                         |   72 -
 conf-dev/{dev-init.sh => dev-meta.sh}         |    9 +-
 conf-dev/generate_encryption_key.php          |    2 -
 conf-dev/init-postgres.sh                     |    3 +-
 conf-dev/vhost.conf                           |    6 +-
 docker-compose.yml                            |   45 +-
 documentation/api/activate-account.md         |  100 --
 documentation/api/category-list.md            |   93 -
 documentation/api/category.md                 |  122 --
 documentation/api/change-password.md          |  119 --
 documentation/api/database-list.md            |  194 ---
 documentation/api/database.md                 |  212 ---
 documentation/api/dataset-list.md             |  278 ---
 documentation/api/dataset.md                  |  217 ---
 documentation/api/family-list.md              |  143 --
 documentation/api/family.md                   |  177 --
 documentation/api/file-list.md                |  168 --
 documentation/api/file.md                     |  184 --
 documentation/api/group-list.md               |   89 -
 documentation/api/group.md                    |  122 --
 documentation/api/login.md                    |  109 --
 documentation/api/new-password.md             |   81 -
 documentation/api/project-list.md             |  179 --
 documentation/api/project.md                  |  182 --
 documentation/api/register.md                 |  100 --
 documentation/api/root.md                     |   44 -
 documentation/api/tables-available.md         |   60 -
 documentation/api/user-list.md                |  150 --
 documentation/api/user.md                     |  167 --
 documentation/mcd/anis_v3_mcd.png             |  Bin 93829 -> 0 bytes
 documentation/mcd/anis_v3_mcd.txt             |   27 -
 documentation/mcd/anis_v3_settings_mcd.png    |  Bin 7201 -> 0 bytes
 documentation/mcd/anis_v3_settings_mcd.txt    |    5 -
 public/index.php                              |   67 +-
 src/Action/AbstractAction.php                 |   52 +
 src/Action/Admin/InstanceAction.php           |  110 --
 src/Action/Admin/InstanceListAction.php       |  100 --
 src/Action/Admin/MetamodelAction.php          |   87 -
 src/Action/Admin/OptionAction.php             |   91 -
 src/Action/Admin/OptionListAction.php         |   90 -
 src/Action/Admin/SelectAction.php             |   90 -
 src/Action/Admin/SelectListAction.php         |   79 -
 src/Action/Admin/UserAction.php               |   91 -
 src/Action/Admin/UserListAction.php           |   49 -
 src/Action/{Meta => }/AttributeAction.php     |  117 +-
 src/Action/AttributeListAction.php            |   55 +
 src/Action/DatabaseAction.php                 |  100 ++
 src/Action/DatabaseListAction.php             |   87 +
 src/Action/DatasetAction.php                  |  122 ++
 src/Action/DatasetListAction.php              |  178 ++
 src/Action/FamilyAction.php                   |  103 ++
 src/Action/FamilyListAction.php               |  100 ++
 src/Action/Login/ActivateAccountAction.php    |  168 --
 src/Action/Login/ChangePasswordAction.php     |  173 --
 src/Action/Login/NewPasswordAction.php        |  165 --
 src/Action/Login/RegisterAction.php           |  207 ---
 src/Action/Login/TokenAction.php              |  182 --
 src/Action/Meta/AttributeListAction.php       |  105 --
 src/Action/Meta/DatabaseAction.php            |  153 --
 src/Action/Meta/DatabaseListAction.php        |  137 --
 src/Action/Meta/DatasetAction.php             |  173 --
 src/Action/Meta/DatasetListAction.php         |  240 ---
 src/Action/Meta/FamilyAction.php              |  133 --
 src/Action/Meta/FamilyListAction.php          |  112 --
 src/Action/Meta/FileAction.php                |   93 -
 src/Action/Meta/FileListAction.php            |  105 --
 src/Action/Meta/FileProxyAction.php           |   71 -
 src/Action/Meta/GroupAction.php               |   92 -
 src/Action/Meta/GroupListAction.php           |   81 -
 src/Action/Meta/OutputCategoryAction.php      |  160 --
 src/Action/Meta/OutputCategoryListAction.php  |   99 --
 src/Action/Meta/ProjectAction.php             |  159 --
 src/Action/Meta/ProjectListAction.php         |   98 --
 src/Action/Meta/TableListAction.php           |   90 -
 src/Action/OutputCategoryAction.php           |  112 ++
 src/Action/OutputCategoryListAction.php       |   87 +
 src/Action/ProjectAction.php                  |  109 ++
 src/Action/ProjectListAction.php              |   89 +
 src/Action/Root/OpenApiAction.php             |   62 -
 src/Action/Root/RootAction.php                |   59 -
 src/Action/RootAction.php                     |   41 +
 src/Action/Search/ServiceAction.php           |   86 -
 src/Action/{Search => }/SearchAction.php      |  268 +--
 src/Action/TableListAction.php                |   86 +
 src/Entity/Admin/Instance.php                 |  336 ----
 src/Entity/Admin/SettingsOption.php           |  112 --
 src/Entity/Admin/SettingsSelect.php           |   82 -
 src/Entity/Admin/User.php                     |  131 --
 src/Entity/{Metamodel => }/Attribute.php      |   12 +-
 src/Entity/{Metamodel => }/CriteriaFamily.php |   12 +-
 src/Entity/{Metamodel => }/Database.php       |   12 +-
 src/Entity/{Metamodel => }/Dataset.php        |   14 +-
 src/Entity/{Metamodel => }/DatasetFamily.php  |   14 +-
 .../{Metamodel => }/DatasetPrivileges.php     |   12 +-
 src/Entity/{Metamodel => }/File.php           |   12 +-
 src/Entity/{Metamodel => }/Group.php          |   12 +-
 src/Entity/{Metamodel => }/OutputCategory.php |   12 +-
 src/Entity/{Metamodel => }/OutputFamily.php   |   12 +-
 src/Entity/{Metamodel => }/Project.php        |   12 +-
 src/Entity/{Metamodel => }/User.php           |   12 +-
 src/Handlers/LogErrorHandler.php              |   52 +
 src/Middleware/AuthorizationMiddleware.php    |  137 ++
 src/Middleware/ContentTypeJsonMiddleware.php  |   41 +
 src/Middleware/CorsMiddleware.php             |   34 -
 src/Middleware/JsonBodyParserMiddleware.php   |   50 +
 src/Middleware/TokenMiddleware.php            |   70 -
 src/Utils/ActionTrait.php                     |   84 -
 src/Utils/AnisErrorHandler.php                |  106 --
 src/Utils/DBALConnectionFactory.php           |   24 +-
 src/Utils/MetaEntityManagerFactory.php        |   98 --
 src/Utils/Operator/Between.php                |   10 +-
 src/Utils/Operator/Equal.php                  |   10 +-
 src/Utils/Operator/GreaterThan.php            |   12 +-
 src/Utils/Operator/GreaterThanEqual.php       |   12 +-
 src/Utils/Operator/IOperator.php              |   10 +-
 src/Utils/Operator/IOperatorFactory.php       |   10 +-
 src/Utils/Operator/In.php                     |   10 +-
 src/Utils/Operator/JsonPostgres.php           |   10 +-
 src/Utils/Operator/LessThan.php               |   10 +-
 src/Utils/Operator/LessThanEqual.php          |   10 +-
 src/Utils/Operator/Like.php                   |   10 +-
 src/Utils/Operator/NotEqual.php               |   10 +-
 src/Utils/Operator/NotIn.php                  |   10 +-
 src/Utils/Operator/NotLike.php                |   10 +-
 src/Utils/Operator/Operator.php               |   10 +-
 src/Utils/Operator/OperatorException.php      |   10 +-
 src/Utils/Operator/OperatorFactory.php        |   10 +-
 src/Utils/Operator/OperatorNotNull.php        |   10 +-
 src/Utils/Operator/OperatorNull.php           |   22 +-
 src/Utils/SearchException.php                 |   10 +-
 src/dependencies.php                          |  269 ---
 src/middleware.php                            |   12 -
 src/routes.php                                |   65 -
 src/settings.php                              |   53 -
 tests/AbstractActionAdminTestCase.php         |   47 -
 tests/AbstractActionMetaTestCase.php          |   56 -
 tests/Action/AttributeActionTest.php          |  250 +++
 tests/Action/AttributeListActionTest.php      |  165 ++
 tests/Action/DatabaseActionTest.php           |  122 ++
 tests/Action/DatabaseListActionTest.php       |  117 ++
 tests/Action/DatasetActionTest.php            |  196 +++
 tests/Action/DatasetListActionTest.php        |  234 +++
 tests/Action/FamilyActionTest.php             |  124 ++
 tests/Action/FamilyListActionTest.php         |  111 ++
 .../Login/ActivateAccountActionTest.php       |   86 -
 .../Action/Login/ChangePasswordActionTest.php |  121 --
 tests/Action/Login/NewPasswordActionTest.php  |   72 -
 tests/Action/Login/RegisterActionTest.php     |   98 --
 tests/Action/Login/TokenActionTest.php        |  120 --
 tests/Action/Meta/DatabaseActionTest.php      |  158 --
 tests/Action/Meta/DatabaseListActionTest.php  |  138 --
 tests/Action/Meta/DatasetActionTest.php       |  180 --
 tests/Action/Meta/DatasetListActionTest.php   |  189 ---
 tests/Action/Meta/FamilyActionTest.php        |  169 --
 tests/Action/Meta/FamilyListActionTest.php    |  236 ---
 tests/Action/Meta/FileActionTest.php          |  138 --
 tests/Action/Meta/FileListActionTest.php      |  149 --
 tests/Action/Meta/GroupActionTest.php         |  113 --
 tests/Action/Meta/GroupListActionTest.php     |   77 -
 .../Action/Meta/OutputCategoryActionTest.php  |  116 --
 .../Meta/OutputCategoryListActionTest.php     |   92 -
 tests/Action/Meta/ProjectActionTest.php       |  159 --
 tests/Action/Meta/ProjectListActionTest.php   |  138 --
 tests/Action/Meta/TableListActionTest.php     |   49 -
 tests/Action/OutputCategoryActionTest.php     |  146 ++
 tests/Action/OutputCategoryListActionTest.php |  133 ++
 tests/Action/ProjectActionTest.php            |  156 ++
 tests/Action/ProjectListActionTest.php        |  146 ++
 tests/Action/Root/RootActionTest.php          |   24 -
 tests/Action/RootActionTest.php               |   36 +
 tests/Action/TableListActionTest.php          |  120 ++
 tests/EntityManagerBuilder.php                |   35 +
 tests/Handlers/LogErrorHandlerTest.php        |   48 +
 .../ContentTypeJsonMiddlewareTest.php         |   41 +
 tests/admin.yaml                              |   15 -
 tests/bootstrap.php                           |   15 +-
 tests/database.yaml                           |  121 --
 196 files changed, 5312 insertions(+), 14143 deletions(-)
 delete mode 100644 .env.dist
 delete mode 100644 CHANGELOG.md
 delete mode 100644 anis-server.yaml
 create mode 100644 app/constants.php
 create mode 100644 app/dependencies.php
 create mode 100644 app/middlewares.php
 create mode 100644 app/routes.php
 create mode 100644 app/settings.php
 delete mode 100755 conf-dev/anis_init.sh
 rename conf-dev/{dev-init.sh => dev-meta.sh} (79%)
 delete mode 100644 conf-dev/generate_encryption_key.php
 delete mode 100644 documentation/api/activate-account.md
 delete mode 100644 documentation/api/category-list.md
 delete mode 100644 documentation/api/category.md
 delete mode 100644 documentation/api/change-password.md
 delete mode 100644 documentation/api/database-list.md
 delete mode 100644 documentation/api/database.md
 delete mode 100644 documentation/api/dataset-list.md
 delete mode 100644 documentation/api/dataset.md
 delete mode 100644 documentation/api/family-list.md
 delete mode 100644 documentation/api/family.md
 delete mode 100644 documentation/api/file-list.md
 delete mode 100644 documentation/api/file.md
 delete mode 100644 documentation/api/group-list.md
 delete mode 100644 documentation/api/group.md
 delete mode 100644 documentation/api/login.md
 delete mode 100644 documentation/api/new-password.md
 delete mode 100644 documentation/api/project-list.md
 delete mode 100644 documentation/api/project.md
 delete mode 100644 documentation/api/register.md
 delete mode 100644 documentation/api/root.md
 delete mode 100644 documentation/api/tables-available.md
 delete mode 100644 documentation/api/user-list.md
 delete mode 100644 documentation/api/user.md
 delete mode 100644 documentation/mcd/anis_v3_mcd.png
 delete mode 100644 documentation/mcd/anis_v3_mcd.txt
 delete mode 100644 documentation/mcd/anis_v3_settings_mcd.png
 delete mode 100644 documentation/mcd/anis_v3_settings_mcd.txt
 create mode 100644 src/Action/AbstractAction.php
 delete mode 100644 src/Action/Admin/InstanceAction.php
 delete mode 100644 src/Action/Admin/InstanceListAction.php
 delete mode 100644 src/Action/Admin/MetamodelAction.php
 delete mode 100644 src/Action/Admin/OptionAction.php
 delete mode 100644 src/Action/Admin/OptionListAction.php
 delete mode 100644 src/Action/Admin/SelectAction.php
 delete mode 100644 src/Action/Admin/SelectListAction.php
 delete mode 100644 src/Action/Admin/UserAction.php
 delete mode 100644 src/Action/Admin/UserListAction.php
 rename src/Action/{Meta => }/AttributeAction.php (50%)
 create mode 100644 src/Action/AttributeListAction.php
 create mode 100644 src/Action/DatabaseAction.php
 create mode 100644 src/Action/DatabaseListAction.php
 create mode 100644 src/Action/DatasetAction.php
 create mode 100644 src/Action/DatasetListAction.php
 create mode 100644 src/Action/FamilyAction.php
 create mode 100644 src/Action/FamilyListAction.php
 delete mode 100644 src/Action/Login/ActivateAccountAction.php
 delete mode 100644 src/Action/Login/ChangePasswordAction.php
 delete mode 100644 src/Action/Login/NewPasswordAction.php
 delete mode 100644 src/Action/Login/RegisterAction.php
 delete mode 100644 src/Action/Login/TokenAction.php
 delete mode 100644 src/Action/Meta/AttributeListAction.php
 delete mode 100644 src/Action/Meta/DatabaseAction.php
 delete mode 100644 src/Action/Meta/DatabaseListAction.php
 delete mode 100644 src/Action/Meta/DatasetAction.php
 delete mode 100644 src/Action/Meta/DatasetListAction.php
 delete mode 100644 src/Action/Meta/FamilyAction.php
 delete mode 100644 src/Action/Meta/FamilyListAction.php
 delete mode 100644 src/Action/Meta/FileAction.php
 delete mode 100644 src/Action/Meta/FileListAction.php
 delete mode 100644 src/Action/Meta/FileProxyAction.php
 delete mode 100644 src/Action/Meta/GroupAction.php
 delete mode 100644 src/Action/Meta/GroupListAction.php
 delete mode 100644 src/Action/Meta/OutputCategoryAction.php
 delete mode 100644 src/Action/Meta/OutputCategoryListAction.php
 delete mode 100644 src/Action/Meta/ProjectAction.php
 delete mode 100644 src/Action/Meta/ProjectListAction.php
 delete mode 100644 src/Action/Meta/TableListAction.php
 create mode 100644 src/Action/OutputCategoryAction.php
 create mode 100644 src/Action/OutputCategoryListAction.php
 create mode 100644 src/Action/ProjectAction.php
 create mode 100644 src/Action/ProjectListAction.php
 delete mode 100644 src/Action/Root/OpenApiAction.php
 delete mode 100644 src/Action/Root/RootAction.php
 create mode 100644 src/Action/RootAction.php
 delete mode 100644 src/Action/Search/ServiceAction.php
 rename src/Action/{Search => }/SearchAction.php (53%)
 create mode 100644 src/Action/TableListAction.php
 delete mode 100644 src/Entity/Admin/Instance.php
 delete mode 100644 src/Entity/Admin/SettingsOption.php
 delete mode 100644 src/Entity/Admin/SettingsSelect.php
 delete mode 100644 src/Entity/Admin/User.php
 rename src/Entity/{Metamodel => }/Attribute.php (98%)
 rename src/Entity/{Metamodel => }/CriteriaFamily.php (87%)
 rename src/Entity/{Metamodel => }/Database.php (93%)
 rename src/Entity/{Metamodel => }/Dataset.php (95%)
 rename src/Entity/{Metamodel => }/DatasetFamily.php (87%)
 rename src/Entity/{Metamodel => }/DatasetPrivileges.php (86%)
 rename src/Entity/{Metamodel => }/File.php (92%)
 rename src/Entity/{Metamodel => }/Group.php (86%)
 rename src/Entity/{Metamodel => }/OutputCategory.php (90%)
 rename src/Entity/{Metamodel => }/OutputFamily.php (88%)
 rename src/Entity/{Metamodel => }/Project.php (92%)
 rename src/Entity/{Metamodel => }/User.php (92%)
 create mode 100644 src/Handlers/LogErrorHandler.php
 create mode 100644 src/Middleware/AuthorizationMiddleware.php
 create mode 100644 src/Middleware/ContentTypeJsonMiddleware.php
 delete mode 100644 src/Middleware/CorsMiddleware.php
 create mode 100644 src/Middleware/JsonBodyParserMiddleware.php
 delete mode 100644 src/Middleware/TokenMiddleware.php
 delete mode 100644 src/Utils/ActionTrait.php
 delete mode 100644 src/Utils/AnisErrorHandler.php
 delete mode 100644 src/Utils/MetaEntityManagerFactory.php
 delete mode 100644 src/dependencies.php
 delete mode 100644 src/middleware.php
 delete mode 100644 src/routes.php
 delete mode 100644 src/settings.php
 delete mode 100644 tests/AbstractActionAdminTestCase.php
 delete mode 100644 tests/AbstractActionMetaTestCase.php
 create mode 100644 tests/Action/AttributeActionTest.php
 create mode 100644 tests/Action/AttributeListActionTest.php
 create mode 100644 tests/Action/DatabaseActionTest.php
 create mode 100644 tests/Action/DatabaseListActionTest.php
 create mode 100644 tests/Action/DatasetActionTest.php
 create mode 100644 tests/Action/DatasetListActionTest.php
 create mode 100644 tests/Action/FamilyActionTest.php
 create mode 100644 tests/Action/FamilyListActionTest.php
 delete mode 100644 tests/Action/Login/ActivateAccountActionTest.php
 delete mode 100644 tests/Action/Login/ChangePasswordActionTest.php
 delete mode 100644 tests/Action/Login/NewPasswordActionTest.php
 delete mode 100644 tests/Action/Login/RegisterActionTest.php
 delete mode 100644 tests/Action/Login/TokenActionTest.php
 delete mode 100644 tests/Action/Meta/DatabaseActionTest.php
 delete mode 100644 tests/Action/Meta/DatabaseListActionTest.php
 delete mode 100644 tests/Action/Meta/DatasetActionTest.php
 delete mode 100644 tests/Action/Meta/DatasetListActionTest.php
 delete mode 100644 tests/Action/Meta/FamilyActionTest.php
 delete mode 100644 tests/Action/Meta/FamilyListActionTest.php
 delete mode 100644 tests/Action/Meta/FileActionTest.php
 delete mode 100644 tests/Action/Meta/FileListActionTest.php
 delete mode 100644 tests/Action/Meta/GroupActionTest.php
 delete mode 100644 tests/Action/Meta/GroupListActionTest.php
 delete mode 100644 tests/Action/Meta/OutputCategoryActionTest.php
 delete mode 100644 tests/Action/Meta/OutputCategoryListActionTest.php
 delete mode 100644 tests/Action/Meta/ProjectActionTest.php
 delete mode 100644 tests/Action/Meta/ProjectListActionTest.php
 delete mode 100644 tests/Action/Meta/TableListActionTest.php
 create mode 100644 tests/Action/OutputCategoryActionTest.php
 create mode 100644 tests/Action/OutputCategoryListActionTest.php
 create mode 100644 tests/Action/ProjectActionTest.php
 create mode 100644 tests/Action/ProjectListActionTest.php
 delete mode 100644 tests/Action/Root/RootActionTest.php
 create mode 100644 tests/Action/RootActionTest.php
 create mode 100644 tests/Action/TableListActionTest.php
 create mode 100644 tests/EntityManagerBuilder.php
 create mode 100644 tests/Handlers/LogErrorHandlerTest.php
 create mode 100644 tests/Middleware/ContentTypeJsonMiddlewareTest.php
 delete mode 100644 tests/admin.yaml
 delete mode 100644 tests/database.yaml

diff --git a/.env.dist b/.env.dist
deleted file mode 100644
index 091eedb..0000000
--- a/.env.dist
+++ /dev/null
@@ -1 +0,0 @@
-AMQP_HOST=amqp_host
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 42f6b3e..b4ed884 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,7 @@ nbproject/
 .vscode/
 .idea/
 vendor/*
+phpunit-coverage/
 data/
 build/*
 logs/*
@@ -14,5 +15,4 @@ cache
 .project
 .settings
 .phpunit*
-anis_v3.sqlite
-.env
\ No newline at end of file
+.env
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 44888d0..9692fc1 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,62 +1,14 @@
 stages:
-    - metrics
-    - metrics-build
-    - metrics-deploy
-    - install
-    - test
-    - sonar
-    - build
-    - deploy
+  - install
+  - test
+  - sonar
+  - build
 
 variables:
-    VERSION: "3.0"
+    VERSION: "3.1"
     SONARQUBE_URL: https://sonarqube.lam.fr
-    METRICS_IMAGE: portus.lam.fr/anis/anis-server-metrics
     CONTAINER_IMAGE: portus.lam.fr/anis/anis-server
 
-php-metrics:
-    image: jakzal/phpqa
-    stage: metrics
-    script: 
-        - phpmetrics --report-html=var/php-metrics .
-    allow_failure: true
-    cache:
-        paths:
-            - var
-        policy: push
-    only:
-        refs:
-            - develop
-
-php-metrics-build:
-    image: docker:stable
-    stage: metrics-build
-    script:
-        - echo "FROM nginx" > var/Dockerfile
-        - echo "COPY php-metrics /usr/share/nginx/html" >> var/Dockerfile
-        - docker login -u fagneray -p $PORTUS_TOKEN portus.lam.fr
-        - docker pull $METRICS_IMAGE:latest || true
-        - docker build --cache-from $METRICS_IMAGE:latest -t $METRICS_IMAGE:latest var
-        - docker push $METRICS_IMAGE:latest
-    allow_failure: true
-    cache:
-        paths:
-            - var
-        policy: pull
-    only:
-        refs:
-            - develop
-
-php-metrics-deploy:
-    image: alpine
-    stage: metrics-deploy
-    script:
-        - apk add --update curl
-        - curl -XPOST $METRICS_WEBHOOK
-    only:
-        refs:
-            - develop
-
 install:
     image: jakzal/phpqa
     stage: install
@@ -102,9 +54,9 @@ build:
     stage: build
     script:
         - docker login -u fagneray -p $PORTUS_TOKEN portus.lam.fr
-        - docker pull $CONTAINER_IMAGE:latest-dev || true
-        - docker build --cache-from $CONTAINER_IMAGE:latest-dev -t $CONTAINER_IMAGE:latest-dev .
-        - docker push $CONTAINER_IMAGE:latest-dev
+        - docker pull $CONTAINER_IMAGE:latest || true
+        - docker build --cache-from $CONTAINER_IMAGE:latest -t $CONTAINER_IMAGE:latest .
+        - docker push $CONTAINER_IMAGE:latest
     cache:
         paths:
             - vendor
@@ -112,13 +64,3 @@ build:
     only:
         refs:
             - develop
-
-deploy:
-    image: alpine
-    stage: deploy
-    script:
-        - apk add --update curl
-        - curl -XPOST $DEV_WEBHOOK
-    only:
-        refs:
-            - develop
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
deleted file mode 100644
index b6975a7..0000000
--- a/CHANGELOG.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# Changelog
-All notable changes to this project will be documented in this file.
-
-The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
-and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
-
-## [3.0.0] - yyyy-mm-dd
-### Added
-- Pour les nouvelles fonctionnalités.
-
-### Changed
-- Pour les changements au sein des fonctionnalités déjà existantes.
-
-### Deprecated
-- Pour les fonctionnalités qui seront supprimées dans la prochaine publication.
-
-### Removed
-- Pour les anciennes fonctionnalités Deprecated qui viennent d’être supprimées.
-
-### Fixed
-- Pour les corrections de bugs.
-
-### Security
-- Pour encourager les utilisateurs à mettre à niveau afin d’éviter des failles de sécurité.
diff --git a/Dockerfile b/Dockerfile
index 15e3de7..dc688d2 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -3,7 +3,7 @@ FROM php:7.3-apache
 # Install modules
 RUN apt-get update \ 
     && apt-get install -y zlib1g zlib1g-dev libpq-dev libpq5 libzip-dev zip unzip \
-    && docker-php-ext-install pgsql pdo_pgsql zip
+    && docker-php-ext-install pgsql pdo_pgsql zip bcmath
 
 RUN a2enmod rewrite
 
@@ -12,4 +12,4 @@ COPY ./conf-dev/vhost.conf /etc/apache2/sites-available/000-default.conf
 
 WORKDIR /srv/app
 
-CMD ["apache2-foreground"]
\ No newline at end of file
+CMD ["apache2-foreground"]
diff --git a/LICENSE b/LICENSE
index 7fb812e..7638578 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,7 +1,6 @@
-
-AstroNomical Information System - Server API
+AstroNomical Information System - Server
   
-  Copyright: François Agneray & Chrystel Moreau 2012 - 2019
+  Copyright: CNRS - 2019
   Address:  Centre de donneeS Astrophysique de Marseille (CeSAM)              
             Laboratoire d'Astrophysique de Marseille                          
             Ple de l'Etoile, site de Chteau-Gombert                         
@@ -9,14 +8,12 @@ AstroNomical Information System - Server API
             13388 Marseille cedex 13 France                                   
             CNRS U.M.R 7326
 
-ANIS Server API is governed by the CeCILL  license under French law and
+Anis Server is governed by the CeCILL license under French law and
 abiding by the rules of distribution of free software.  You can  use, 
 modify and/ or redistribute the software under the terms of the CeCILL
 license as circulated by CEA, CNRS and INRIA at the following URL
 "http://www.cecill.info" and/or below.
 
-
-
                     CeCILL FREE SOFTWARE LICENSE AGREEMENT
                     ======================================
 
diff --git a/Makefile b/Makefile
index beda1b5..c5f2879 100644
--- a/Makefile
+++ b/Makefile
@@ -1,23 +1,23 @@
-UID := $(id -u)
-GID := $(id -g)
+UID := 1000
+GID := 1000
 
 list:
 	@echo ""
 	@echo "Useful targets:"
 	@echo ""
 	@echo "  install       > install php composer dependancies"
-	@echo "  up            > build php image and start anis-server containers for dev only (php + mailer)"
-	@echo "  start         > start anis-server containers"
-	@echo "  restart       > restart anis-server containers"
-	@echo "  stop          > stop  and kill running anis-server containers"
-	@echo "  logs          > display anis-server containers logs"
-	@echo "  shell         > shell into php anis-server container"
+	@echo "  rebuild       > rebuild php image and start containers for dev only"
+	@echo "  start         > start containers"
+	@echo "  restart       > restart containers"
+	@echo "  stop          > stop and kill running containers"
+	@echo "  status        > display stack containers status"
+	@echo "  logs          > display containers logs"
+	@echo "  shell         > shell into php container"
 	@echo "  phpunit       > run php unit test suite"
 	@echo "  phpcs         > run php code sniffer test suite"
-	@echo "  anis-init     > generate a new anis admin database and a default metamodel database (anis-server containers running needed)"
-	@echo "  dev-init      > add metadata datasets for devlopment purpose"
+	@echo "  create-db     > create a database for dev only"
+	@echo "  dev-meta      > load metamodel information for testing application"
 	@echo "  remove-pgdata > remove the metadata database"
-	@echo "  gen-key       > generate a new encryption key that you can use to encrypt data (see anis server config file)"
 	@echo ""
 
 install:
@@ -26,7 +26,7 @@ install:
 	-v $(CURDIR):/project \
 	-w /project jakzal/phpqa composer install --ignore-platform-reqs
 
-up:
+rebuild:
 	@docker-compose up --build -d
 
 start:
@@ -38,6 +38,9 @@ stop:
 	@docker-compose kill
 	@docker-compose rm -v --force
 
+status:
+	@docker-compose ps
+
 logs:
 	@docker-compose logs -f -t
 
@@ -48,21 +51,18 @@ phpunit:
 	@docker run --init -it --rm --user $(UID):$(GID) \
 	-v $(CURDIR):/project \
 	-w /project jakzal/phpqa phpdbg -qrr ./vendor/bin/phpunit --bootstrap ./tests/bootstrap.php \
-	--whitelist src --coverage-text --colors=never ./tests
+	--whitelist src --colors --coverage-html ./phpunit-coverage ./tests
 
 phpcs:
 	@docker run --init -it --rm --user $(UID):$(GID) \
 	-v $(CURDIR):/project \
-	-w /project jakzal/phpqa phpcs --standard=PSR2 --extensions=php --colors src tests
+	-w /project jakzal/phpqa phpcs --standard=PSR12 --extensions=php --colors src tests
 
-anis-init:
-	@docker-compose exec php sh ./conf-dev/anis_init.sh
+create-db:
+	@docker-compose exec php ./vendor/bin/doctrine orm:schema-tool:create
 
-dev-init:
-	@docker-compose exec php sh ./conf-dev/dev-init.sh
+dev-meta:
+	@docker-compose exec php sh ./conf-dev/dev-meta.sh
 
 remove-pgdata:
-	@docker volume rm anis-server_pgdata
-
-gen-key:
-	@docker-compose exec php php ./conf-dev/generate_encryption_key.php
\ No newline at end of file
+	@docker volume rm anis-metamodel_pgdata
diff --git a/README.md b/README.md
index 64ec381..8b3e7ff 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@ AstroNomical Information System is a generic web tool that aims to facilitate th
 
 This software allows you to control one or more databases related to astronomical projects and allows access to datasets via URLs.
 
-Anis is protected by the `CeCILL` licence (see LICENCE file at the software root).
+Anis is protected by the CeCILL licence (see LICENCE file at the software root).
 
 ## Authors
 
@@ -16,111 +16,36 @@ Here is the list of people involved in the development:
 * `Chrystel Moreau` : Laboratoire d'Astrophysique de Marseille (CNRS)
 * `Tifenn Guillas` : Laboratoire d'Astrophysique de Marseille (CNRS)
 
-# Installation guide
+## Functionalities
 
-## Prerequisites
+Anis Server allows : 
 
-Before to start to install the anis-server you must make sure that you have the following commands installed on your computer: 
+- Add project/database that will contain a set of datasets
+- Add and configure a dataset that references a table or view of a business database
+- The possibility to search in a referenced dataset with search criteria
 
-1. `make`
-2. `docker`
-3. `docker-compose`
+## Installing and starting the application
 
-You also need an Internet connection to download packages and dependancies.
+Anis Server contains a Makefile that helps the developer to install and start the application.
 
-## List of commands
+To list all operations availables just type `make` in your terminal at the root of this application.
 
-The `Makefile` at the root of the project provides a list of commands available to manage the application in a development mode.
+- To install all dependancies : `make install`
+- To start/stop/restart/status all services : `make start|stop|restart|status`
+- To display logs for all services : `make logs`
+- To open a shell command into php container : `make shell`
+- To execute tests suite : `make phpunit`
+- To execute php code sniffer : `make phpcs`
+- To create the metamodel database : `make create-db`
+- To load metamodel information for testing : `make dev-meta`
 
-To see the list of commands just open a terminal in the root of the project and type: 
+## Open API
 
-> make
+You can find an open api documentation into the `anis-server.yaml` file.
 
-**Warning**: The `docker-compose.yml` and the` Makefile` commands must be used only for development or testing but no in production mode.
+## Few examples with curl
 
-## Dependancies installation
-
-To start installing dependancies, use the following command at the software root: 
-
-> make install
-
-The `Composer.phar` software will be downloaded and it will automatically use the `composer.json` and `composer.lock` files to download all the dependancies and save them to the `vendor` directory at the root of the application.
-
-After that anis-server can work!
-
-## Start anis-server
-
-Anis server has been developed to be used with docker containers. If you open the `docker-compose.yml` file you can see all containers needed for the **development or test configuration** of anis server.
-
-`Makefile` provides a command to build install and run all the containers that you need for a fresh installation of anis-server software:
-
-> make up
-
-**Note:** This command use `docker-compose` to work. These operations may take a few minutes as it is necessary to download docker images
-
-If you want to list all anis stack containers running:
-
-> docker-compose ps
-
-And if you want to print anis stack logs:
-
-> make logs
-
-## Databases installation
-
-Anis server need at least two databases to work:
-
-1. An admin database to store users and the list of available metamodel databases.
-2. A default metamodel database to store information about business databases, projects and datasets availables.
-
-Our `Makefile` provides a command to generate these two databases:
-
-> make anis-init
-
-## Datasets for beginning to use
-
-Our `Makefile` also provides a command to add a business database and configure datasets into the default metamodel database to beginning to test or develop the anis-server. To install the test datasets type:
-
-> make dev-init
-
-## Anis server is now ready to use
-
-Open a browser and go => [http://localhost:8080/](http://localhost:8080/)
-
-Few examples: 
-
-* To list all datasets available in the default instance => [http://localhost:8080/metadata/default/dataset](http://localhost:8080/metadata/default/dataset)
-* To print all data for the obs_cat dataset with column 1, 2 and 3 => [http://localhost:8080/search/default/data/obs_cat?a=1;2;3](http://localhost:8080/search/default/data/obs_cat?a=1;2;3)
-* To print only 3 obs_cat data (search by id) => [http://localhost:8080/search/default/data/obs_cat?a=1;2;3&c=1::in::104600094|104600095|104600108](http://localhost:8080/search/default/data/obs_cat?a=1;2;3&c=1::in::104600094|104600095|104600108)
-
-# More about Anis-Server
-
-First of all, you will find the user manual at the root of the project: [MANUAL.md](MANUAL.md)
-
-## Software directories
-
-* `conf-dev`: Configuration files used by make commands and docker-compose to work
-* `public`: Web server root (index.php)
-* `src`: Source code of Anis Server
-* `test`: Anis Unit Tests `phpunit`
-
-## Key files
-
-* `public/index.php`: Bootstrap file for starting application (file used by an http web server like nginx or apache)
-* `src/settings.php`: Anis server configuration file
-* `src/routes.php`: Anis server configured routes (list all availables URL)
-
-## Technologies
-
-You can see here just a few direct links about softwares or dependancies used by anis-team for the devlopment of anis-server: 
-
-* `Slim` : [http://www.slimframework.com/](http://www.slimframework.com/)
-* `Doctrine 2` : [https://www.doctrine-project.org/](https://www.doctrine-project.org/)
-* `Swiftmailer` : [https://swiftmailer.symfony.com/](https://swiftmailer.symfony.com/)
-* `Monolog` : [https://seldaek.github.io/monolog/](https://seldaek.github.io/monolog/)
-* `Composer` : [https://getcomposer.org/](https://getcomposer.org/)
-* `PHP-FIG` : [http://www.php-fig.org/](http://www.php-fig.org/)
-* `PHP-Unit` : [http://phpunit.de/](http://phpunit.de/)
-* `Docker` : [https://www.docker.com/](https://www.docker.com/)
-* `GIT` : [http://git-scm.com/](http://git-scm.com/)
-* `CeCILL`: [http://www.cecill.info/index.en.html](http://www.cecill.info/index.en.html)
\ No newline at end of file
+* To list all datasets available in the default instance => http://localhost:8082/dataset
+* To print all data for the obs_cat dataset with column 1, 2 and 3 => http://localhost:8082/search/obs_cat?a=1;2;3
+* To count the number of data available for a request => http://localhost:8082/search/obs_cat?a=count
+* To print only 3 obs_cat data (search by id) => http://localhost:8082/search/obs_cat?a=1;2;3&c=1::in::104600094|104600095|104600108
\ No newline at end of file
diff --git a/VERSION b/VERSION
index 282895a..06a4457 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-3.0.3
\ No newline at end of file
+3.1
\ No newline at end of file
diff --git a/anis-server.yaml b/anis-server.yaml
deleted file mode 100644
index 142348f..0000000
--- a/anis-server.yaml
+++ /dev/null
@@ -1,1009 +0,0 @@
-openapi: 3.0.1
-info:
-  title: Anis Server API
-  description: 'AstroNomical Information System is a generic web tool that aims to facilitate the provision of data (Astrophysics), accessible from a database, to a community of scientists.'
-  contact:
-    email: anis@lam.fr
-  license:
-    name: CeCILL 2.1
-    url: http://www.cecill.info/licences/Licence_CeCILL_V2.1-en.html
-  version: 3.0.0
-servers:
-- url: https://anis-dev.lam.fr/
-tags:
-- name: root
-  description: Anis Server API root path
-- name: login
-  description: Set of actions about the user (registration, token, ...)
-- name: metadata
-  description: Set of actions about metamodel database management
-- name: search
-  description: Access to the data
-- name: settings
-  description: Stores data to help the administrator fill the metamodel
-paths:
-  /:
-    get:
-      tags:
-      - root
-      summary: Ensures that the service works
-      responses:
-        200:
-          description: Ensures that the service works
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ApiResponse'
-  
-  /register:
-    post:
-      tags:
-      - login
-      summary: Add a new Anis user
-      description: Action to register a new Anis user. An email with a code is send by the server to activate this new account.
-      operationId: register
-      requestBody:
-        description: Login object that needs to add a new anis user
-        content:
-          application/json:
-            schema:
-              $ref: '#/components/schemas/Login'
-        required: true
-      responses:
-        201:
-          description: New user correctly added
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/AnisUser'
-        400:
-          description: Invalid input
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ErrorResponse'
-  
-  /activate-account:
-    get:
-      tags:
-      - login
-      summary: Activate a new account
-      description: Activate a new registered user with the code sended by the server
-      operationId: activate
-      parameters:
-      - name: email
-        in: query
-        description: The email adress of the account to be activated
-        required: true
-        schema:
-          type: string
-      - name: activation_key
-        in: query
-        description: The activation code sended by the server
-        required: true
-        schema:
-          type: string
-      responses:
-        200:
-          description: Account successfully activated
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/AnisUser'
-        400:
-          description: Invalid input
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ErrorResponse'
-                
-  /login:
-    post:
-      tags:
-      - login
-      summary: Get an Anis user token
-      description: Request a json web token (jwt) to the Anis Server with a couple of email and password
-      operationId: login
-      requestBody:
-        description: Login object that needs to get a new token
-        content:
-          application/json:
-            schema:
-              $ref: '#/components/schemas/Login'
-        required: true
-      responses:
-        200:
-          description: Returns a new token
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/Token'
-        400:
-          description: Invalid input
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ErrorResponse'
-                
-  /new-password:
-    post:
-      tags:
-      - login
-      summary: Ask for a new password
-      description: Request the generation of a new password that will be sent by email
-      operationId: newPassword
-      requestBody:
-        description: The email address of the account for which the new password will be generated
-        content:
-          application/json:
-            schema:
-              properties:
-                email:
-                  type: string
-                  example: user@provider.fr
-        required: true
-      responses:
-        200:
-          description: New password generated
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ApiResponse'
-        400:
-          description: Invalid input
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ErrorResponse'
-
-  /change-password:
-    post:
-      tags:
-        - login
-      summary: Anis user can change his account password
-      description: Ask anis server with the actual and the new password
-      operationId: changePassword
-      requestBody:
-        description: The email address and the actual password of the account for which the new password will be change + the new password
-        content:
-          application/json:
-            schema:
-              $ref: '#/components/schemas/ChangePassword'
-        required: true
-      responses:
-        200:
-          description: Password changed
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ApiResponse'
-        400:
-          description: Invalid input
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ErrorResponse'
-  
-  /metadata/database:
-    get:
-      tags:
-        - metadata
-      summary: List all available databases
-      description: Anis can connect to databases (PostgreSQL, MySQL, SQLite, Oracle ...). This action lists the databases already registered and where anis can connect.
-      operationId: getDatabaseList
-      responses:
-        200:
-          description: Databases list
-          content:
-            application/json:
-              schema:
-                type: array
-                items:
-                  $ref: '#/components/schemas/Database'
-    post:
-      tags:
-        - metadata
-      summary: Add a new database
-      description: Anis can connect to databases (PostgreSQL, MySQL, SQLite, Oracle ...). This action add a new data source.
-      operationId: addDatabase
-      requestBody:
-        description: Database form object that needs to be added to the store
-        content:
-          application/json:
-            schema:
-              $ref: '#/components/schemas/DatabaseForm'
-        required: true
-      responses:
-        200:
-          description: Database added
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/Database'
-        400:
-          description: Invalid input
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ErrorResponse'
-
-  /metadata/database/{id}:
-    get:
-      tags:
-        - metadata
-      summary: Find a database by ID
-      description: Returns a single database registered
-      operationId: getDatabase
-      parameters:
-        - name: id
-          in: path
-          description: ID of database to return
-          required: true
-          schema:
-            type: integer
-      responses:
-        200:
-          description: Successful operation
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/Database'
-        404:
-          description: Database not found
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ErrorResponse'
-    put:
-      tags:
-        - metadata
-      summary: Updates a database with form data
-      operationId: editDatabase
-      parameters:
-        - name: id
-          in: path
-          description: ID of database to return
-          required: true
-          schema:
-            type: integer
-      requestBody:
-        description: Database form object that needs to be added to the store
-        content:
-          application/json:
-            schema:
-              $ref: '#/components/schemas/DatabaseForm'
-        required: true
-      responses:
-        200:
-          description: Database edited
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/Database'
-        400:
-          description: Invalid input
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ErrorResponse'
-        404:
-          description: Database not found
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ErrorResponse'
-    delete:
-      tags:
-        - metadata
-      summary: Delete a registered database
-      operationId: deleteDatabase
-      parameters:
-        - name: id
-          in: path
-          description: ID of database to return
-          required: true
-          schema:
-            type: integer
-      responses:
-        200:
-          description: Successful operation
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ApiResponse'
-        404:
-          description: Database not found
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ErrorResponse'
-      
-  /metadata/database/{id}/table:
-    get:
-      tags:
-        - metadata
-      summary: Get tables and views list
-      description: Get tables and views listed in the database identified by the ID parameter
-      operationId: tableListDatabase
-      parameters:
-        - name: id
-          in: path
-          description: ID of database to return
-          required: true
-          schema:
-            type: integer
-      responses:
-        200:
-          description: Tables and views list
-          content:
-            application/json:
-              schema:
-                type: array
-                items:
-                  type: string
-        404:
-          description: Database not found
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ErrorResponse'
-
-  /metadata/project:
-    get:
-      tags:
-        - metadata
-      summary: List all available projects
-      description: All searchable datasets are listed in one or more projects and a project is attached to a database
-      operationId: getProjectList
-      responses:
-        200:
-          description: Project list
-          content:
-            application/json:
-              schema:
-                type: array
-                items:
-                  $ref: '#/components/schemas/Project'
-    post:
-      tags:
-        - metadata
-      summary: Add a new project
-      description: All searchable datasets are listed in one or more projects and a project is attached to a database. This action add a new data project.
-      operationId: addProject
-      requestBody:
-        description: Project form object that needs to be added to the store
-        content:
-          application/json:
-            schema:
-              $ref: '#/components/schemas/Project'
-        required: true
-      responses:
-        200:
-          description: Project added
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/Project'
-        400:
-          description: Invalid input
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ErrorResponse'
-
-  /metadata/project/{name}:
-    get:
-      tags:
-        - metadata
-      summary: Find a project by name
-      description: Returns a single project registered
-      operationId: getProject
-      parameters:
-        - name: name
-          in: path
-          description: Name of project to return
-          required: true
-          schema:
-            type: string
-      responses:
-        200:
-          description: Successful operation
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/Project'
-        404:
-          description: Project not found
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ErrorResponse'
-    put:
-      tags:
-        - metadata
-      summary: Updates a project with form data
-      operationId: editProject
-      parameters:
-        - name: name
-          in: path
-          description: Name of project to return
-          required: true
-          schema:
-            type: string
-      requestBody:
-        description: Project form object that needs to be edited to the store
-        content:
-          application/json:
-            schema:
-              $ref: '#/components/schemas/Project'
-        required: true
-      responses:
-        200:
-          description: Project edited
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/Project'
-        400:
-          description: Invalid input
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ErrorResponse'
-        404:
-          description: Project not found
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ErrorResponse'
-    delete:
-      tags:
-        - metadata
-      summary: Delete a registered project
-      operationId: deleteProject
-      parameters:
-        - name: name
-          in: path
-          description: Name of project to return
-          required: true
-          schema:
-            type: string
-      responses:
-        200:
-          description: Successful operation
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ApiResponse'
-        404:
-          description: Project not found
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ErrorResponse'
-
-  /metadata/dataset:
-    get:
-      tags:
-        - metadata
-      summary: List all available datasets
-      description: Get all searchable datasets
-      operationId: getDatasetList
-      responses:
-        200:
-          description: Dataset list
-          content:
-            application/json:
-              schema:
-                type: array
-                items:
-                  $ref: '#/components/schemas/Dataset'
-    post:
-      tags:
-        - metadata
-      summary: Add a new dataset
-      description: Add a new dataset. This action add automatically the associated attributes (columns)
-      operationId: addDataset
-      requestBody:
-        description: Dataset form object that needs to be added to the store
-        content:
-          application/json:
-            schema:
-              $ref: '#/components/schemas/Dataset'
-        required: true
-      responses:
-        200:
-          description: Dataset added
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/Dataset'
-        400:
-          description: Invalid input
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ErrorResponse'
-                
-  /metadata/dataset/{name}:
-    get:
-      tags:
-        - metadata
-      summary: Find a dataset by name
-      description: Returns a single dataset registered
-      operationId: getDataset
-      parameters:
-        - name: name
-          in: path
-          description: Name of dataset to return
-          required: true
-          schema:
-            type: string
-      responses:
-        200:
-          description: Successful operation
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/Dataset'
-        404:
-          description: Dataset not found
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ErrorResponse'
-    put:
-      tags:
-        - metadata
-      summary: Updates a dataset with form data
-      operationId: editDataset
-      parameters:
-        - name: name
-          in: path
-          description: Name of dataset to return
-          required: true
-          schema:
-            type: string
-      requestBody:
-        description: Dataset form object that needs to be edited to the store
-        content:
-          application/json:
-            schema:
-              $ref: '#/components/schemas/Dataset'
-        required: true
-      responses:
-        200:
-          description: Dataset edited
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/Dataset'
-        400:
-          description: Invalid input
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ErrorResponse'
-        404:
-          description: Dataset not found
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ErrorResponse'
-    delete:
-      tags:
-        - metadata
-      summary: Delete a registered dataset
-      operationId: deleteDataset
-      parameters:
-        - name: name
-          in: path
-          description: Name of dataset to return
-          required: true
-          schema:
-            type: string
-      responses:
-        200:
-          description: Successful operation
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ApiResponse'
-        404:
-          description: Dataset not found
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ErrorResponse'
-
-  /metadata/dataset/{name}/attribute:
-    get:
-      tags:
-        - metadata
-      summary: Retrieve all attributes for a dataset
-      operationId: getAttributes
-      parameters:
-        - name: name
-          in: path
-          description: Name of dataset to return
-          required: true
-          schema:
-            type: string
-      responses:
-        200:
-          description: Successful operation
-          content:
-            application/json:
-              schema:
-                type: array
-                items:
-                  $ref: '#/components/schemas/Attribute'
-        404:
-          description: Dataset not found
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ErrorResponse'
-
-    put:
-      tags:
-        - metadata
-      summary: Updates all attributes for a dataset
-      operationId: editAttributes
-      parameters:
-        - name: name
-          in: path
-          description: Name of dataset to return
-          required: true
-          schema:
-            type: string
-      responses:
-        200:
-          description: Successful operation
-          content:
-            application/json:
-              schema:
-                type: array
-                items:
-                  $ref: '#/components/schemas/Attribute'
-        404:
-          description: Dataset not found
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ErrorResponse'
-
-  /search/meta/{dname}:
-    get:
-      tags:
-        - search
-      summary: Retrieve all metdata of a search
-      parameters:
-        - name: dname
-          in: path
-          description: Name of dataset about the search
-          required: true
-          schema:
-            type: string
-        - name: c
-          in: query
-          description: Search criteria list separated by a semicolon
-          required: true
-          schema:
-            type: string
-      responses:
-        200:
-          description: Successful operation
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/MetaResponse'
-        400:
-          description: Invalid input
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ErrorResponse'
-        404:
-          description: Dataset not found
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ErrorResponse'
-
-  /search/data/{dname}:
-    get:
-      tags:
-        - search
-      summary: Retrieve all data of a search
-      parameters:
-        - name: dname
-          in: path
-          description: Name of dataset about the search
-          required: true
-          schema:
-            type: string
-        - name: c
-          in: query
-          description: Search criteria list separated by a semicolon
-          required: true
-          schema:
-            type: string
-        - name: a
-          in: query
-          description: Search id attributes output list separated by a semicolon
-          required: true
-          schema:
-            type: string
-        - name: o
-          in: query
-          description: Display order list of the search separated by a semicolon. This parameter is mandatory for pagination (p)
-          required: false
-          schema:
-            type: string
-        - name: p
-          in: query
-          description: Pagination settings separated by a colon
-          required: false
-          schema:
-            type: string
-      responses:
-        200:
-          description: Successful operation
-          content:
-            application/json:
-              schema:
-                type: array
-                items:
-                  type: object
-        400:
-          description: Invalid input
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ErrorResponse'
-        404:
-          description: Dataset not found
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ErrorResponse'
-
-components:
-  schemas:
-    ApiResponse:
-      type: object
-      properties:
-        code:
-          type: integer
-          format: int32
-        type:
-          type: string
-        message:
-          type: string
-    ErrorResponse:
-      type: object
-      properties:
-        error:
-          type: string
-          example: Invalid request
-        error_description:
-          type: string
-          example: Param email needed to register a new user
-    Token:
-      type: object
-      properties:
-        email:
-          type: string
-          example: user@provider.fr
-        token:
-          type: string
-    Login:
-      type: object
-      properties:
-        email:
-          type: string
-          example: user@provider.fr
-        password:
-          type: string
-          example: my_password
-    AnisUser:
-      type: object
-      properties:
-        email:
-          type: string
-          example: user@provider.fr
-        activated:
-          type: boolean
-          example: false
-        adminsi:
-          type: boolean
-          example: false
-        superuser:
-          type: boolean
-          example: false
-        id_group:
-          type: integer
-          example: 1
-    ChangePassword:
-      type: object
-      properties:
-        email:
-          type: string
-          example: user@provider.fr
-        password:
-          type: string
-          example: my_password
-        new_password:
-          type: string
-          example: my_new_password
-    DatabaseForm:
-      type: object
-      properties:
-        label:
-          type: string
-          example: Database
-        dbname:
-          type: string
-          example: db1
-        dbtype:
-          type: string
-          example: pgsql
-        dbhost:
-          type: string
-          example: dbserver
-        dbport:
-          type: integer
-          example: 5432
-        dblogin:
-          type: string
-          example: dbuser
-        dbpassword:
-          type: string
-          example: dbpassword
-    Database:
-      allOf:
-        - type: object
-          properties:
-            id:
-              type: string
-              example: 1
-        - $ref: '#/components/schemas/DatabaseForm'
-    Project:
-      type: object
-      properties:
-        name:
-          type: string
-          example: my_project
-        label:
-          type: string
-          example: My Project
-        description:
-          type: string
-          example: Project description
-        link:
-          type: string
-          example: http://myproject-website.com
-        manager:
-          type: string
-          example: M. Dupont
-        id_database:
-          type: integer
-          example: 1
-    Dataset:
-      type: object
-      properties:
-        name:
-          type: string
-          example: my_dataset
-        table_ref:
-          type: string
-          example: database_table_name
-        label:
-          type: string
-          example: My dataset
-        description:
-          type: string
-          example: My dataset description
-        display:
-          type: integer
-          example: 10
-        count:
-          type: integer
-          example: 1500
-        vo:
-          type: boolean
-          example: true
-        data_path:
-          type: string
-          example: /mnt/my_data
-        project_name:
-          type: string
-          example: my_project
-        id_dataset_family:
-          type: integer
-          example: 1
-    Attribute:
-      type: object
-      properties:
-        id:
-          type: integer
-          example: 1
-        name:
-          type: string
-          example: ra
-        table_name:
-          type: string
-          example: obs_cat
-        label:
-          type: string
-          example: Main_catalog
-        form_label:
-          type: string
-          example: Main obervations catalog
-        description:
-          type: string
-          example: Description for this catalog
-        output_display:
-          type: integer
-        criteria_display:
-          type: integer
-        search_flag:
-          type: string
-        search_type:
-          type: string
-        type:
-          type: string
-        operator:
-          type: string
-        min:
-          type: number
-        max:
-          type: number
-        placeholder_min:
-          type: string
-        placeholder_max:
-          type: string
-        uri_action:
-          type: string
-        renderer:
-          type: string
-        display_detail:
-          type: string
-        selected:
-          type: boolean
-        order_by:
-          type: boolean
-        order_display:
-          type: integer
-        detail:
-          type: boolean
-        renderer_detail:
-          type: string
-        vo_utype:
-          type: string
-        vo_ucd:
-          type: string
-        vo_unit:
-          type: string
-        vo_description:
-          type: string
-        vo_datatype:
-          type: string
-        vo_size:
-          type: integer
-        id_criteria_family:
-          type: integer
-        id_output_family:
-          type: integer
-        id_category:
-          type: integer
-    MetaResponse:
-      type: object
-      properties:
-        dataset-selected:
-          type: string
-        total-items:
-          type: integer
-        url:
-          type: string
\ No newline at end of file
diff --git a/app/constants.php b/app/constants.php
new file mode 100644
index 0000000..75400ac
--- /dev/null
+++ b/app/constants.php
@@ -0,0 +1,18 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+define('GET', 'GET');
+define('POST', 'POST');
+define('PUT', 'PUT');
+define('DELETE', 'DELETE');
+define('OPTIONS', 'OPTIONS');
+define('SETTINGS', 'settings');
diff --git a/app/dependencies.php b/app/dependencies.php
new file mode 100644
index 0000000..e64cc63
--- /dev/null
+++ b/app/dependencies.php
@@ -0,0 +1,113 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+use Psr\Container\ContainerInterface;
+
+// Load settings
+$container->set(SETTINGS, function () {
+    return include __DIR__ . '/../app/settings.php';
+});
+
+// Doctrine factory
+$container->set('em', function (ContainerInterface $c) {
+    $settings = $c->get(SETTINGS)['database'];
+    $dc = \Doctrine\ORM\Tools\Setup::createAnnotationMetadataConfiguration(
+        array('src/Entity'),
+        $settings['dev_mode']
+    );
+    $dc->setProxyDir($settings['path_proxy']);
+    if ($settings['dev_mode']) {
+        $dc->setAutoGenerateProxyClasses(true);
+    } else {
+        $dc->setAutoGenerateProxyClasses(false);
+    }
+    return \Doctrine\ORM\EntityManager::create($settings['connection_options'], $dc);
+});
+
+// Monolog factory
+$container->set('logger', function (ContainerInterface $c) {
+    $loggerSettings = $c->get('settings')['logger'];
+    $logger = new \Monolog\Logger($loggerSettings['name']);
+    $logger->pushProcessor(new \Monolog\Processor\UidProcessor());
+    $logger->pushHandler(new \Monolog\Handler\StreamHandler($loggerSettings['path'], $loggerSettings['level']));
+    return $logger;
+});
+
+// Actions
+$container->set('App\Action\RootAction', function () {
+    return new App\Action\RootAction();
+});
+
+$container->set('App\Action\DatabaseListAction', function (ContainerInterface $c) {
+    return new App\Action\DatabaseListAction($c->get('em'));
+});
+
+$container->set('App\Action\DatabaseAction', function (ContainerInterface $c) {
+    return new App\Action\DatabaseAction($c->get('em'));
+});
+
+$container->set('App\Action\TableListAction', function (ContainerInterface $c) {
+    return new App\Action\TableListAction($c->get('em'), new App\Utils\DBALConnectionFactory());
+});
+
+$container->set('App\Action\ProjectListAction', function (ContainerInterface $c) {
+    return new App\Action\ProjectListAction($c->get('em'));
+});
+
+$container->set('App\Action\ProjectAction', function (ContainerInterface $c) {
+    return new App\Action\ProjectAction($c->get('em'));
+});
+
+$container->set('App\Action\FamilyListAction', function (ContainerInterface $c) {
+    return new App\Action\FamilyListAction($c->get('em'));
+});
+
+$container->set('App\Action\FamilyAction', function (ContainerInterface $c) {
+    return new App\Action\FamilyAction($c->get('em'));
+});
+
+$container->set('App\Action\OutputCategoryListAction', function (ContainerInterface $c) {
+    return new App\Action\OutputCategoryListAction($c->get('em'));
+});
+
+$container->set('App\Action\OutputCategoryAction', function (ContainerInterface $c) {
+    return new App\Action\OutputCategoryAction($c->get('em'));
+});
+
+$container->set('App\Action\DatasetListAction', function (ContainerInterface $c) {
+    return new App\Action\DatasetListAction($c->get('em'), new App\Utils\DBALConnectionFactory());
+});
+
+$container->set('App\Action\DatasetAction', function (ContainerInterface $c) {
+    return new App\Action\DatasetAction($c->get('em'), new App\Utils\DBALConnectionFactory());
+});
+
+$container->set('App\Action\AttributeListAction', function (ContainerInterface $c) {
+    return new App\Action\AttributeListAction($c->get('em'));
+});
+
+$container->set('App\Action\AttributeAction', function (ContainerInterface $c) {
+    return new App\Action\AttributeAction($c->get('em'));
+});
+
+$container->set('App\Action\SearchAction', function (ContainerInterface $c) {
+    return new App\Action\SearchAction(
+        $c->get('em'),
+        new App\Utils\DBALConnectionFactory(),
+        new App\Utils\Operator\OperatorFactory()
+    );
+});
+
+// Middlewares
+$container->set('App\Middleware\AuthorizationMiddleware', function (ContainerInterface $c) {
+    return new App\Middleware\AuthorizationMiddleware($c->get('em'), $c->get(SETTINGS)['token_options']);
+});
diff --git a/app/middlewares.php b/app/middlewares.php
new file mode 100644
index 0000000..b029af0
--- /dev/null
+++ b/app/middlewares.php
@@ -0,0 +1,14 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+$app->add(new App\Middleware\JsonBodyParserMiddleware());
+$app->add(new App\Middleware\ContentTypeJsonMiddleware());
diff --git a/app/routes.php b/app/routes.php
new file mode 100644
index 0000000..42c5ec3
--- /dev/null
+++ b/app/routes.php
@@ -0,0 +1,29 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+use Slim\Routing\RouteCollectorProxy;
+
+$app->get('/', App\Action\RootAction::class);
+$app->map([OPTIONS, GET, POST], '/database', App\Action\DatabaseListAction::class);
+$app->map([OPTIONS, GET, PUT, DELETE], '/database/{id}', App\Action\DatabaseAction::class);
+$app->map([OPTIONS, GET], '/database/{id}/table', App\Action\TableListAction::class);
+$app->map([OPTIONS, GET, POST], '/project', App\Action\ProjectListAction::class);
+$app->map([OPTIONS, GET, PUT, DELETE], '/project/{name}', App\Action\ProjectAction::class);
+$app->map([OPTIONS, GET, POST], '/family/{type}', App\Action\FamilyListAction::class);
+$app->map([OPTIONS, GET, PUT, DELETE], '/family/{type}/{id}', App\Action\FamilyAction::class);
+$app->map([OPTIONS, GET, POST], '/output-category', App\Action\OutputCategoryListAction::class);
+$app->map([OPTIONS, GET, PUT, DELETE], '/output-category/{id}', App\Action\OutputCategoryAction::class);
+$app->map([OPTIONS, GET, POST], '/dataset', App\Action\DatasetListAction::class);
+$app->map([OPTIONS, GET, PUT, DELETE], '/dataset/{name}', App\Action\DatasetAction::class);
+$app->map([OPTIONS, GET], '/dataset/{name}/attribute', App\Action\AttributeListAction::class);
+$app->map([OPTIONS, GET, PUT], '/dataset/{name}/attribute/{id}', App\Action\AttributeAction::class);
+$app->get('/search/{dname}', App\Action\SearchAction::class);
diff --git a/app/settings.php b/app/settings.php
new file mode 100644
index 0000000..c56912f
--- /dev/null
+++ b/app/settings.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+return [
+    'displayErrorDetails' => getenv('DISPLAY_ERROR_DETAILS'),
+    'database' => [
+        'path_proxy' => getenv('DATABASE_PATH_PROXY'),
+        'dev_mode' => getenv('DATABASE_DEV_MODE'),
+        'connection_options' => [
+            'driver' => getenv('DATABASE_CO_DRIVER'),
+            'path' => getenv('DATABASE_CO_PATH'),
+            'host' => getenv('DATABASE_CO_HOST'),
+            'port' => (int) getenv('DATABASE_CO_PORT'),
+            'dbname' => getenv('DATABASE_CO_DBNAME'),
+            'user' => getenv('DATABASE_CO_USER'),
+            'password' => getenv('DATABASE_CO_PASSWORD')
+        ],
+    ],
+    'logger' => [
+        'name' => getenv('LOGGER_NAME'),
+        'path' => getenv('LOGGER_PATH'),
+        'level' => getenv('LOGGER_LEVEL')
+    ]
+];
diff --git a/cli-config.php b/cli-config.php
index 4039d69..1436d10 100644
--- a/cli-config.php
+++ b/cli-config.php
@@ -2,17 +2,17 @@
 // File needed by doctrine cli
 require 'vendor/autoload.php';
 
-$settings = require './src/settings.php';
-$adminDb = $settings['settings']['admin_db'];
+$settings = require './app/settings.php';
+$database = $settings['database'];
 
-$c = \Doctrine\ORM\Tools\Setup::createAnnotationMetadataConfiguration(array('src/Entity/Admin'), $adminDb['dev_mode']);
-$c->setProxyDir(getcwd() . '/' . $adminDb['path_proxy']);
-if ($adminDb['dev_mode']) {
+$c = \Doctrine\ORM\Tools\Setup::createAnnotationMetadataConfiguration(array('src/Entity'), $database['dev_mode']);
+$c->setProxyDir(getcwd() . '/' . $database['path_proxy']);
+if ($database['dev_mode']) {
     $c->setAutoGenerateProxyClasses(true);
 } else {
     $c->setAutoGenerateProxyClasses(false);
 }
-$em = \Doctrine\ORM\EntityManager::create($adminDb['connection_options'], $c);
+$em = \Doctrine\ORM\EntityManager::create($database['connection_options'], $c);
 
 $helpers = new Symfony\Component\Console\Helper\HelperSet(array(
     'db' => new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($em->getConnection()),
diff --git a/composer.json b/composer.json
index d0c4c18..6bf0294 100644
--- a/composer.json
+++ b/composer.json
@@ -1,23 +1,32 @@
 {
-    "name": "cesamsi/anis-v3-server",
-    "description": "CMS used for astronomical project",
+    "name": "anis/server",
+    "type": "project",
+    "description": "Anis Server is used to configure information system CMS and to search into a business database",
+    "license": "CeCILL v2.1",
     "authors": [
         {
             "name": "François Agneray",
             "email": "francois.agneray@lam.fr"
+        },
+        {
+            "name": "Chrystel Moreau",
+            "email": "chrystel.moreau@lam.fr"
+        },
+        {
+            "name": "Tifenn Guillas",
+            "email": "tifenn.guillas@lam.fr"
         }
     ],
     "require": {
-        "slim/slim": "^3.8",
-        "doctrine/orm": "^2.5",
-        "monolog/monolog": "^1.23",
-        "lcobucci/jwt": "^3.2",
-        "swiftmailer/swiftmailer": "^6.0",
-        "php-amqplib/php-amqplib": "^2.10"
+        "slim/slim": "^4.0",
+        "nyholm/psr7": "^1.2",
+        "nyholm/psr7-server": "^0.3.0",
+        "php-di/php-di": "^6.0",
+        "monolog/monolog": "^2.0",
+        "doctrine/orm": "^2.6"
     },
     "require-dev": {
-        "phpunit/phpunit": "^7.2",
-        "phpunit/dbunit": "^4.0"
+        "phpunit/phpunit": "^7.2"
     },
     "autoload": {
 		"psr-4": {
diff --git a/composer.lock b/composer.lock
index e02f710..8a59269 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,51 +4,20 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "a2184ef90f51603d72f1a7f49ec35c4a",
+    "content-hash": "41b0ad136ffb7b6f8f4c93825e72e5be",
     "packages": [
-        {
-            "name": "container-interop/container-interop",
-            "version": "1.2.0",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/container-interop/container-interop.git",
-                "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/container-interop/container-interop/zipball/79cbf1341c22ec75643d841642dd5d6acd83bdb8",
-                "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8",
-                "shasum": ""
-            },
-            "require": {
-                "psr/container": "^1.0"
-            },
-            "type": "library",
-            "autoload": {
-                "psr-4": {
-                    "Interop\\Container\\": "src/Interop/Container/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "description": "Promoting the interoperability of container objects (DIC, SL, etc.)",
-            "homepage": "https://github.com/container-interop/container-interop",
-            "time": "2017-02-14T19:40:03+00:00"
-        },
         {
             "name": "doctrine/annotations",
-            "version": "v1.7.0",
+            "version": "v1.8.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/doctrine/annotations.git",
-                "reference": "fa4c4e861e809d6a1103bd620cce63ed91aedfeb"
+                "reference": "904dca4eb10715b92569fbcd79e201d5c349b6bc"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/doctrine/annotations/zipball/fa4c4e861e809d6a1103bd620cce63ed91aedfeb",
-                "reference": "fa4c4e861e809d6a1103bd620cce63ed91aedfeb",
+                "url": "https://api.github.com/repos/doctrine/annotations/zipball/904dca4eb10715b92569fbcd79e201d5c349b6bc",
+                "reference": "904dca4eb10715b92569fbcd79e201d5c349b6bc",
                 "shasum": ""
             },
             "require": {
@@ -57,7 +26,7 @@
             },
             "require-dev": {
                 "doctrine/cache": "1.*",
-                "phpunit/phpunit": "^7.5@dev"
+                "phpunit/phpunit": "^7.5"
             },
             "type": "library",
             "extra": {
@@ -103,20 +72,20 @@
                 "docblock",
                 "parser"
             ],
-            "time": "2019-08-08T18:11:40+00:00"
+            "time": "2019-10-01T18:55:10+00:00"
         },
         {
             "name": "doctrine/cache",
-            "version": "v1.8.0",
+            "version": "1.10.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/doctrine/cache.git",
-                "reference": "d768d58baee9a4862ca783840eca1b9add7a7f57"
+                "reference": "382e7f4db9a12dc6c19431743a2b096041bcdd62"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/doctrine/cache/zipball/d768d58baee9a4862ca783840eca1b9add7a7f57",
-                "reference": "d768d58baee9a4862ca783840eca1b9add7a7f57",
+                "url": "https://api.github.com/repos/doctrine/cache/zipball/382e7f4db9a12dc6c19431743a2b096041bcdd62",
+                "reference": "382e7f4db9a12dc6c19431743a2b096041bcdd62",
                 "shasum": ""
             },
             "require": {
@@ -127,7 +96,7 @@
             },
             "require-dev": {
                 "alcaeus/mongo-php-adapter": "^1.1",
-                "doctrine/coding-standard": "^4.0",
+                "doctrine/coding-standard": "^6.0",
                 "mongodb/mongodb": "^1.1",
                 "phpunit/phpunit": "^7.0",
                 "predis/predis": "~1.0"
@@ -138,7 +107,7 @@
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.8.x-dev"
+                    "dev-master": "1.9.x-dev"
                 }
             },
             "autoload": {
@@ -151,6 +120,10 @@
                 "MIT"
             ],
             "authors": [
+                {
+                    "name": "Guilherme Blanco",
+                    "email": "guilhermeblanco@gmail.com"
+                },
                 {
                     "name": "Roman Borschel",
                     "email": "roman@code-factory.org"
@@ -159,10 +132,6 @@
                     "name": "Benjamin Eberlei",
                     "email": "kontakt@beberlei.de"
                 },
-                {
-                    "name": "Guilherme Blanco",
-                    "email": "guilhermeblanco@gmail.com"
-                },
                 {
                     "name": "Jonathan Wage",
                     "email": "jonwage@gmail.com"
@@ -172,26 +141,33 @@
                     "email": "schmittjoh@gmail.com"
                 }
             ],
-            "description": "Caching library offering an object-oriented API for many cache backends",
-            "homepage": "https://www.doctrine-project.org",
+            "description": "PHP Doctrine Cache library is a popular cache implementation that supports many different drivers such as redis, memcache, apc, mongodb and others.",
+            "homepage": "https://www.doctrine-project.org/projects/cache.html",
             "keywords": [
+                "abstraction",
+                "apcu",
                 "cache",
-                "caching"
+                "caching",
+                "couchdb",
+                "memcached",
+                "php",
+                "redis",
+                "xcache"
             ],
-            "time": "2018-08-21T18:01:43+00:00"
+            "time": "2019-11-29T15:36:20+00:00"
         },
         {
             "name": "doctrine/collections",
-            "version": "v1.6.2",
+            "version": "1.6.4",
             "source": {
                 "type": "git",
                 "url": "https://github.com/doctrine/collections.git",
-                "reference": "c5e0bc17b1620e97c968ac409acbff28b8b850be"
+                "reference": "6b1e4b2b66f6d6e49983cebfe23a21b7ccc5b0d7"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/doctrine/collections/zipball/c5e0bc17b1620e97c968ac409acbff28b8b850be",
-                "reference": "c5e0bc17b1620e97c968ac409acbff28b8b850be",
+                "url": "https://api.github.com/repos/doctrine/collections/zipball/6b1e4b2b66f6d6e49983cebfe23a21b7ccc5b0d7",
+                "reference": "6b1e4b2b66f6d6e49983cebfe23a21b7ccc5b0d7",
                 "shasum": ""
             },
             "require": {
@@ -219,6 +195,10 @@
                 "MIT"
             ],
             "authors": [
+                {
+                    "name": "Guilherme Blanco",
+                    "email": "guilhermeblanco@gmail.com"
+                },
                 {
                     "name": "Roman Borschel",
                     "email": "roman@code-factory.org"
@@ -227,10 +207,6 @@
                     "name": "Benjamin Eberlei",
                     "email": "kontakt@beberlei.de"
                 },
-                {
-                    "name": "Guilherme Blanco",
-                    "email": "guilhermeblanco@gmail.com"
-                },
                 {
                     "name": "Jonathan Wage",
                     "email": "jonwage@gmail.com"
@@ -248,7 +224,7 @@
                 "iterators",
                 "php"
             ],
-            "time": "2019-06-09T13:48:14+00:00"
+            "time": "2019-11-13T13:07:11+00:00"
         },
         {
             "name": "doctrine/common",
@@ -335,31 +311,30 @@
         },
         {
             "name": "doctrine/dbal",
-            "version": "v2.9.2",
+            "version": "v2.10.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/doctrine/dbal.git",
-                "reference": "22800bd651c1d8d2a9719e2a3dc46d5108ebfcc9"
+                "reference": "0c9a646775ef549eb0a213a4f9bd4381d9b4d934"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/doctrine/dbal/zipball/22800bd651c1d8d2a9719e2a3dc46d5108ebfcc9",
-                "reference": "22800bd651c1d8d2a9719e2a3dc46d5108ebfcc9",
+                "url": "https://api.github.com/repos/doctrine/dbal/zipball/0c9a646775ef549eb0a213a4f9bd4381d9b4d934",
+                "reference": "0c9a646775ef549eb0a213a4f9bd4381d9b4d934",
                 "shasum": ""
             },
             "require": {
                 "doctrine/cache": "^1.0",
                 "doctrine/event-manager": "^1.0",
                 "ext-pdo": "*",
-                "php": "^7.1"
+                "php": "^7.2"
             },
             "require-dev": {
-                "doctrine/coding-standard": "^5.0",
-                "jetbrains/phpstorm-stubs": "^2018.1.2",
-                "phpstan/phpstan": "^0.10.1",
-                "phpunit/phpunit": "^7.4",
-                "symfony/console": "^2.0.5|^3.0|^4.0",
-                "symfony/phpunit-bridge": "^3.4.5|^4.0.5"
+                "doctrine/coding-standard": "^6.0",
+                "jetbrains/phpstorm-stubs": "^2019.1",
+                "phpstan/phpstan": "^0.11.3",
+                "phpunit/phpunit": "^8.4.1",
+                "symfony/console": "^2.0.5|^3.0|^4.0|^5.0"
             },
             "suggest": {
                 "symfony/console": "For helpful console commands such as SQL execution and import of files."
@@ -370,7 +345,7 @@
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "2.9.x-dev",
+                    "dev-master": "2.10.x-dev",
                     "dev-develop": "3.0.x-dev"
                 }
             },
@@ -384,6 +359,10 @@
                 "MIT"
             ],
             "authors": [
+                {
+                    "name": "Guilherme Blanco",
+                    "email": "guilhermeblanco@gmail.com"
+                },
                 {
                     "name": "Roman Borschel",
                     "email": "roman@code-factory.org"
@@ -392,10 +371,6 @@
                     "name": "Benjamin Eberlei",
                     "email": "kontakt@beberlei.de"
                 },
-                {
-                    "name": "Guilherme Blanco",
-                    "email": "guilhermeblanco@gmail.com"
-                },
                 {
                     "name": "Jonathan Wage",
                     "email": "jonwage@gmail.com"
@@ -406,27 +381,38 @@
             "keywords": [
                 "abstraction",
                 "database",
+                "db2",
                 "dbal",
+                "mariadb",
+                "mssql",
                 "mysql",
-                "persistence",
+                "oci8",
+                "oracle",
+                "pdo",
                 "pgsql",
-                "php",
-                "queryobject"
-            ],
-            "time": "2018-12-31T03:27:51+00:00"
+                "postgresql",
+                "queryobject",
+                "sasql",
+                "sql",
+                "sqlanywhere",
+                "sqlite",
+                "sqlserver",
+                "sqlsrv"
+            ],
+            "time": "2019-11-03T16:50:43+00:00"
         },
         {
             "name": "doctrine/event-manager",
-            "version": "v1.0.0",
+            "version": "1.1.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/doctrine/event-manager.git",
-                "reference": "a520bc093a0170feeb6b14e9d83f3a14452e64b3"
+                "reference": "629572819973f13486371cb611386eb17851e85c"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/doctrine/event-manager/zipball/a520bc093a0170feeb6b14e9d83f3a14452e64b3",
-                "reference": "a520bc093a0170feeb6b14e9d83f3a14452e64b3",
+                "url": "https://api.github.com/repos/doctrine/event-manager/zipball/629572819973f13486371cb611386eb17851e85c",
+                "reference": "629572819973f13486371cb611386eb17851e85c",
                 "shasum": ""
             },
             "require": {
@@ -436,7 +422,7 @@
                 "doctrine/common": "<2.9@dev"
             },
             "require-dev": {
-                "doctrine/coding-standard": "^4.0",
+                "doctrine/coding-standard": "^6.0",
                 "phpunit/phpunit": "^7.0"
             },
             "type": "library",
@@ -455,6 +441,10 @@
                 "MIT"
             ],
             "authors": [
+                {
+                    "name": "Guilherme Blanco",
+                    "email": "guilhermeblanco@gmail.com"
+                },
                 {
                     "name": "Roman Borschel",
                     "email": "roman@code-factory.org"
@@ -463,10 +453,6 @@
                     "name": "Benjamin Eberlei",
                     "email": "kontakt@beberlei.de"
                 },
-                {
-                    "name": "Guilherme Blanco",
-                    "email": "guilhermeblanco@gmail.com"
-                },
                 {
                     "name": "Jonathan Wage",
                     "email": "jonwage@gmail.com"
@@ -480,27 +466,29 @@
                     "email": "ocramius@gmail.com"
                 }
             ],
-            "description": "Doctrine Event Manager component",
+            "description": "The Doctrine Event Manager is a simple PHP event system that was built to be used with the various Doctrine projects.",
             "homepage": "https://www.doctrine-project.org/projects/event-manager.html",
             "keywords": [
                 "event",
-                "eventdispatcher",
-                "eventmanager"
+                "event dispatcher",
+                "event manager",
+                "event system",
+                "events"
             ],
-            "time": "2018-06-11T11:59:03+00:00"
+            "time": "2019-11-10T09:48:07+00:00"
         },
         {
             "name": "doctrine/inflector",
-            "version": "v1.3.0",
+            "version": "1.3.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/doctrine/inflector.git",
-                "reference": "5527a48b7313d15261292c149e55e26eae771b0a"
+                "reference": "ec3a55242203ffa6a4b27c58176da97ff0a7aec1"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/doctrine/inflector/zipball/5527a48b7313d15261292c149e55e26eae771b0a",
-                "reference": "5527a48b7313d15261292c149e55e26eae771b0a",
+                "url": "https://api.github.com/repos/doctrine/inflector/zipball/ec3a55242203ffa6a4b27c58176da97ff0a7aec1",
+                "reference": "ec3a55242203ffa6a4b27c58176da97ff0a7aec1",
                 "shasum": ""
             },
             "require": {
@@ -525,6 +513,10 @@
                 "MIT"
             ],
             "authors": [
+                {
+                    "name": "Guilherme Blanco",
+                    "email": "guilhermeblanco@gmail.com"
+                },
                 {
                     "name": "Roman Borschel",
                     "email": "roman@code-factory.org"
@@ -533,10 +525,6 @@
                     "name": "Benjamin Eberlei",
                     "email": "kontakt@beberlei.de"
                 },
-                {
-                    "name": "Guilherme Blanco",
-                    "email": "guilhermeblanco@gmail.com"
-                },
                 {
                     "name": "Jonathan Wage",
                     "email": "jonwage@gmail.com"
@@ -554,20 +542,20 @@
                 "singularize",
                 "string"
             ],
-            "time": "2018-01-09T20:05:19+00:00"
+            "time": "2019-10-30T19:59:35+00:00"
         },
         {
             "name": "doctrine/instantiator",
-            "version": "1.2.0",
+            "version": "1.3.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/doctrine/instantiator.git",
-                "reference": "a2c590166b2133a4633738648b6b064edae0814a"
+                "reference": "ae466f726242e637cebdd526a7d991b9433bacf1"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/doctrine/instantiator/zipball/a2c590166b2133a4633738648b6b064edae0814a",
-                "reference": "a2c590166b2133a4633738648b6b064edae0814a",
+                "url": "https://api.github.com/repos/doctrine/instantiator/zipball/ae466f726242e637cebdd526a7d991b9433bacf1",
+                "reference": "ae466f726242e637cebdd526a7d991b9433bacf1",
                 "shasum": ""
             },
             "require": {
@@ -610,20 +598,20 @@
                 "constructor",
                 "instantiate"
             ],
-            "time": "2019-03-17T17:37:11+00:00"
+            "time": "2019-10-21T16:45:58+00:00"
         },
         {
             "name": "doctrine/lexer",
-            "version": "1.1.0",
+            "version": "1.2.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/doctrine/lexer.git",
-                "reference": "e17f069ede36f7534b95adec71910ed1b49c74ea"
+                "reference": "5242d66dbeb21a30dd8a3e66bf7a73b66e05e1f6"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/doctrine/lexer/zipball/e17f069ede36f7534b95adec71910ed1b49c74ea",
-                "reference": "e17f069ede36f7534b95adec71910ed1b49c74ea",
+                "url": "https://api.github.com/repos/doctrine/lexer/zipball/5242d66dbeb21a30dd8a3e66bf7a73b66e05e1f6",
+                "reference": "5242d66dbeb21a30dd8a3e66bf7a73b66e05e1f6",
                 "shasum": ""
             },
             "require": {
@@ -637,7 +625,7 @@
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.1.x-dev"
+                    "dev-master": "1.2.x-dev"
                 }
             },
             "autoload": {
@@ -672,38 +660,39 @@
                 "parser",
                 "php"
             ],
-            "time": "2019-07-30T19:33:28+00:00"
+            "time": "2019-10-30T14:39:59+00:00"
         },
         {
             "name": "doctrine/orm",
-            "version": "v2.6.3",
+            "version": "v2.7.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/doctrine/orm.git",
-                "reference": "434820973cadf2da2d66e7184be370084cc32ca8"
+                "reference": "4d763ca4c925f647b248b9fa01b5f47aa3685d62"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/doctrine/orm/zipball/434820973cadf2da2d66e7184be370084cc32ca8",
-                "reference": "434820973cadf2da2d66e7184be370084cc32ca8",
+                "url": "https://api.github.com/repos/doctrine/orm/zipball/4d763ca4c925f647b248b9fa01b5f47aa3685d62",
+                "reference": "4d763ca4c925f647b248b9fa01b5f47aa3685d62",
                 "shasum": ""
             },
             "require": {
-                "doctrine/annotations": "~1.5",
-                "doctrine/cache": "~1.6",
-                "doctrine/collections": "^1.4",
-                "doctrine/common": "^2.7.1",
-                "doctrine/dbal": "^2.6",
-                "doctrine/instantiator": "~1.1",
+                "doctrine/annotations": "^1.8",
+                "doctrine/cache": "^1.9.1",
+                "doctrine/collections": "^1.5",
+                "doctrine/common": "^2.11",
+                "doctrine/dbal": "^2.9.3",
+                "doctrine/event-manager": "^1.1",
+                "doctrine/instantiator": "^1.3",
+                "doctrine/persistence": "^1.2",
                 "ext-pdo": "*",
                 "php": "^7.1",
-                "symfony/console": "~3.0|~4.0"
+                "symfony/console": "^3.0|^4.0|^5.0"
             },
             "require-dev": {
-                "doctrine/coding-standard": "^1.0",
-                "phpunit/phpunit": "^6.5",
-                "squizlabs/php_codesniffer": "^3.2",
-                "symfony/yaml": "~3.4|~4.0"
+                "doctrine/coding-standard": "^5.0",
+                "phpunit/phpunit": "^7.5",
+                "symfony/yaml": "^3.4|^4.0|^5.0"
             },
             "suggest": {
                 "symfony/yaml": "If you want to use YAML Metadata Mapping Driver"
@@ -714,7 +703,7 @@
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "2.6.x-dev"
+                    "dev-master": "2.7.x-dev"
                 }
             },
             "autoload": {
@@ -727,6 +716,10 @@
                 "MIT"
             ],
             "authors": [
+                {
+                    "name": "Guilherme Blanco",
+                    "email": "guilhermeblanco@gmail.com"
+                },
                 {
                     "name": "Roman Borschel",
                     "email": "roman@code-factory.org"
@@ -735,10 +728,6 @@
                     "name": "Benjamin Eberlei",
                     "email": "kontakt@beberlei.de"
                 },
-                {
-                    "name": "Guilherme Blanco",
-                    "email": "guilhermeblanco@gmail.com"
-                },
                 {
                     "name": "Jonathan Wage",
                     "email": "jonwage@gmail.com"
@@ -749,25 +738,25 @@
                 }
             ],
             "description": "Object-Relational-Mapper for PHP",
-            "homepage": "http://www.doctrine-project.org",
+            "homepage": "https://www.doctrine-project.org/projects/orm.html",
             "keywords": [
                 "database",
                 "orm"
             ],
-            "time": "2018-11-20T23:46:46+00:00"
+            "time": "2019-11-19T08:38:05+00:00"
         },
         {
             "name": "doctrine/persistence",
-            "version": "1.1.1",
+            "version": "1.2.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/doctrine/persistence.git",
-                "reference": "3da7c9d125591ca83944f477e65ed3d7b4617c48"
+                "reference": "43526ae63312942e5316100bb3ed589ba1aba491"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/doctrine/persistence/zipball/3da7c9d125591ca83944f477e65ed3d7b4617c48",
-                "reference": "3da7c9d125591ca83944f477e65ed3d7b4617c48",
+                "url": "https://api.github.com/repos/doctrine/persistence/zipball/43526ae63312942e5316100bb3ed589ba1aba491",
+                "reference": "43526ae63312942e5316100bb3ed589ba1aba491",
                 "shasum": ""
             },
             "require": {
@@ -789,7 +778,7 @@
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.1.x-dev"
+                    "dev-master": "1.2.x-dev"
                 }
             },
             "autoload": {
@@ -802,6 +791,10 @@
                 "MIT"
             ],
             "authors": [
+                {
+                    "name": "Guilherme Blanco",
+                    "email": "guilhermeblanco@gmail.com"
+                },
                 {
                     "name": "Roman Borschel",
                     "email": "roman@code-factory.org"
@@ -810,10 +803,6 @@
                     "name": "Benjamin Eberlei",
                     "email": "kontakt@beberlei.de"
                 },
-                {
-                    "name": "Guilherme Blanco",
-                    "email": "guilhermeblanco@gmail.com"
-                },
                 {
                     "name": "Jonathan Wage",
                     "email": "jonwage@gmail.com"
@@ -836,7 +825,7 @@
                 "orm",
                 "persistence"
             ],
-            "time": "2019-04-23T08:28:24+00:00"
+            "time": "2019-04-23T12:39:21+00:00"
         },
         {
             "name": "doctrine/reflection",
@@ -914,41 +903,36 @@
             "time": "2018-06-14T14:45:07+00:00"
         },
         {
-            "name": "egulias/email-validator",
-            "version": "2.1.11",
+            "name": "jeremeamia/superclosure",
+            "version": "2.4.0",
             "source": {
                 "type": "git",
-                "url": "https://github.com/egulias/EmailValidator.git",
-                "reference": "92dd169c32f6f55ba570c309d83f5209cefb5e23"
+                "url": "https://github.com/jeremeamia/super_closure.git",
+                "reference": "5707d5821b30b9a07acfb4d76949784aaa0e9ce9"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/92dd169c32f6f55ba570c309d83f5209cefb5e23",
-                "reference": "92dd169c32f6f55ba570c309d83f5209cefb5e23",
+                "url": "https://api.github.com/repos/jeremeamia/super_closure/zipball/5707d5821b30b9a07acfb4d76949784aaa0e9ce9",
+                "reference": "5707d5821b30b9a07acfb4d76949784aaa0e9ce9",
                 "shasum": ""
             },
             "require": {
-                "doctrine/lexer": "^1.0.1",
-                "php": ">= 5.5"
+                "nikic/php-parser": "^1.2|^2.0|^3.0|^4.0",
+                "php": ">=5.4",
+                "symfony/polyfill-php56": "^1.0"
             },
             "require-dev": {
-                "dominicsayers/isemail": "dev-master",
-                "phpunit/phpunit": "^4.8.35||^5.7||^6.0",
-                "satooshi/php-coveralls": "^1.0.1",
-                "symfony/phpunit-bridge": "^4.4@dev"
-            },
-            "suggest": {
-                "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation"
+                "phpunit/phpunit": "^4.0|^5.0"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "2.1.x-dev"
+                    "dev-master": "2.4-dev"
                 }
             },
             "autoload": {
                 "psr-4": {
-                    "Egulias\\EmailValidator\\": "EmailValidator"
+                    "SuperClosure\\": "src/"
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
@@ -957,92 +941,42 @@
             ],
             "authors": [
                 {
-                    "name": "Eduardo Gulias Davis"
-                }
-            ],
-            "description": "A library for validating emails against several RFCs",
-            "homepage": "https://github.com/egulias/EmailValidator",
-            "keywords": [
-                "email",
-                "emailvalidation",
-                "emailvalidator",
-                "validation",
-                "validator"
-            ],
-            "time": "2019-08-13T17:33:27+00:00"
-        },
-        {
-            "name": "lcobucci/jwt",
-            "version": "3.3.1",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/lcobucci/jwt.git",
-                "reference": "a11ec5f4b4d75d1fcd04e133dede4c317aac9e18"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/lcobucci/jwt/zipball/a11ec5f4b4d75d1fcd04e133dede4c317aac9e18",
-                "reference": "a11ec5f4b4d75d1fcd04e133dede4c317aac9e18",
-                "shasum": ""
-            },
-            "require": {
-                "ext-mbstring": "*",
-                "ext-openssl": "*",
-                "php": "^5.6 || ^7.0"
-            },
-            "require-dev": {
-                "mikey179/vfsstream": "~1.5",
-                "phpmd/phpmd": "~2.2",
-                "phpunit/php-invoker": "~1.1",
-                "phpunit/phpunit": "^5.7 || ^7.3",
-                "squizlabs/php_codesniffer": "~2.3"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "3.1-dev"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "Lcobucci\\JWT\\": "src"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "BSD-3-Clause"
-            ],
-            "authors": [
-                {
-                    "name": "Luís Otávio Cobucci Oblonczyk",
-                    "email": "lcobucci@gmail.com",
+                    "name": "Jeremy Lindblom",
+                    "email": "jeremeamia@gmail.com",
+                    "homepage": "https://github.com/jeremeamia",
                     "role": "Developer"
                 }
             ],
-            "description": "A simple library to work with JSON Web Token and JSON Web Signature",
+            "description": "Serialize Closure objects, including their context and binding",
+            "homepage": "https://github.com/jeremeamia/super_closure",
             "keywords": [
-                "JWS",
-                "jwt"
+                "closure",
+                "function",
+                "lambda",
+                "parser",
+                "serializable",
+                "serialize",
+                "tokenizer"
             ],
-            "time": "2019-05-24T18:30:49+00:00"
+            "time": "2018-03-21T22:21:57+00:00"
         },
         {
             "name": "monolog/monolog",
-            "version": "1.25.1",
+            "version": "2.0.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/Seldaek/monolog.git",
-                "reference": "70e65a5470a42cfec1a7da00d30edb6e617e8dcf"
+                "reference": "f9d56fd2f5533322caccdfcddbb56aedd622ef1c"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/Seldaek/monolog/zipball/70e65a5470a42cfec1a7da00d30edb6e617e8dcf",
-                "reference": "70e65a5470a42cfec1a7da00d30edb6e617e8dcf",
+                "url": "https://api.github.com/repos/Seldaek/monolog/zipball/f9d56fd2f5533322caccdfcddbb56aedd622ef1c",
+                "reference": "f9d56fd2f5533322caccdfcddbb56aedd622ef1c",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.3.0",
-                "psr/log": "~1.0"
+                "php": "^7.2",
+                "psr/log": "^1.0.1"
             },
             "provide": {
                 "psr/log-implementation": "1.0.0"
@@ -1050,33 +984,36 @@
             "require-dev": {
                 "aws/aws-sdk-php": "^2.4.9 || ^3.0",
                 "doctrine/couchdb": "~1.0@dev",
-                "graylog2/gelf-php": "~1.0",
-                "jakub-onderka/php-parallel-lint": "0.9",
+                "elasticsearch/elasticsearch": "^6.0",
+                "graylog2/gelf-php": "^1.4.2",
+                "jakub-onderka/php-parallel-lint": "^0.9",
                 "php-amqplib/php-amqplib": "~2.4",
                 "php-console/php-console": "^3.1.3",
-                "phpunit/phpunit": "~4.5",
-                "phpunit/phpunit-mock-objects": "2.3.0",
+                "phpspec/prophecy": "^1.6.1",
+                "phpunit/phpunit": "^8.3",
+                "predis/predis": "^1.1",
+                "rollbar/rollbar": "^1.3",
                 "ruflin/elastica": ">=0.90 <3.0",
-                "sentry/sentry": "^0.13",
                 "swiftmailer/swiftmailer": "^5.3|^6.0"
             },
             "suggest": {
                 "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
                 "doctrine/couchdb": "Allow sending log messages to a CouchDB server",
+                "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client",
                 "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
-                "ext-mongo": "Allow sending log messages to a MongoDB server",
+                "ext-mbstring": "Allow to work properly with unicode symbols",
+                "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)",
                 "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
-                "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver",
+                "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)",
                 "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
                 "php-console/php-console": "Allow sending log messages to Google Chrome",
                 "rollbar/rollbar": "Allow sending log messages to Rollbar",
-                "ruflin/elastica": "Allow sending log messages to an Elastic Search server",
-                "sentry/sentry": "Allow sending log messages to a Sentry server"
+                "ruflin/elastica": "Allow sending log messages to an Elastic Search server"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "2.0.x-dev"
+                    "dev-master": "2.x-dev"
                 }
             },
             "autoload": {
@@ -1102,7 +1039,7 @@
                 "logging",
                 "psr-3"
             ],
-            "time": "2019-09-06T13:49:17+00:00"
+            "time": "2019-11-13T10:27:43+00:00"
         },
         {
             "name": "nikic/fast-route",
@@ -1151,109 +1088,341 @@
             "time": "2018-02-13T20:26:39+00:00"
         },
         {
-            "name": "php-amqplib/php-amqplib",
-            "version": "v2.10.0",
+            "name": "nikic/php-parser",
+            "version": "v4.3.0",
             "source": {
                 "type": "git",
-                "url": "https://github.com/php-amqplib/php-amqplib.git",
-                "reference": "04e5366f032906d5f716890427e425e71307d3a8"
+                "url": "https://github.com/nikic/PHP-Parser.git",
+                "reference": "9a9981c347c5c49d6dfe5cf826bb882b824080dc"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/php-amqplib/php-amqplib/zipball/04e5366f032906d5f716890427e425e71307d3a8",
-                "reference": "04e5366f032906d5f716890427e425e71307d3a8",
+                "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/9a9981c347c5c49d6dfe5cf826bb882b824080dc",
+                "reference": "9a9981c347c5c49d6dfe5cf826bb882b824080dc",
                 "shasum": ""
             },
             "require": {
-                "ext-bcmath": "*",
-                "ext-sockets": "*",
-                "php": ">=5.6"
+                "ext-tokenizer": "*",
+                "php": ">=7.0"
             },
-            "replace": {
-                "videlalvaro/php-amqplib": "self.version"
+            "require-dev": {
+                "ircmaxell/php-yacc": "0.0.5",
+                "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0"
+            },
+            "bin": [
+                "bin/php-parse"
+            ],
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "4.3-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "PhpParser\\": "lib/PhpParser"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Nikita Popov"
+                }
+            ],
+            "description": "A PHP parser written in PHP",
+            "keywords": [
+                "parser",
+                "php"
+            ],
+            "time": "2019-11-08T13:50:10+00:00"
+        },
+        {
+            "name": "nyholm/psr7",
+            "version": "1.2.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/Nyholm/psr7.git",
+                "reference": "55ff6b76573f5b242554c9775792bd59fb52e11c"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/Nyholm/psr7/zipball/55ff6b76573f5b242554c9775792bd59fb52e11c",
+                "reference": "55ff6b76573f5b242554c9775792bd59fb52e11c",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.1",
+                "php-http/message-factory": "^1.0",
+                "psr/http-factory": "^1.0",
+                "psr/http-message": "^1.0"
+            },
+            "provide": {
+                "psr/http-factory-implementation": "1.0",
+                "psr/http-message-implementation": "1.0"
             },
             "require-dev": {
-                "ext-curl": "*",
-                "nategood/httpful": "^0.2.20",
-                "phpdocumentor/phpdocumentor": "dev-master",
-                "phpunit/phpunit": "^5.7|^6.5|^7.0",
-                "squizlabs/php_codesniffer": "^2.5"
+                "http-interop/http-factory-tests": "dev-master",
+                "php-http/psr7-integration-tests": "dev-master",
+                "phpunit/phpunit": "^7.5"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "2.10-dev"
+                    "dev-master": "1.0-dev"
                 }
             },
             "autoload": {
                 "psr-4": {
-                    "PhpAmqpLib\\": "PhpAmqpLib/"
+                    "Nyholm\\Psr7\\": "src/"
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
-                "LGPL-2.1-or-later"
+                "MIT"
             ],
             "authors": [
                 {
-                    "name": "Alvaro Videla",
-                    "role": "Original Maintainer"
+                    "name": "Tobias Nyholm",
+                    "email": "tobias.nyholm@gmail.com"
                 },
                 {
-                    "name": "John Kelly",
-                    "role": "Maintainer",
-                    "email": "johnmkelly86@gmail.com"
-                },
+                    "name": "Martijn van der Ven",
+                    "email": "martijn@vanderven.se"
+                }
+            ],
+            "description": "A fast PHP7 implementation of PSR-7",
+            "homepage": "http://tnyholm.se",
+            "keywords": [
+                "psr-17",
+                "psr-7"
+            ],
+            "time": "2019-09-05T13:24:16+00:00"
+        },
+        {
+            "name": "nyholm/psr7-server",
+            "version": "0.3.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/Nyholm/psr7-server.git",
+                "reference": "1b71a848fcb066fb805b7a9ab3f41ff65bffcde8"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/Nyholm/psr7-server/zipball/1b71a848fcb066fb805b7a9ab3f41ff65bffcde8",
+                "reference": "1b71a848fcb066fb805b7a9ab3f41ff65bffcde8",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.1",
+                "psr/http-factory": "^1.0",
+                "psr/http-message": "^1.0"
+            },
+            "require-dev": {
+                "nyholm/nsa": "^1.1",
+                "nyholm/psr7": "^1.0",
+                "phpunit/phpunit": "^7.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Nyholm\\Psr7Server\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
                 {
-                    "name": "Raúl Araya",
-                    "role": "Maintainer",
-                    "email": "nubeiro@gmail.com"
+                    "name": "Tobias Nyholm",
+                    "email": "tobias.nyholm@gmail.com"
                 },
                 {
-                    "name": "Luke Bakken",
-                    "role": "Maintainer",
-                    "email": "luke@bakken.io"
+                    "name": "Martijn van der Ven",
+                    "email": "martijn@vanderven.se"
                 }
             ],
-            "description": "Formerly videlalvaro/php-amqplib.  This library is a pure PHP implementation of the AMQP protocol. It's been tested against RabbitMQ.",
-            "homepage": "https://github.com/php-amqplib/php-amqplib/",
+            "description": "Helper classes to handle PSR-7 server requests",
+            "homepage": "http://tnyholm.se",
             "keywords": [
-                "message",
-                "queue",
-                "rabbitmq"
+                "psr-17",
+                "psr-7"
             ],
-            "time": "2019-08-08T18:28:18+00:00"
+            "time": "2018-09-02T10:41:28+00:00"
         },
         {
-            "name": "pimple/pimple",
-            "version": "v3.2.3",
+            "name": "php-di/invoker",
+            "version": "2.0.0",
             "source": {
                 "type": "git",
-                "url": "https://github.com/silexphp/Pimple.git",
-                "reference": "9e403941ef9d65d20cba7d54e29fe906db42cf32"
+                "url": "https://github.com/PHP-DI/Invoker.git",
+                "reference": "540c27c86f663e20fe39a24cd72fa76cdb21d41a"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/silexphp/Pimple/zipball/9e403941ef9d65d20cba7d54e29fe906db42cf32",
-                "reference": "9e403941ef9d65d20cba7d54e29fe906db42cf32",
+                "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/540c27c86f663e20fe39a24cd72fa76cdb21d41a",
+                "reference": "540c27c86f663e20fe39a24cd72fa76cdb21d41a",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.3.0",
+                "psr/container": "~1.0"
+            },
+            "require-dev": {
+                "athletic/athletic": "~0.1.8",
+                "phpunit/phpunit": "~4.5"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Invoker\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "description": "Generic and extensible callable invoker",
+            "homepage": "https://github.com/PHP-DI/Invoker",
+            "keywords": [
+                "callable",
+                "dependency",
+                "dependency-injection",
+                "injection",
+                "invoke",
+                "invoker"
+            ],
+            "time": "2017-03-20T19:28:22+00:00"
+        },
+        {
+            "name": "php-di/php-di",
+            "version": "6.0.10",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/PHP-DI/PHP-DI.git",
+                "reference": "a6c813bf6b0d0bdeade3ac5a920e2c2a5b1a6ce3"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/a6c813bf6b0d0bdeade3ac5a920e2c2a5b1a6ce3",
+                "reference": "a6c813bf6b0d0bdeade3ac5a920e2c2a5b1a6ce3",
+                "shasum": ""
+            },
+            "require": {
+                "jeremeamia/superclosure": "^2.0",
+                "nikic/php-parser": "^2.0|^3.0|^4.0",
+                "php": ">=7.0.0",
+                "php-di/invoker": "^2.0",
+                "php-di/phpdoc-reader": "^2.0.1",
                 "psr/container": "^1.0"
             },
+            "provide": {
+                "psr/container-implementation": "^1.0"
+            },
             "require-dev": {
-                "symfony/phpunit-bridge": "^3.2"
+                "doctrine/annotations": "~1.2",
+                "friendsofphp/php-cs-fixer": "^2.4",
+                "mnapoli/phpunit-easymock": "~1.0",
+                "ocramius/proxy-manager": "~2.0.2",
+                "phpstan/phpstan": "^0.9.2",
+                "phpunit/phpunit": "~6.4"
+            },
+            "suggest": {
+                "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)",
+                "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "DI\\": "src/"
+                },
+                "files": [
+                    "src/functions.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "description": "The dependency injection container for humans",
+            "homepage": "http://php-di.org/",
+            "keywords": [
+                "PSR-11",
+                "container",
+                "container-interop",
+                "dependency injection",
+                "di",
+                "ioc",
+                "psr11"
+            ],
+            "time": "2019-10-21T11:58:24+00:00"
+        },
+        {
+            "name": "php-di/phpdoc-reader",
+            "version": "2.1.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/PHP-DI/PhpDocReader.git",
+                "reference": "15678f7451c020226807f520efb867ad26fbbfcf"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/15678f7451c020226807f520efb867ad26fbbfcf",
+                "reference": "15678f7451c020226807f520efb867ad26fbbfcf",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.4.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.6"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "PhpDocReader\\": "src/PhpDocReader"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)",
+            "keywords": [
+                "phpdoc",
+                "reflection"
+            ],
+            "time": "2019-09-26T11:24:58+00:00"
+        },
+        {
+            "name": "php-http/message-factory",
+            "version": "v1.0.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-http/message-factory.git",
+                "reference": "a478cb11f66a6ac48d8954216cfed9aa06a501a1"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-http/message-factory/zipball/a478cb11f66a6ac48d8954216cfed9aa06a501a1",
+                "reference": "a478cb11f66a6ac48d8954216cfed9aa06a501a1",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.4",
+                "psr/http-message": "^1.0"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.2.x-dev"
+                    "dev-master": "1.0-dev"
                 }
             },
             "autoload": {
-                "psr-0": {
-                    "Pimple": "src/"
+                "psr-4": {
+                    "Http\\Message\\": "src/"
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
@@ -1262,17 +1431,20 @@
             ],
             "authors": [
                 {
-                    "name": "Fabien Potencier",
-                    "email": "fabien@symfony.com"
+                    "name": "Márk Sági-Kazár",
+                    "email": "mark.sagikazar@gmail.com"
                 }
             ],
-            "description": "Pimple, a simple Dependency Injection Container",
-            "homepage": "http://pimple.sensiolabs.org",
+            "description": "Factory interfaces for PSR-7 HTTP Message",
+            "homepage": "http://php-http.org",
             "keywords": [
-                "container",
-                "dependency injection"
+                "factory",
+                "http",
+                "message",
+                "stream",
+                "uri"
             ],
-            "time": "2018-01-21T07:42:36+00:00"
+            "time": "2015-12-19T14:08:53+00:00"
         },
         {
             "name": "psr/container",
@@ -1299,7 +1471,159 @@
             },
             "autoload": {
                 "psr-4": {
-                    "Psr\\Container\\": "src/"
+                    "Psr\\Container\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
+                }
+            ],
+            "description": "Common Container Interface (PHP FIG PSR-11)",
+            "homepage": "https://github.com/php-fig/container",
+            "keywords": [
+                "PSR-11",
+                "container",
+                "container-interface",
+                "container-interop",
+                "psr"
+            ],
+            "time": "2017-02-14T16:28:37+00:00"
+        },
+        {
+            "name": "psr/http-factory",
+            "version": "1.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/http-factory.git",
+                "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be",
+                "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.0.0",
+                "psr/http-message": "^1.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Http\\Message\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interfaces for PSR-7 HTTP message factories",
+            "keywords": [
+                "factory",
+                "http",
+                "message",
+                "psr",
+                "psr-17",
+                "psr-7",
+                "request",
+                "response"
+            ],
+            "time": "2019-04-30T12:38:16+00:00"
+        },
+        {
+            "name": "psr/http-message",
+            "version": "1.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/http-message.git",
+                "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
+                "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Http\\Message\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interface for HTTP messages",
+            "homepage": "https://github.com/php-fig/http-message",
+            "keywords": [
+                "http",
+                "http-message",
+                "psr",
+                "psr-7",
+                "request",
+                "response"
+            ],
+            "time": "2016-08-06T14:39:51+00:00"
+        },
+        {
+            "name": "psr/http-server-handler",
+            "version": "1.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/http-server-handler.git",
+                "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7",
+                "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.0",
+                "psr/http-message": "^1.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Http\\Server\\": "src/"
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
@@ -1312,33 +1636,38 @@
                     "homepage": "http://www.php-fig.org/"
                 }
             ],
-            "description": "Common Container Interface (PHP FIG PSR-11)",
-            "homepage": "https://github.com/php-fig/container",
+            "description": "Common interface for HTTP server-side request handler",
             "keywords": [
-                "PSR-11",
-                "container",
-                "container-interface",
-                "container-interop",
-                "psr"
+                "handler",
+                "http",
+                "http-interop",
+                "psr",
+                "psr-15",
+                "psr-7",
+                "request",
+                "response",
+                "server"
             ],
-            "time": "2017-02-14T16:28:37+00:00"
+            "time": "2018-10-30T16:46:14+00:00"
         },
         {
-            "name": "psr/http-message",
+            "name": "psr/http-server-middleware",
             "version": "1.0.1",
             "source": {
                 "type": "git",
-                "url": "https://github.com/php-fig/http-message.git",
-                "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
+                "url": "https://github.com/php-fig/http-server-middleware.git",
+                "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
-                "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
+                "url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/2296f45510945530b9dceb8bcedb5cb84d40c5f5",
+                "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.3.0"
+                "php": ">=7.0",
+                "psr/http-message": "^1.0",
+                "psr/http-server-handler": "^1.0"
             },
             "type": "library",
             "extra": {
@@ -1348,7 +1677,7 @@
             },
             "autoload": {
                 "psr-4": {
-                    "Psr\\Http\\Message\\": "src/"
+                    "Psr\\Http\\Server\\": "src/"
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
@@ -1361,30 +1690,31 @@
                     "homepage": "http://www.php-fig.org/"
                 }
             ],
-            "description": "Common interface for HTTP messages",
-            "homepage": "https://github.com/php-fig/http-message",
+            "description": "Common interface for HTTP server-side middleware",
             "keywords": [
                 "http",
-                "http-message",
+                "http-interop",
+                "middleware",
                 "psr",
+                "psr-15",
                 "psr-7",
                 "request",
                 "response"
             ],
-            "time": "2016-08-06T14:39:51+00:00"
+            "time": "2018-10-30T17:12:04+00:00"
         },
         {
             "name": "psr/log",
-            "version": "1.1.0",
+            "version": "1.1.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/php-fig/log.git",
-                "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd"
+                "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/php-fig/log/zipball/6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd",
-                "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd",
+                "url": "https://api.github.com/repos/php-fig/log/zipball/446d54b4cb6bf489fc9d75f55843658e6f25d801",
+                "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801",
                 "shasum": ""
             },
             "require": {
@@ -1393,7 +1723,7 @@
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.0.x-dev"
+                    "dev-master": "1.1.x-dev"
                 }
             },
             "autoload": {
@@ -1418,44 +1748,56 @@
                 "psr",
                 "psr-3"
             ],
-            "time": "2018-11-20T15:27:04+00:00"
+            "time": "2019-11-01T11:05:21+00:00"
         },
         {
             "name": "slim/slim",
-            "version": "3.12.2",
+            "version": "4.3.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/slimphp/Slim.git",
-                "reference": "200c6143f15baa477601879b64ab2326847aac0b"
+                "reference": "26020e9a099e69b0b12918115894f7106364dcb7"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/slimphp/Slim/zipball/200c6143f15baa477601879b64ab2326847aac0b",
-                "reference": "200c6143f15baa477601879b64ab2326847aac0b",
+                "url": "https://api.github.com/repos/slimphp/Slim/zipball/26020e9a099e69b0b12918115894f7106364dcb7",
+                "reference": "26020e9a099e69b0b12918115894f7106364dcb7",
                 "shasum": ""
             },
             "require": {
-                "container-interop/container-interop": "^1.2",
                 "ext-json": "*",
-                "ext-libxml": "*",
-                "ext-simplexml": "*",
-                "nikic/fast-route": "^1.0",
-                "php": ">=5.5.0",
-                "pimple/pimple": "^3.0",
+                "nikic/fast-route": "^1.3",
+                "php": "^7.1",
                 "psr/container": "^1.0",
-                "psr/http-message": "^1.0"
-            },
-            "provide": {
-                "psr/http-message-implementation": "1.0"
+                "psr/http-factory": "^1.0",
+                "psr/http-message": "^1.0",
+                "psr/http-server-handler": "^1.0",
+                "psr/http-server-middleware": "^1.0"
             },
             "require-dev": {
-                "phpunit/phpunit": "^4.0",
-                "squizlabs/php_codesniffer": "^2.5"
+                "ext-simplexml": "*",
+                "guzzlehttp/psr7": "^1.5",
+                "http-interop/http-factory-guzzle": "^1.0",
+                "nyholm/psr7": "^1.1",
+                "nyholm/psr7-server": "^0.3.0",
+                "phpspec/prophecy": "^1.8",
+                "phpstan/phpstan": "^0.11.5",
+                "phpunit/phpunit": "^7.5",
+                "slim/http": "^0.7",
+                "slim/psr7": "^0.3",
+                "squizlabs/php_codesniffer": "^3.4.2",
+                "zendframework/zend-diactoros": "^2.1"
+            },
+            "suggest": {
+                "ext-simplexml": "Needed to support XML format in BodyParsingMiddleware",
+                "ext-xml": "Needed to support XML format in BodyParsingMiddleware",
+                "slim/psr7": "Slim PSR-7 implementation. See http://www.slimframework.com/docs/v4/start/installation.html for more information."
             },
             "type": "library",
             "autoload": {
                 "psr-4": {
-                    "Slim\\": "Slim"
+                    "Slim\\": "Slim",
+                    "Slim\\Tests\\": "tests"
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
@@ -1478,6 +1820,11 @@
                     "email": "rob@akrabat.com",
                     "homepage": "http://akrabat.com"
                 },
+                {
+                    "name": "Pierre Berube",
+                    "email": "pierre@lgse.com",
+                    "homepage": "http://www.lgse.com"
+                },
                 {
                     "name": "Gabriel Manricks",
                     "email": "gmanricks@me.com",
@@ -1485,113 +1832,52 @@
                 }
             ],
             "description": "Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs",
-            "homepage": "https://slimframework.com",
+            "homepage": "https://www.slimframework.com",
             "keywords": [
                 "api",
                 "framework",
                 "micro",
                 "router"
             ],
-            "time": "2019-08-20T18:46:05+00:00"
-        },
-        {
-            "name": "swiftmailer/swiftmailer",
-            "version": "v6.2.1",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/swiftmailer/swiftmailer.git",
-                "reference": "5397cd05b0a0f7937c47b0adcb4c60e5ab936b6a"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/5397cd05b0a0f7937c47b0adcb4c60e5ab936b6a",
-                "reference": "5397cd05b0a0f7937c47b0adcb4c60e5ab936b6a",
-                "shasum": ""
-            },
-            "require": {
-                "egulias/email-validator": "~2.0",
-                "php": ">=7.0.0",
-                "symfony/polyfill-iconv": "^1.0",
-                "symfony/polyfill-intl-idn": "^1.10",
-                "symfony/polyfill-mbstring": "^1.0"
-            },
-            "require-dev": {
-                "mockery/mockery": "~0.9.1",
-                "symfony/phpunit-bridge": "^3.4.19|^4.1.8"
-            },
-            "suggest": {
-                "ext-intl": "Needed to support internationalized email addresses",
-                "true/punycode": "Needed to support internationalized email addresses, if ext-intl is not installed"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "6.2-dev"
-                }
-            },
-            "autoload": {
-                "files": [
-                    "lib/swift_required.php"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Chris Corbyn"
-                },
-                {
-                    "name": "Fabien Potencier",
-                    "email": "fabien@symfony.com"
-                }
-            ],
-            "description": "Swiftmailer, free feature-rich PHP mailer",
-            "homepage": "https://swiftmailer.symfony.com",
-            "keywords": [
-                "email",
-                "mail",
-                "mailer"
-            ],
-            "time": "2019-04-21T09:21:45+00:00"
+            "time": "2019-10-05T21:24:58+00:00"
         },
         {
             "name": "symfony/console",
-            "version": "v4.3.4",
+            "version": "v5.0.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/console.git",
-                "reference": "de63799239b3881b8a08f8481b22348f77ed7b36"
+                "reference": "dae5ef273d700771168ab889d9f8a19b2d206656"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/console/zipball/de63799239b3881b8a08f8481b22348f77ed7b36",
-                "reference": "de63799239b3881b8a08f8481b22348f77ed7b36",
+                "url": "https://api.github.com/repos/symfony/console/zipball/dae5ef273d700771168ab889d9f8a19b2d206656",
+                "reference": "dae5ef273d700771168ab889d9f8a19b2d206656",
                 "shasum": ""
             },
             "require": {
-                "php": "^7.1.3",
+                "php": "^7.2.5",
                 "symfony/polyfill-mbstring": "~1.0",
                 "symfony/polyfill-php73": "^1.8",
-                "symfony/service-contracts": "^1.1"
+                "symfony/service-contracts": "^1.1|^2"
             },
             "conflict": {
-                "symfony/dependency-injection": "<3.4",
-                "symfony/event-dispatcher": "<4.3",
-                "symfony/process": "<3.3"
+                "symfony/dependency-injection": "<4.4",
+                "symfony/event-dispatcher": "<4.4",
+                "symfony/lock": "<4.4",
+                "symfony/process": "<4.4"
             },
             "provide": {
                 "psr/log-implementation": "1.0"
             },
             "require-dev": {
                 "psr/log": "~1.0",
-                "symfony/config": "~3.4|~4.0",
-                "symfony/dependency-injection": "~3.4|~4.0",
-                "symfony/event-dispatcher": "^4.3",
-                "symfony/lock": "~3.4|~4.0",
-                "symfony/process": "~3.4|~4.0",
-                "symfony/var-dumper": "^4.3"
+                "symfony/config": "^4.4|^5.0",
+                "symfony/dependency-injection": "^4.4|^5.0",
+                "symfony/event-dispatcher": "^4.4|^5.0",
+                "symfony/lock": "^4.4|^5.0",
+                "symfony/process": "^4.4|^5.0",
+                "symfony/var-dumper": "^4.4|^5.0"
             },
             "suggest": {
                 "psr/log": "For using the console logger",
@@ -1602,7 +1888,7 @@
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "4.3-dev"
+                    "dev-master": "5.0-dev"
                 }
             },
             "autoload": {
@@ -1629,37 +1915,37 @@
             ],
             "description": "Symfony Console Component",
             "homepage": "https://symfony.com",
-            "time": "2019-08-26T08:26:39+00:00"
+            "time": "2019-12-01T10:51:15+00:00"
         },
         {
-            "name": "symfony/polyfill-iconv",
-            "version": "v1.12.0",
+            "name": "symfony/polyfill-mbstring",
+            "version": "v1.13.1",
             "source": {
                 "type": "git",
-                "url": "https://github.com/symfony/polyfill-iconv.git",
-                "reference": "685968b11e61a347c18bf25db32effa478be610f"
+                "url": "https://github.com/symfony/polyfill-mbstring.git",
+                "reference": "7b4aab9743c30be783b73de055d24a39cf4b954f"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/685968b11e61a347c18bf25db32effa478be610f",
-                "reference": "685968b11e61a347c18bf25db32effa478be610f",
+                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/7b4aab9743c30be783b73de055d24a39cf4b954f",
+                "reference": "7b4aab9743c30be783b73de055d24a39cf4b954f",
                 "shasum": ""
             },
             "require": {
                 "php": ">=5.3.3"
             },
             "suggest": {
-                "ext-iconv": "For best performance"
+                "ext-mbstring": "For best performance"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.12-dev"
+                    "dev-master": "1.13-dev"
                 }
             },
             "autoload": {
                 "psr-4": {
-                    "Symfony\\Polyfill\\Iconv\\": ""
+                    "Symfony\\Polyfill\\Mbstring\\": ""
                 },
                 "files": [
                     "bootstrap.php"
@@ -1679,108 +1965,44 @@
                     "homepage": "https://symfony.com/contributors"
                 }
             ],
-            "description": "Symfony polyfill for the Iconv extension",
+            "description": "Symfony polyfill for the Mbstring extension",
             "homepage": "https://symfony.com",
             "keywords": [
                 "compatibility",
-                "iconv",
+                "mbstring",
                 "polyfill",
                 "portable",
                 "shim"
             ],
-            "time": "2019-08-06T08:03:45+00:00"
+            "time": "2019-11-27T14:18:11+00:00"
         },
         {
-            "name": "symfony/polyfill-intl-idn",
-            "version": "v1.12.0",
+            "name": "symfony/polyfill-php56",
+            "version": "v1.13.1",
             "source": {
                 "type": "git",
-                "url": "https://github.com/symfony/polyfill-intl-idn.git",
-                "reference": "6af626ae6fa37d396dc90a399c0ff08e5cfc45b2"
+                "url": "https://github.com/symfony/polyfill-php56.git",
+                "reference": "53dd1cdf3cb986893ccf2b96665b25b3abb384f4"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/6af626ae6fa37d396dc90a399c0ff08e5cfc45b2",
-                "reference": "6af626ae6fa37d396dc90a399c0ff08e5cfc45b2",
+                "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/53dd1cdf3cb986893ccf2b96665b25b3abb384f4",
+                "reference": "53dd1cdf3cb986893ccf2b96665b25b3abb384f4",
                 "shasum": ""
             },
             "require": {
                 "php": ">=5.3.3",
-                "symfony/polyfill-mbstring": "^1.3",
-                "symfony/polyfill-php72": "^1.9"
-            },
-            "suggest": {
-                "ext-intl": "For best performance"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "1.12-dev"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "Symfony\\Polyfill\\Intl\\Idn\\": ""
-                },
-                "files": [
-                    "bootstrap.php"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Laurent Bassin",
-                    "email": "laurent@bassin.info"
-                },
-                {
-                    "name": "Symfony Community",
-                    "homepage": "https://symfony.com/contributors"
-                }
-            ],
-            "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions",
-            "homepage": "https://symfony.com",
-            "keywords": [
-                "compatibility",
-                "idn",
-                "intl",
-                "polyfill",
-                "portable",
-                "shim"
-            ],
-            "time": "2019-08-06T08:03:45+00:00"
-        },
-        {
-            "name": "symfony/polyfill-mbstring",
-            "version": "v1.12.0",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/symfony/polyfill-mbstring.git",
-                "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/b42a2f66e8f1b15ccf25652c3424265923eb4f17",
-                "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=5.3.3"
-            },
-            "suggest": {
-                "ext-mbstring": "For best performance"
+                "symfony/polyfill-util": "~1.0"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.12-dev"
+                    "dev-master": "1.13-dev"
                 }
             },
             "autoload": {
                 "psr-4": {
-                    "Symfony\\Polyfill\\Mbstring\\": ""
+                    "Symfony\\Polyfill\\Php56\\": ""
                 },
                 "files": [
                     "bootstrap.php"
@@ -1800,29 +2022,28 @@
                     "homepage": "https://symfony.com/contributors"
                 }
             ],
-            "description": "Symfony polyfill for the Mbstring extension",
+            "description": "Symfony polyfill backporting some PHP 5.6+ features to lower PHP versions",
             "homepage": "https://symfony.com",
             "keywords": [
                 "compatibility",
-                "mbstring",
                 "polyfill",
                 "portable",
                 "shim"
             ],
-            "time": "2019-08-06T08:03:45+00:00"
+            "time": "2019-11-27T13:56:44+00:00"
         },
         {
-            "name": "symfony/polyfill-php72",
-            "version": "v1.12.0",
+            "name": "symfony/polyfill-php73",
+            "version": "v1.13.1",
             "source": {
                 "type": "git",
-                "url": "https://github.com/symfony/polyfill-php72.git",
-                "reference": "04ce3335667451138df4307d6a9b61565560199e"
+                "url": "https://github.com/symfony/polyfill-php73.git",
+                "reference": "4b0e2222c55a25b4541305a053013d5647d3a25f"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/04ce3335667451138df4307d6a9b61565560199e",
-                "reference": "04ce3335667451138df4307d6a9b61565560199e",
+                "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/4b0e2222c55a25b4541305a053013d5647d3a25f",
+                "reference": "4b0e2222c55a25b4541305a053013d5647d3a25f",
                 "shasum": ""
             },
             "require": {
@@ -1831,15 +2052,18 @@
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.12-dev"
+                    "dev-master": "1.13-dev"
                 }
             },
             "autoload": {
                 "psr-4": {
-                    "Symfony\\Polyfill\\Php72\\": ""
+                    "Symfony\\Polyfill\\Php73\\": ""
                 },
                 "files": [
                     "bootstrap.php"
+                ],
+                "classmap": [
+                    "Resources/stubs"
                 ]
             },
             "notification-url": "https://packagist.org/downloads/",
@@ -1856,7 +2080,7 @@
                     "homepage": "https://symfony.com/contributors"
                 }
             ],
-            "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions",
+            "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions",
             "homepage": "https://symfony.com",
             "keywords": [
                 "compatibility",
@@ -1864,20 +2088,20 @@
                 "portable",
                 "shim"
             ],
-            "time": "2019-08-06T08:03:45+00:00"
+            "time": "2019-11-27T16:25:15+00:00"
         },
         {
-            "name": "symfony/polyfill-php73",
-            "version": "v1.12.0",
+            "name": "symfony/polyfill-util",
+            "version": "v1.13.1",
             "source": {
                 "type": "git",
-                "url": "https://github.com/symfony/polyfill-php73.git",
-                "reference": "2ceb49eaccb9352bff54d22570276bb75ba4a188"
+                "url": "https://github.com/symfony/polyfill-util.git",
+                "reference": "964a67f293b66b95883a5ed918a65354fcd2258f"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/2ceb49eaccb9352bff54d22570276bb75ba4a188",
-                "reference": "2ceb49eaccb9352bff54d22570276bb75ba4a188",
+                "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/964a67f293b66b95883a5ed918a65354fcd2258f",
+                "reference": "964a67f293b66b95883a5ed918a65354fcd2258f",
                 "shasum": ""
             },
             "require": {
@@ -1886,19 +2110,13 @@
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.12-dev"
+                    "dev-master": "1.13-dev"
                 }
             },
             "autoload": {
                 "psr-4": {
-                    "Symfony\\Polyfill\\Php73\\": ""
-                },
-                "files": [
-                    "bootstrap.php"
-                ],
-                "classmap": [
-                    "Resources/stubs"
-                ]
+                    "Symfony\\Polyfill\\Util\\": ""
+                }
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
@@ -1914,32 +2132,32 @@
                     "homepage": "https://symfony.com/contributors"
                 }
             ],
-            "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions",
+            "description": "Symfony utilities for portability of PHP codes",
             "homepage": "https://symfony.com",
             "keywords": [
+                "compat",
                 "compatibility",
                 "polyfill",
-                "portable",
                 "shim"
             ],
-            "time": "2019-08-06T08:03:45+00:00"
+            "time": "2019-11-27T13:56:44+00:00"
         },
         {
             "name": "symfony/service-contracts",
-            "version": "v1.1.6",
+            "version": "v2.0.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/service-contracts.git",
-                "reference": "ea7263d6b6d5f798b56a45a5b8d686725f2719a3"
+                "reference": "144c5e51266b281231e947b51223ba14acf1a749"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/service-contracts/zipball/ea7263d6b6d5f798b56a45a5b8d686725f2719a3",
-                "reference": "ea7263d6b6d5f798b56a45a5b8d686725f2719a3",
+                "url": "https://api.github.com/repos/symfony/service-contracts/zipball/144c5e51266b281231e947b51223ba14acf1a749",
+                "reference": "144c5e51266b281231e947b51223ba14acf1a749",
                 "shasum": ""
             },
             "require": {
-                "php": "^7.1.3",
+                "php": "^7.2.5",
                 "psr/container": "^1.0"
             },
             "suggest": {
@@ -1948,7 +2166,7 @@
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.1-dev"
+                    "dev-master": "2.0-dev"
                 }
             },
             "autoload": {
@@ -1980,7 +2198,7 @@
                 "interoperability",
                 "standards"
             ],
-            "time": "2019-08-20T14:44:19+00:00"
+            "time": "2019-11-18T17:27:11+00:00"
         }
     ],
     "packages-dev": [
@@ -2286,22 +2504,22 @@
         },
         {
             "name": "phpspec/prophecy",
-            "version": "1.8.1",
+            "version": "1.9.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/phpspec/prophecy.git",
-                "reference": "1927e75f4ed19131ec9bcc3b002e07fb1173ee76"
+                "reference": "f6811d96d97bdf400077a0cc100ae56aa32b9203"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/phpspec/prophecy/zipball/1927e75f4ed19131ec9bcc3b002e07fb1173ee76",
-                "reference": "1927e75f4ed19131ec9bcc3b002e07fb1173ee76",
+                "url": "https://api.github.com/repos/phpspec/prophecy/zipball/f6811d96d97bdf400077a0cc100ae56aa32b9203",
+                "reference": "f6811d96d97bdf400077a0cc100ae56aa32b9203",
                 "shasum": ""
             },
             "require": {
                 "doctrine/instantiator": "^1.0.2",
                 "php": "^5.3|^7.0",
-                "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0",
+                "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0",
                 "sebastian/comparator": "^1.1|^2.0|^3.0",
                 "sebastian/recursion-context": "^1.0|^2.0|^3.0"
             },
@@ -2345,60 +2563,7 @@
                 "spy",
                 "stub"
             ],
-            "time": "2019-06-13T12:50:23+00:00"
-        },
-        {
-            "name": "phpunit/dbunit",
-            "version": "4.0.0",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/sebastianbergmann/dbunit.git",
-                "reference": "e77b469c3962b5a563f09a2a989f1c9bd38b8615"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/dbunit/zipball/e77b469c3962b5a563f09a2a989f1c9bd38b8615",
-                "reference": "e77b469c3962b5a563f09a2a989f1c9bd38b8615",
-                "shasum": ""
-            },
-            "require": {
-                "ext-pdo": "*",
-                "ext-simplexml": "*",
-                "php": "^7.1",
-                "phpunit/phpunit": "^7.0",
-                "symfony/yaml": "^3.0 || ^4.0"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "4.0-dev"
-                }
-            },
-            "autoload": {
-                "classmap": [
-                    "src/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "BSD-3-Clause"
-            ],
-            "authors": [
-                {
-                    "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de",
-                    "role": "lead"
-                }
-            ],
-            "description": "PHPUnit extension for database interaction testing",
-            "homepage": "https://github.com/sebastianbergmann/dbunit/",
-            "keywords": [
-                "database",
-                "testing",
-                "xunit"
-            ],
-            "abandoned": true,
-            "time": "2018-02-07T06:47:59+00:00"
+            "time": "2019-10-03T11:07:50+00:00"
         },
         {
             "name": "phpunit/php-code-coverage",
@@ -2605,16 +2770,16 @@
         },
         {
             "name": "phpunit/php-token-stream",
-            "version": "3.1.0",
+            "version": "3.1.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/php-token-stream.git",
-                "reference": "e899757bb3df5ff6e95089132f32cd59aac2220a"
+                "reference": "995192df77f63a59e47f025390d2d1fdf8f425ff"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/e899757bb3df5ff6e95089132f32cd59aac2220a",
-                "reference": "e899757bb3df5ff6e95089132f32cd59aac2220a",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/995192df77f63a59e47f025390d2d1fdf8f425ff",
+                "reference": "995192df77f63a59e47f025390d2d1fdf8f425ff",
                 "shasum": ""
             },
             "require": {
@@ -2650,20 +2815,20 @@
             "keywords": [
                 "tokenizer"
             ],
-            "time": "2019-07-25T05:29:42+00:00"
+            "time": "2019-09-17T06:23:10+00:00"
         },
         {
             "name": "phpunit/phpunit",
-            "version": "7.5.15",
+            "version": "7.5.18",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/phpunit.git",
-                "reference": "d79c053d972856b8b941bb233e39dc521a5093f0"
+                "reference": "fcf6c4bfafaadc07785528b06385cce88935474d"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/d79c053d972856b8b941bb233e39dc521a5093f0",
-                "reference": "d79c053d972856b8b941bb233e39dc521a5093f0",
+                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fcf6c4bfafaadc07785528b06385cce88935474d",
+                "reference": "fcf6c4bfafaadc07785528b06385cce88935474d",
                 "shasum": ""
             },
             "require": {
@@ -2734,7 +2899,7 @@
                 "testing",
                 "xunit"
             ],
-            "time": "2019-08-21T07:05:16+00:00"
+            "time": "2019-12-06T05:14:37+00:00"
         },
         {
             "name": "sebastian/code-unit-reverse-lookup",
@@ -2903,16 +3068,16 @@
         },
         {
             "name": "sebastian/environment",
-            "version": "4.2.2",
+            "version": "4.2.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/environment.git",
-                "reference": "f2a2c8e1c97c11ace607a7a667d73d47c19fe404"
+                "reference": "464c90d7bdf5ad4e8a6aea15c091fec0603d4368"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/f2a2c8e1c97c11ace607a7a667d73d47c19fe404",
-                "reference": "f2a2c8e1c97c11ace607a7a667d73d47c19fe404",
+                "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/464c90d7bdf5ad4e8a6aea15c091fec0603d4368",
+                "reference": "464c90d7bdf5ad4e8a6aea15c091fec0603d4368",
                 "shasum": ""
             },
             "require": {
@@ -2952,20 +3117,20 @@
                 "environment",
                 "hhvm"
             ],
-            "time": "2019-05-05T09:05:15+00:00"
+            "time": "2019-11-20T08:46:58+00:00"
         },
         {
             "name": "sebastian/exporter",
-            "version": "3.1.1",
+            "version": "3.1.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/exporter.git",
-                "reference": "06a9a5947f47b3029d76118eb5c22802e5869687"
+                "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/06a9a5947f47b3029d76118eb5c22802e5869687",
-                "reference": "06a9a5947f47b3029d76118eb5c22802e5869687",
+                "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/68609e1261d215ea5b21b7987539cbfbe156ec3e",
+                "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e",
                 "shasum": ""
             },
             "require": {
@@ -3019,7 +3184,7 @@
                 "export",
                 "exporter"
             ],
-            "time": "2019-08-11T12:43:14+00:00"
+            "time": "2019-09-14T09:02:43+00:00"
         },
         {
             "name": "sebastian/global-state",
@@ -3304,16 +3469,16 @@
         },
         {
             "name": "symfony/polyfill-ctype",
-            "version": "v1.12.0",
+            "version": "v1.13.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-ctype.git",
-                "reference": "550ebaac289296ce228a706d0867afc34687e3f4"
+                "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/550ebaac289296ce228a706d0867afc34687e3f4",
-                "reference": "550ebaac289296ce228a706d0867afc34687e3f4",
+                "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/f8f0b461be3385e56d6de3dbb5a0df24c0c275e3",
+                "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3",
                 "shasum": ""
             },
             "require": {
@@ -3325,7 +3490,7 @@
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.12-dev"
+                    "dev-master": "1.13-dev"
                 }
             },
             "autoload": {
@@ -3358,66 +3523,7 @@
                 "polyfill",
                 "portable"
             ],
-            "time": "2019-08-06T08:03:45+00:00"
-        },
-        {
-            "name": "symfony/yaml",
-            "version": "v4.3.4",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/symfony/yaml.git",
-                "reference": "5a0b7c32dc3ec56fd4abae8a4a71b0cf05013686"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/symfony/yaml/zipball/5a0b7c32dc3ec56fd4abae8a4a71b0cf05013686",
-                "reference": "5a0b7c32dc3ec56fd4abae8a4a71b0cf05013686",
-                "shasum": ""
-            },
-            "require": {
-                "php": "^7.1.3",
-                "symfony/polyfill-ctype": "~1.8"
-            },
-            "conflict": {
-                "symfony/console": "<3.4"
-            },
-            "require-dev": {
-                "symfony/console": "~3.4|~4.0"
-            },
-            "suggest": {
-                "symfony/console": "For validating YAML files using the lint command"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "4.3-dev"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "Symfony\\Component\\Yaml\\": ""
-                },
-                "exclude-from-classmap": [
-                    "/Tests/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Fabien Potencier",
-                    "email": "fabien@symfony.com"
-                },
-                {
-                    "name": "Symfony Community",
-                    "homepage": "https://symfony.com/contributors"
-                }
-            ],
-            "description": "Symfony Yaml Component",
-            "homepage": "https://symfony.com",
-            "time": "2019-08-20T14:27:59+00:00"
+            "time": "2019-11-27T13:56:44+00:00"
         },
         {
             "name": "theseer/tokenizer",
@@ -3452,8 +3558,8 @@
             "authors": [
                 {
                     "name": "Arne Blankerts",
-                    "role": "Developer",
-                    "email": "arne@blankerts.de"
+                    "email": "arne@blankerts.de",
+                    "role": "Developer"
                 }
             ],
             "description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
@@ -3461,31 +3567,29 @@
         },
         {
             "name": "webmozart/assert",
-            "version": "1.5.0",
+            "version": "1.6.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/webmozart/assert.git",
-                "reference": "88e6d84706d09a236046d686bbea96f07b3a34f4"
+                "reference": "573381c0a64f155a0d9a23f4b0c797194805b925"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/webmozart/assert/zipball/88e6d84706d09a236046d686bbea96f07b3a34f4",
-                "reference": "88e6d84706d09a236046d686bbea96f07b3a34f4",
+                "url": "https://api.github.com/repos/webmozart/assert/zipball/573381c0a64f155a0d9a23f4b0c797194805b925",
+                "reference": "573381c0a64f155a0d9a23f4b0c797194805b925",
                 "shasum": ""
             },
             "require": {
                 "php": "^5.3.3 || ^7.0",
                 "symfony/polyfill-ctype": "^1.8"
             },
+            "conflict": {
+                "vimeo/psalm": "<3.6.0"
+            },
             "require-dev": {
                 "phpunit/phpunit": "^4.8.36 || ^7.5.13"
             },
             "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "1.3-dev"
-                }
-            },
             "autoload": {
                 "psr-4": {
                     "Webmozart\\Assert\\": "src/"
@@ -3507,7 +3611,7 @@
                 "check",
                 "validate"
             ],
-            "time": "2019-08-24T08:43:50+00:00"
+            "time": "2019-11-24T13:36:37+00:00"
         }
     ],
     "aliases": [],
diff --git a/conf-dev/Dockerfile b/conf-dev/Dockerfile
index 4b38349..6563bae 100644
--- a/conf-dev/Dockerfile
+++ b/conf-dev/Dockerfile
@@ -9,10 +9,9 @@ RUN apt-get update \
 RUN pecl install xdebug \
     && rm -rf /tmp/pear
 
+RUN touch /var/log/xdebug_remote.log && chown www-data:www-data /var/log/xdebug_remote.log
+
 # Install mod_rewrite
 RUN a2enmod rewrite
 
-# Create doctrine_proxy folder
-RUN mkdir /tmp/doctrine_proxy && chmod 777 /tmp/doctrine_proxy
-
-CMD ["apache2-foreground"]
\ No newline at end of file
+CMD ["apache2-foreground"]
diff --git a/conf-dev/anis_init.sh b/conf-dev/anis_init.sh
deleted file mode 100755
index b29cf08..0000000
--- a/conf-dev/anis_init.sh
+++ /dev/null
@@ -1,72 +0,0 @@
-#!/bin/sh
-set -e
-
-# Create the anis admin database (only tables)
-./vendor/bin/doctrine orm:schema-tool:create
-
-# Add the first anis superuser
-curl -d '{"email":"admin@anis.fr","password":"admin"}' -H "Content-Type: application/json" -X POST http://localhost/login/register
-curl -d '{"adminsi":true,"superuser":true,"activated":true}' -H "Content-Type: application/json" -X PUT http://localhost/admin/user/admin@anis.fr
-
-# Add settings for admin inteface
-curl -d '{"name":"search_flag","label":"Search flag"}' -H "Content-Type: application/json" -X POST http://localhost/admin/settings/select
-curl -d '{"name":"search_type","label":"Search Type"}' -H "Content-Type: application/json" -X POST http://localhost/admin/settings/select
-curl -d '{"name":"operator","label":"Operator"}' -H "Content-Type: application/json" -X POST http://localhost/admin/settings/select
-curl -d '{"name":"renderer","label":"Renderer"}' -H "Content-Type: application/json" -X POST http://localhost/admin/settings/select
-curl -d '{"name":"renderer_detail","label":"Renderer detail"}' -H "Content-Type: application/json" -X POST http://localhost/admin/settings/select
-
-curl -d '{"label":"ID","value":"ID","display":10,"id_settings_select":1}' -H "Content-Type: application/json" -X POST http://localhost/admin/settings/option
-curl -d '{"label":"RA","value":"RA","display":20,"id_settings_select":1}' -H "Content-Type: application/json" -X POST http://localhost/admin/settings/option
-curl -d '{"label":"DEC","value":"DEC","display":30,"id_settings_select":1}' -H "Content-Type: application/json" -X POST http://localhost/admin/settings/option
-
-curl -d '{"label":"Field","value":"field","display":10,"id_settings_select":2}' -H "Content-Type: application/json" -X POST http://localhost/admin/settings/option
-curl -d '{"label":"Between","value":"between","display":20,"id_settings_select":2}' -H "Content-Type: application/json" -X POST http://localhost/admin/settings/option
-curl -d '{"label":"Select","value":"select","display":30,"id_settings_select":2}' -H "Content-Type: application/json" -X POST http://localhost/admin/settings/option
-curl -d '{"label":"Select multiple","value":"select-multiple","display":40,"id_settings_select":2}' -H "Content-Type: application/json" -X POST http://localhost/admin/settings/option
-curl -d '{"label":"Datalist","value":"datalist","display":50,"id_settings_select":2}' -H "Content-Type: application/json" -X POST http://localhost/admin/settings/option
-curl -d '{"label":"Radio","value":"radio","display":60,"id_settings_select":2}' -H "Content-Type: application/json" -X POST http://localhost/admin/settings/option
-curl -d '{"label":"Checkbox","value":"checkbox","display":70,"id_settings_select":2}' -H "Content-Type: application/json" -X POST http://localhost/admin/settings/option
-curl -d '{"label":"Between date","value":"between-date","display":80,"id_settings_select":2}' -H "Content-Type: application/json" -X POST http://localhost/admin/settings/option
-curl -d '{"label":"Date","value":"date","display":90,"id_settings_select":2}' -H "Content-Type: application/json" -X POST http://localhost/admin/settings/option
-curl -d '{"label":"Time","value":"time","display":100,"id_settings_select":2}' -H "Content-Type: application/json" -X POST http://localhost/admin/settings/option
-curl -d '{"label":"Date time","value":"date-time","display":110,"id_settings_select":2}' -H "Content-Type: application/json" -X POST http://localhost/admin/settings/option
-curl -d '{"label":"JSON","value":"json","display":120,"id_settings_select":2}' -H "Content-Type: application/json" -X POST http://localhost/admin/settings/option
-
-curl -d '{"label":"=","value":"eq","display":10,"id_settings_select":3}' -H "Content-Type: application/json" -X POST http://localhost/admin/settings/option
-curl -d '{"label":"≠","value":"neq","display":20,"id_settings_select":3}' -H "Content-Type: application/json" -X POST http://localhost/admin/settings/option
-curl -d '{"label":"between","value":"bw","display":30,"id_settings_select":3}' -H "Content-Type: application/json" -X POST http://localhost/admin/settings/option
-curl -d '{"label":">","value":"gt","display":40,"id_settings_select":3}' -H "Content-Type: application/json" -X POST http://localhost/admin/settings/option
-curl -d '{"label":">=","value":"gte","display":50,"id_settings_select":3}' -H "Content-Type: application/json" -X POST http://localhost/admin/settings/option
-curl -d '{"label":"<","value":"lt","display":60,"id_settings_select":3}' -H "Content-Type: application/json" -X POST http://localhost/admin/settings/option
-curl -d '{"label":"<=","value":"lte","display":70,"id_settings_select":3}' -H "Content-Type: application/json" -X POST http://localhost/admin/settings/option
-curl -d '{"label":"like","value":"lk","display":80,"id_settings_select":3}' -H "Content-Type: application/json" -X POST http://localhost/admin/settings/option
-curl -d '{"label":"not like","value":"nlk","display":90,"id_settings_select":3}' -H "Content-Type: application/json" -X POST http://localhost/admin/settings/option
-curl -d '{"label":"in","value":"in","display":100,"id_settings_select":3}' -H "Content-Type: application/json" -X POST http://localhost/admin/settings/option
-curl -d '{"label":"not in","value":"nin","display":110,"id_settings_select":3}' -H "Content-Type: application/json" -X POST http://localhost/admin/settings/option
-curl -d '{"label":"null","value":"nl","display":120,"id_settings_select":3}' -H "Content-Type: application/json" -X POST http://localhost/admin/settings/option
-curl -d '{"label":"not null","value":"nnl","display":130,"id_settings_select":3}' -H "Content-Type: application/json" -X POST http://localhost/admin/settings/option
-curl -d '{"label":"json","value":"js","display":140,"id_settings_select":3}' -H "Content-Type: application/json" -X POST http://localhost/admin/settings/option
-
-curl -d '{"label":"Image","value":"img","display":10,"id_settings_select":4}' -H "Content-Type: application/json" -X POST http://localhost/admin/settings/option
-curl -d '{"label":"Thumbnail","value":"thumbnail","display":20,"id_settings_select":4}' -H "Content-Type: application/json" -X POST http://localhost/admin/settings/option
-curl -d '{"label":"Link","value":"link","display":30,"id_settings_select":4}' -H "Content-Type: application/json" -X POST http://localhost/admin/settings/option
-curl -d '{"label":"Button","value":"btn","display":40,"id_settings_select":4}' -H "Content-Type: application/json" -X POST http://localhost/admin/settings/option
-curl -d '{"label":"Detail link","value":"detail-link","display":50,"id_settings_select":4}' -H "Content-Type: application/json" -X POST http://localhost/admin/settings/option
-curl -d '{"label":"Detail button","value":"detail-btn","display":60,"id_settings_select":4}' -H "Content-Type: application/json" -X POST http://localhost/admin/settings/option
-curl -d '{"label":"Download","value":"download","display":70,"id_settings_select":4}' -H "Content-Type: application/json" -X POST http://localhost/admin/settings/option
-curl -d '{"label":"JSON","value":"json","display":80,"id_settings_select":4}' -H "Content-Type: application/json" -X POST http://localhost/admin/settings/option
-
-curl -d '{"label":"Image","value":"img","display":10,"id_settings_select":5}' -H "Content-Type: application/json" -X POST http://localhost/admin/settings/option
-
-# Generate default metamodel
-curl -d '{"name":"default","label":"Default instance","dev_mode":true,"driver":"pdo_pgsql","path":"","host":"db","port":5432,"login":"anis","password":"anis","path_proxy":"/tmp/doctrine_proxy","dbname":"anis_metamodel"}' -H "Content-Type: application/json" -X POST http://localhost/admin/instance
-curl -X GET http://localhost/admin/instance/default/create-database
-
-# Add admin group to the default metamodel
-curl -d '{"label":"Admin"}' -H "Content-Type: application/json" -X POST http://localhost/metadata/default/group
-
-# Add all default families 
-curl -d '{"label":"Default dataset family","display":10}' -H "Content-Type: application/json" -X POST http://localhost/metadata/default/family/dataset
-curl -d '{"label":"Default criteria family","display":10}' -H "Content-Type: application/json" -X POST http://localhost/metadata/default/family/criteria
-curl -d '{"label":"Default output family","display":10}' -H "Content-Type: application/json" -X POST http://localhost/metadata/default/family/output
-curl -d '{"label":"Default output category","display":10,"id_output_family":1}' -H "Content-Type: application/json" -X POST http://localhost/metadata/default/output-category
\ No newline at end of file
diff --git a/conf-dev/dev-init.sh b/conf-dev/dev-meta.sh
similarity index 79%
rename from conf-dev/dev-init.sh
rename to conf-dev/dev-meta.sh
index f178091..9db6662 100644
--- a/conf-dev/dev-init.sh
+++ b/conf-dev/dev-meta.sh
@@ -1,8 +1,9 @@
 #!/bin/sh
 set -e
 
-curl -d '{"label":"Test","dbname":"anis_test","dbtype":"pdo_pgsql","dbhost":"db","dbport":5432,"dblogin":"anis","dbpassword":"anis"}' -H "Content-Type: application/json" -X POST http://localhost/metadata/default/database
-curl -d '{"name":"anis_project","label":"Anis Project Test","description":"Project used for testing","link":"http://project.com","manager":"M. Durand","id_database":1}' -H "Content-Type: application/json" -X POST http://localhost/metadata/default/project
+curl -d '{"label":"Test","dbname":"anis_test","dbtype":"pdo_pgsql","dbhost":"db","dbport":5432,"dblogin":"anis","dbpassword":"anis"}' -H "Content-Type: application/json" -X POST http://localhost/database
+curl -d '{"name":"anis_project","label":"Anis Project Test","description":"Project used for testing","link":"http://project.com","manager":"M. Durand","id_database":1}' -H "Content-Type: application/json" -X POST http://localhost/project
 
-curl -d '{"name":"obs_cat","table_ref":"obs_cat","label":"ObsCat dataset","description":"ObsCat","display":"10","count":"10000","vo":false,"data_path":"/mnt/mount","selectable_row":true,"project_name":"anis_project","id_dataset_family":1}' -H "Content-Type: application/json" -X POST http://localhost/metadata/default/dataset
-curl -d '{"name":"observations","table_ref":"observations_info","label":"Observations dataset","description":"Observations","display":"20","count":"177454","vo":false,"data_path":"/mnt/mount","selectable_row":false,"project_name":"anis_project","id_dataset_family":1}' -H "Content-Type: application/json" -X POST http://localhost/metadata/default/dataset
+curl -d '{"label":"Default dataset family","display":10}' -H "Content-Type: application/json" -X POST http://localhost/family/dataset
+curl -d '{"name":"obs_cat","table_ref":"obs_cat","label":"ObsCat dataset","description":"ObsCat","display":"10","count":"10000","vo":false,"data_path":"/mnt/mount","selectable_row":true,"project_name":"anis_project","id_dataset_family":1}' -H "Content-Type: application/json" -X POST http://localhost/dataset
+curl -d '{"name":"observations","table_ref":"observations_info","label":"Observations dataset","description":"Observations","display":"20","count":"177454","vo":false,"data_path":"/mnt/mount","selectable_row":false,"project_name":"anis_project","id_dataset_family":1}' -H "Content-Type: application/json" -X POST http://localhost/dataset
diff --git a/conf-dev/generate_encryption_key.php b/conf-dev/generate_encryption_key.php
deleted file mode 100644
index 6ca7ab6..0000000
--- a/conf-dev/generate_encryption_key.php
+++ /dev/null
@@ -1,2 +0,0 @@
-<?php
-echo base64_encode(openssl_random_pseudo_bytes(32)) . PHP_EOL;
diff --git a/conf-dev/init-postgres.sh b/conf-dev/init-postgres.sh
index c3c2083..ef67c76 100644
--- a/conf-dev/init-postgres.sh
+++ b/conf-dev/init-postgres.sh
@@ -3,11 +3,10 @@ set -e
 
 psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
     CREATE USER anis LOGIN PASSWORD 'anis';
-    CREATE DATABASE anis_admin;
     CREATE DATABASE anis_metamodel;
     CREATE DATABASE anis_test;
     GRANT ALL PRIVILEGES ON DATABASE anis_metamodel TO anis;
     GRANT ALL PRIVILEGES ON DATABASE anis_test TO anis;
 EOSQL
 psql -v ON_ERROR_STOP=1 -f /sql/obs_cat.sql --username "anis" --dbname "anis_test"
-psql -v ON_ERROR_STOP=1 -f /sql/observations_info.sql --username "anis" --dbname "anis_test"
\ No newline at end of file
+psql -v ON_ERROR_STOP=1 -f /sql/observations_info.sql --username "anis" --dbname "anis_test"
diff --git a/conf-dev/vhost.conf b/conf-dev/vhost.conf
index 5723255..723b9ec 100644
--- a/conf-dev/vhost.conf
+++ b/conf-dev/vhost.conf
@@ -1,7 +1,7 @@
 <VirtualHost *:80>
-    DocumentRoot /srv/app/public
+    DocumentRoot /project/public
 
-    <Directory "/srv/app/public">
+    <Directory "/project/public">
         Require all granted
         RewriteEngine on
         RewriteRule ^.+$ index.php [L]
@@ -9,4 +9,4 @@
 
     ErrorLog ${APACHE_LOG_DIR}/error.log
     CustomLog ${APACHE_LOG_DIR}/access.log combined
-</VirtualHost>
\ No newline at end of file
+</VirtualHost>
diff --git a/docker-compose.yml b/docker-compose.yml
index 8491513..704697d 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -3,37 +3,28 @@ version: '3'
 services:
     php:
         build: conf-dev
-        working_dir: /srv/app
+        working_dir: /project
         environment:
             docker: "true"
-            SLIM_DISPLAY_ERROR_DETAILS: "true"
-            METADATA_DOCTRINE_PATH_PROXY: "/tmp/doctrine_proxy"
-            METADATA_DOCTRINE_DEV_MODE: "true"
-            METADATA_DB_DRIVER: "pdo_pgsql"
-            METADATA_DB_HOST: "db"
-            METADATA_DB_PORT: 5432
-            METADATA_DB_DBNAME: "anis_admin"
-            METADATA_DB_USER: "anis"
-            METADATA_DB_PASSWORD: "anis"
-            MAILER_HOST: "mailer"
-            MAILER_PORT: 25
-            LOGGER_NAME: "anis-v3-server"
-            LOGGER_PATH: "php://stdout"
+            DISPLAY_ERROR_DETAILS: 'true'
+            DATABASE_PATH_PROXY: "/tmp/doctrine_proxy"
+            DATABASE_DEV_MODE: "true"
+            DATABASE_CO_DRIVER: "pdo_pgsql"
+            DATABASE_CO_HOST: "db"
+            DATABASE_CO_PORT: 5432
+            DATABASE_CO_DBNAME: "anis_metamodel"
+            DATABASE_CO_USER: "postgres"
+            DATABASE_CO_PASSWORD: "postgres"
+            LOGGER_NAME: "anis-metamodel"
+            LOGGER_PATH: "php://stderr"
             LOGGER_LEVEL: "debug"
-            AMQP_HOST: ${AMQP_HOST}
-            AMQP_PORT: 5672
-            AMQP_USER: "guest"
-            AMQP_PASSWORD: "guest"
         ports:
-            - 8080:80
+            - 8082:80
         volumes:
-            - .:/srv/app
+            - .:/project
             - ./conf-dev/dev-php.ini:/usr/local/etc/php/conf.d/dev-php.ini
             - ./conf-dev/vhost.conf:/etc/apache2/sites-available/000-default.conf
-    mailer:
-        image: djfarrelly/maildev
-        ports:
-            - 1080:80
+
     db:
         image: postgres
         environment:
@@ -43,10 +34,6 @@ services:
             - ./conf-dev/obs_cat.sql:/sql/obs_cat.sql
             - ./conf-dev/observations_info.sql:/sql/observations_info.sql
             - ./conf-dev/init-postgres.sh:/docker-entrypoint-initdb.d/init-postgres.sh
-    adminer:
-        image: adminer
-        ports:
-            - 8083:8080
 
 volumes:
-    pgdata:
+    pgdata:
\ No newline at end of file
diff --git a/documentation/api/activate-account.md b/documentation/api/activate-account.md
deleted file mode 100644
index a9907b5..0000000
--- a/documentation/api/activate-account.md
+++ /dev/null
@@ -1,100 +0,0 @@
-# Enregistrer un nouvel utilisateur
-----
-1. Permet d'activer un nouvel utilisateur ANIS (`GET`)
-
-### URL
-
-- /activate-account
-
-### Method
-
-- `OPTIONS`
-- `GET`
-
-### Headers
-
-None
-
-### URL Params
-
-| Nom du paramètre | Type   | Description                                                | Obligatoire  |
-| :--------------- | :----- | :--------------------------------------------------------- | :----------: |
-| email            | string | Adresse email du nouvel utilisateur                        | [x]          |
-| activation_key   | string | Clé d'activation qui permet de valider l'utilisateur       | [x]          |
-
-### Data Params
-
-None
-
-### Success Response
-
-**Code:** 200 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-	"data": {
-		"email": "user1@anis.fr",
-		"activated": true,
-		"adminsi": false,
-		"superuser": false,
-		"id_group": 1
-	}
-}
-```
-
-### Error Response
-
-1. Si le paramètre email est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param email needed to activate a new user account"
-}
-```
-
-2. Si le paramètre activation key est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param activation_key needed to activate a new user account"
-}
-```
-
-3. Si l'utilisateur identifié par l'email envoyé dans la requête n'éxiste pas dans la base de données
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-    "error": "Invalid user",
-    "error_description": "HTTP 400: No account is identified with this email address"
-}
-```
-
-4. Si la clé d'activation envoyée par l'utilisateur n'est pas valide
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid activation key",
-    "error_description": "HTTP 400: Bad activation key; Unable to activate account"
-}
-```
-
-### Exemple d'utilisation
-
-1. Enregistrer un nouvel utilisateur 
-
-> curl -X GET http://localhost:8989/activate-account?email=user1@anis.fr&activation_key=noioioi
diff --git a/documentation/api/category-list.md b/documentation/api/category-list.md
deleted file mode 100644
index 3e56ade..0000000
--- a/documentation/api/category-list.md
+++ /dev/null
@@ -1,93 +0,0 @@
-# Metadata: Category collection
-----
-1. Permet de récupérer l'ensemble des category disponibles dans la base de données metadata (`GET`)
-2. Permet également de créer une nouvelle category (`POST`)
-
-### URL
-
-- /metadata/category
-
-### Method
-
-- `GET`
-- `POST`
-  
-### Headers
-
-- Content-type: application/json
-
-### URL Params
-
-None
-
-### Data Params
-
-1. Pour une requête GET
-
-None
-
-2. Pour une requête POST
-
-| Nom du paramètre | Type   | Description                                       | Obligatoire  |
-| :--------------- | :----- | :------------------------------------------------ | :----------: |
-| label            | string | Le label de la category à créer                   | [x]          |
-
-### Success Response
-
-1. `GET` 
-
-**Code:** 200 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-	"data": [{
-		"id": 3,
-		"label": "Default"
-	}, {
-		"id": 4,
-		"label": "Instrumental settings"
-	}, {
-		"id": 5,
-		"label": "Photometry"
-	}]
-}
-```
- 
-2. `POST` 
-
-**Code:** 200 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-	"data": {
-		"id": 9,
-		"label": "My category"
-	}
-}
-```
-
-### Error Response
-
-1. Si le paramètre label est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param label needed to add a new category"
-}
-```
-
-### Exemples d'utilisation
-
-1. Récuperer l'ensemble des databases metadata
-
-> curl -X GET http://localhost:8989/metadata/category
-
-2. Ajouter une nouvelle database metadata
-
-> curl -d '{"label":"My category"}' -H "Content-Type: application/json" -X POST http://localhost:8989/metadata/category
diff --git a/documentation/api/category.md b/documentation/api/category.md
deleted file mode 100644
index 42d3182..0000000
--- a/documentation/api/category.md
+++ /dev/null
@@ -1,122 +0,0 @@
-# Metadata: Category item
-----
-1. Permet de récupérer la category qui correspond à l'identifiant passé dans l'URL (`GET`)
-2. Permet de modifier intégralement la category qui correspond à l'identifiant passé dans l'URL (`PUT`)
-3. Permet de supprimer la category qui correspond à l'identifiant passé dans l'URL (`DELETE`)
-
-### URL
-
-- /metadata/category/:id
-
-### Method
-
-- `GET`
-- `PUT`
-- `DELETE`
-  
-### Headers
-
-- Content-type: application/json
-
-### URL Params
-
-| Nom du paramètre | Type | Description                |
-| :--------------- | :--- | :--------------------------|
-| id               | int  | Identifiant de la category |
-
-### Data Params
-
-1. Pour une requête GET
-
-None
-
-2. Pour une requête PUT
-
-| Nom du paramètre | Type   | Description                                       | Obligatoire  |
-| :--------------- | :----- | :------------------------------------------------ | :----------: |
-| label            | string | Le label de la catégory à modifier                | [x]          |
-
-3. Pour une requête DELETE
-
-None
-
-### Success Response
-
-1. `GET` 
-
-**Code:** 200 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-	"data": {
-		"id": 3,
-		"label": "Default category"
-	}
-}
-```
- 
-2. `PUT` 
-
-**Code:** 200 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-	"data": {
-		"id": 9,
-		"label": "My modified category"
-	}
-}
-```
-
-3. `DELETE`
-
-**Code:** 200 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-	"message": "Category with id 1 is removed!"
-}
-```
-
-### Error Response
-
-1. Si la category avec l'id passé en paramètre est inconnu
-
-**Code:** 404 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 404: Category with id 15 is not found"
-}
-```
-
-2. Si le paramètre label est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param label needed to edit the category"
-}
-```
-
-### Exemples d'utilisation
-
-1. Récuperer la category qui correspond à l'identifiant passé en paramètre
-
-> curl -X GET http://localhost:8989/metadata/category/9
-
-2. Modifier la category qui correspond à l'identifiant passé en paramètre
-
-> curl -d '{"label":"My modified category"}' -H "Content-Type: application/json" -X PUT http://localhost:8989/metadata/category/9
-
-3. Supprimer la category qui correspond à l'identifiant passé en paramètre
-
-> curl -X DELETE http://localhost:8989/metadata/category/9
diff --git a/documentation/api/change-password.md b/documentation/api/change-password.md
deleted file mode 100644
index f1c7cf6..0000000
--- a/documentation/api/change-password.md
+++ /dev/null
@@ -1,119 +0,0 @@
-# Changement du mot de passe utilisateur
-----
-1. Permet à l'utilisateur de changer son mot de passe de login (`POST`)
-
-### URL
-
-- /change-password
-
-### Method
-
-- `OPTIONS`
-- `POST`
-  
-### Headers
-
-- Content-type: application/json
-
-### URL Params
-
-None
-
-### Data Params
-
-| Nom du paramètre | Type   | Description                           | Obligatoire  |
-| :--------------- | :----- | :------------------------------------ | :----------: |
-| email            | string | Adresse email de l'utilisateur        | [x]          |
-| password         | string | Mot de passe de l'utilisateur         | [x]          |
-| new_password     | string | Nouveau mot de passe de l'utilisateur | [x]          |
-
-### Success Response
-
-**Code:** 200 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-	"message": "Password changed!"
-}
-```
-
-### Error Response
-
-1. Si le paramètre email est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param email needed to change your password"
-}
-```
-
-2. Si le paramètre password est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param password needed to change your password"
-}
-```
-
-3. Si le paramètre new_password est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param new_password needed to change your password"
-}
-```
-
-4. Si l'utilisateur identifié par l'email envoyé dans la requête n'éxiste pas dans la base de données
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-    "error": "Invalid user",
-    "error_description": "HTTP 400: No account is identified with this email address"
-}
-```
-
-5. Si le compte utilisateur n'est pas encore activé
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-    "error": "Invalid user",
-    "error_description": "HTTP 400: Account not yet activated"
-}
-```
-
-6. Si l'utilisateur a renseigné un mauvais mot de passe
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-    "error": "Invalid user",
-    "error_description": "HTTP 400: Bad password; unable to change the password"
-}
-```
-
-### Exemple d'utilisation
-
-1. Changer son mot de passe
-
-> curl -d '{"email":"admin@anis.fr", "password":"5b3f576db200c", "new_password":"admin"}' -H "Content-Type: application/json" -X POST http://localhost:8989/change-password
diff --git a/documentation/api/database-list.md b/documentation/api/database-list.md
deleted file mode 100644
index 3569c55..0000000
--- a/documentation/api/database-list.md
+++ /dev/null
@@ -1,194 +0,0 @@
-# Metadata: Database collection
-----
-1. Permet de récupérer l'ensemble des databases disponibles dans la base de données metadata (`GET`)
-2. Permet également de créer une nouvelle database (`POST`)
-
-### URL
-
-- /metadata/database
-
-### Method
-
-- `OPTIONS`
-- `GET`
-- `POST`
-  
-### Headers
-
-- Content-type: application/json
-
-### URL Params
-
-None
-
-### Data Params
-
-1. Pour une requête GET
-
-None
-
-2. Pour une requête POST
-
-| Nom du paramètre | Type   | Description                                       | Obligatoire  |
-| :--------------- | :----- | :------------------------------------------------ | :----------: |
-| label            | string | Le label de la database à créer                   | [x]          |
-| dname            | string | Le nom de la base de données                      | [x]          |
-| dbtype           | string | Le SGBD qui contient la base de données           | [x]          |
-| dbhost           | string | Le nom de la machine hôte ou est installé le SGBD | [x]          |
-| dbport           | int    | Le port d'écoute du SGBD                          | [x]          |
-| dblogin          | string | Le login de connexion vers la base de données     | [x]          |
-| dbpassword       | string | Le password de connexion vers la base de données  | [x]          |
-
-### Success Response
-
-1. `GET` 
-
-**Code:** 200 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-	"data": [{
-		"id": 16,
-		"label": "Exodat",
-		"dbname": "exodat_new",
-		"dbtype": "pgsql",
-		"dbhost": "cesamsidb",
-		"dbport": 5432,
-		"dblogin": "consult",
-		"dbpassword": "consult"
-	}, {
-		"id": 17,
-		"label": "db",
-		"dbname": "anis_v3_database",
-		"dbtype": "pgsql",
-		"dbhost": "db",
-		"dbport": 5432,
-		"dblogin": "anis",
-		"dbpassword": "anis"
-	}, {
-		"id": 18,
-		"label": "Cosmologydb",
-		"dbname": "cosmologydb",
-		"dbtype": "pgsql",
-		"dbhost": "cesamsidb",
-		"dbport": 5432,
-		"dblogin": "consult",
-		"dbpassword": "consult"
-	}]
-}
-```
- 
-2. `POST` 
-
-**Code:** 201 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-	"id": 16,
-	"label": "Exodat",
-	"dbname": "exodat_new",
-	"dbtype": "pgsql",
-	"dbhost": "cesamsidb",
-	"dbport": 5432,
-	"dblogin": "consult",
-	"dbpassword": "consult"
-}
-```
-
-### Error Response
-
-1. Si le paramètre label est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param label needed to add a new database"
-}
-```
-
-2. Si le paramètre dbname est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param dbname needed to add a new database"
-}
-```
-
-3. Si le paramètre dbtype est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param dbtype needed to add a new database"
-}
-```
-
-4. Si le paramètre dbhost est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param dbhost needed to add a new database"
-}
-```
-
-5. Si le paramètre dbport est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param dbport needed to add a new database"
-}
-```
-
-6. Si le paramètre dblogin est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param dblogin needed to add a new database"
-}
-```
-
-7. Si le paramètre dbpassword est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param dbpassword needed to add a new database"
-}
-```
-
-### Exemples d'utilisation
-
-1. Récuperer l'ensemble des databases metadata
-
-> curl -X GET http://localhost:8989/metadata/database
-
-2. Ajouter une nouvelle database metadata
-
-> curl -d '{"label":"Test","dbname":"test","dbtype":"pgsql","dbhost":"cesamsidb","dbport":5432,"dblogin":"consult","dbpassword":"consult"}' -H "Content-Type: application/json" -X POST http://localhost:8989/metadata/database
diff --git a/documentation/api/database.md b/documentation/api/database.md
deleted file mode 100644
index 7294469..0000000
--- a/documentation/api/database.md
+++ /dev/null
@@ -1,212 +0,0 @@
-# Metadata: Database item
-----
-1. Permet de récupérer la database qui correspond à l'identifiant passé dans l'URL (`GET`)
-2. Permet de modifier intégralement la database qui correspond à l'identifiant passé dans l'URL (`PUT`)
-3. Permet de supprimer la database qui correspond à l'identifiant passé dans l'URL (`DELETE`)
-
-### URL
-
-- /metadata/database/:id
-
-### Method
-
-- `GET`
-- `PUT`
-- `DELETE`
-  
-### Headers
-
-- Content-type: application/json
-
-### URL Params
-
-| Nom du paramètre | Type | Description                |
-| :--------------- | :--- | :--------------------------|
-| id               | int  | Identifiant de la database |
-
-### Data Params
-
-1. Pour une requête GET
-
-None
-
-2. Pour une requête PUT
-
-| Nom du paramètre | Type   | Description                                       | Obligatoire  |
-| :--------------- | :----- | :------------------------------------------------ | :----------: |
-| label            | string | Le label de la database à créer                   | [x]          |
-| dname            | string | Le nom de la base de données                      | [x]          |
-| dbtype           | string | Le SGBD qui contient la base de données           | [x]          |
-| dbhost           | string | Le nom de la machine hôte ou est installé le SGBD | [x]          |
-| dbport           | int    | Le port d'écoute du SGBD                          | [x]          |
-| dblogin          | string | Le login de connexion vers la base de données     | [x]          |
-| dbpassword       | string | Le password de connexion vers la base de données  | [x]          |
-
-3. Pour une requête DELETE
-
-None
-
-### Success Response
-
-1. `GET` 
-
-**Code:** 200 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-	"data": {
-		"id": 19,
-		"label": "Test",
-		"dbname": "test",
-		"dbtype": "pgsql",
-		"dbhost": "cesamsidb",
-		"dbport": 5432,
-		"dblogin": "consult",
-		"dbpassword": "consult"
-	}
-}
-```
- 
-2. `PUT` 
-
-**Code:** 200 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-	"data": {
-		"id": 19,
-		"label": "Test",
-		"dbname": "test",
-		"dbtype": "pgsql",
-		"dbhost": "cesamsidb",
-		"dbport": 5432,
-		"dblogin": "consult",
-		"dbpassword": "consult"
-	}
-}
-```
-
-3. `DELETE`
-
-**Code:** 200 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-	"message": "Database with id 1 is removed!"
-}
-```
-
-### Error Response
-
-1. Si la database avec l'id passé en paramètre est inconnu
-
-**Code:** 404 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 404: Database with id 15 is not found"
-}
-```
-
-2. Si le paramètre label est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param label needed to edit the database"
-}
-```
-
-3. Si le paramètre dbname est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param dbname needed to edit the database"
-}
-```
-
-4. Si le paramètre dbtype est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param dbtype needed to edit the database"
-}
-```
-
-5. Si le paramètre dbhost est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param dbhost needed to edit the database"
-}
-```
-
-6. Si le paramètre dbport est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param dbport needed to edit the database"
-}
-```
-
-7. Si le paramètre dblogin est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param dblogin needed to edit the database"
-}
-```
-
-8. Si le paramètre dbpassword est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param dbpassword needed to edit the database"
-}
-```
-
-### Exemples d'utilisation
-
-1. Récuperer la database qui correspond à l'identifiant passé en paramètre
-
-> curl -X GET http://localhost:8989/metadata/database/16
-
-2. Modifier la database qui correspond à l'identifiant passé en paramètre
-
-> curl -d '{"label":"Test_modif","dbname":"test","dbtype":"pgsql","dbhost":"cesamsidb","dbport":5432,"dblogin":"consult","dbpassword":"consult"}' -H "Content-Type: application/json" -X POST http://localhost:8989/metadata/database/19
-
-3. Supprimer la database qui correspond à l'identifiant passé en paramètre
-
-> curl -X DELETE http://localhost:8989/metadata/database/19
diff --git a/documentation/api/dataset-list.md b/documentation/api/dataset-list.md
deleted file mode 100644
index d05fe6d..0000000
--- a/documentation/api/dataset-list.md
+++ /dev/null
@@ -1,278 +0,0 @@
-# Metadata: Dataset collection
-----
-1. Permet de récupérer l'ensemble des datasets disponibles dans la base de données metadata (`GET`)
-2. Permet également de créer une nouveau dataset (`POST`)
-
-### URL
-
-- /metadata/dataset
-
-### Method
-
-- `GET`
-- `POST`
-  
-### Headers
-
-- Content-type: application/json
-
-### URL Params
-
-None
-
-### Data Params
-
-1. Pour une requête GET
-
-None
-
-2. Pour une requête POST
-
-| Nom du paramètre  | Type   | Description                                                        | Obligatoire  |
-| :---------------- | :----- | :----------------------------------------------------------------- | :----------: |
-| name              | string | Nom qui identifie le dataset                                       | [x]          |
-| table_ref         | string | Table ou vue de référence pour le dataset                          | [x]          |
-| label             | string | Label du dataset                                                   | [x]          |
-| description       | text   | Description du dataset                                             | [x]          |
-| illustration      | string | Image stocké dans le data_pah et qui permet d'illustrer le dataset | [x]          |
-| display           | int    | Numéro d'ordre d'apparation du dataset dans sa famille             | [x]          |
-| count             | int    | Nom d'enregistrements dans le dataset                              | [x]          |
-| vo                | int    | Permet d'activer les fonctionnalités VO pour le dataset            | [x]          |
-| data_path         | int    | Chemin vers le stockage des fichiers associés au dataset           | [x]          |
-| project_name      | int    | Nom qui identifie le projet qui contiendra le dataset              | [x]          |
-| id_dataset_family | int    | Identifiant de la famille ou sera rangé le dataset                 | [x]          |
-
-### Success Response
-
-1. `GET` 
-
-**Code:** 200 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-	"data": [{
-		"name": "exo2mass_cat",
-		"table_ref": "v_exo2mass_joins",
-		"label": "Stars in the CoRoT eyes",
-		"description": "It is the reference catalog manufactured by exodat team used to prepare the next observations (EXO2MASS).",
-		"display": 10,
-		"count": 65865234,
-		"vo": true,
-		"data_path": "",
-		"project_name": "exodat",
-		"id_dataset_family": 4
-	}, {
-		"name": "aspic_vuds_dr1_cosmos",
-		"table_ref": "aspic_vuds_dr1_cosmos",
-		"label": "VUDS-DR1-COSMOS",
-		"description": "VUDS-COSMOS Spectroscopic catalogue",
-		"display": 10,
-		"count": 384,
-		"vo": true,
-		"data_path": "\/mnt\/data-path",
-		"project_name": "aspic",
-		"id_dataset_family": 6
-	}, {
-		"name": "corot_targets",
-		"table_ref": "v_corot_targets",
-		"label": "CoRoT Targets",
-		"description": "Information about targets observed by the CoRoT mission",
-		"display": 30,
-		"count": 177454,
-		"vo": false,
-		"data_path": "",
-		"project_name": "exodat",
-		"id_dataset_family": 5
-	}, {
-		"name": "obscat",
-		"table_ref": "v_obs_cat",
-		"label": "ObsCat",
-		"description": "CoRoT stars observations",
-		"display": 30,
-		"count": 11170033,
-		"vo": true,
-		"data_path": "\/mnt\/data-path",
-		"project_name": "exodat",
-		"id_dataset_family": 4
-	}]
-}
-```
- 
-2. `POST` 
-
-**Code:** 200 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-	"data": {
-		"name": "my_dataset",
-		"table_ref": "my_table",
-		"label": "Mon super dataset",
-		"description": "Une description compl\u00e8te du dataset",
-		"display": 150,
-		"count": 550,
-		"vo": false,
-		"data_path": "\/mnt\/mount",
-		"project_name": "aspic",
-		"id_dataset_family": 5
-	}
-}
-```
-
-### Error Response
-
-1. Si le nom qui identifie le projet à associer au dataset est inconnu (`POST`)
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-	"error": "Invalid request",
-    "error_description": "HTTP 404: Project with name undifined is not found"
-}
-```
-
-2. Si l'identifiant de la famille à associer au dataset est inconnu (`POST`)
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-	"error": "Invalid request",
-    "error_description": "HTTP 404: Dataset family with id 15 is not found"
-}
-```
-
-3. Si le paramètre name est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param name needed to add a new dataset"
-}
-```
-
-4. Si le paramètre table_ref est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param table_ref needed to add a new dataset"
-}
-```
-
-5. Si le paramètre label est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param label needed to add a new dataset"
-}
-```
-
-6. Si le paramètre description est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param description needed to add a new dataset"
-}
-```
-
-7. Si le paramètre display est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param display needed to add a new dataset"
-}
-```
-
-8. Si le paramètre count est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param count needed to add a new dataset"
-}
-```
-
-9. Si le paramètre vo est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param vo needed to add a new dataset"
-}
-```
-
-10. Si le paramètre data_path est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param data_path needed to add a new dataset"
-}
-```
-
-11. Si le paramètre project_name est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param project_name needed to add a new dataset"
-}
-```
-
-12. Si le paramètre id_dataset_family est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param id_dataset_family needed to add a new dataset"
-}
-```
-
-### Exemples d'utilisation
-
-1. Récuperer l'ensemble des datasets
-
-> curl -X GET http://localhost:8989/metadata/dataset
-
-2. Ajouter une nouveau projet
-
-> curl -d '{"name":"my_dataset","table_ref":"my_table","label":"Mon super dataset","description":"Une description complète du dataset","display":"150","count":"550","vo":false,"data_path":"/mnt/mount","project_name":"aspic","id_dataset_family":5}' -H "Content-Type: application/json" -X POST http://localhost:8989/metadata/dataset
diff --git a/documentation/api/dataset.md b/documentation/api/dataset.md
deleted file mode 100644
index c9e5486..0000000
--- a/documentation/api/dataset.md
+++ /dev/null
@@ -1,217 +0,0 @@
-# Metadata: Dataset item
-----
-1. Permet de récupérer le dataset qui correspond à l'identifiant passé dans l'URL (`GET`)
-2. Permet de modifier intégralement le dataset qui correspond à l'identifiant passé dans l'URL (`PUT`)
-3. Permet de supprimer le dataset qui correspond à l'identifiant passé dans l'URL (`DELETE`)
-
-### URL
-
-- /metadata/dataset/:name
-
-### Method
-
-- `GET`
-- `PUT`
-- `DELETE`
-  
-### Headers
-
-- Content-type: application/json
-
-### URL Params
-
-| Nom du paramètre | Type | Description                |
-| :--------------- | :--- | :--------------------------|
-| name             | int  | Identifiant du dataset     |
-
-### Data Params
-
-1. Pour une requête GET
-
-None
-
-2. Pour une requête PUT
-
-| Nom du paramètre  | Type   | Description                                                        | Obligatoire  |
-| :---------------- | :----- | :----------------------------------------------------------------- | :----------: |
-| label             | string | Label du dataset                                                   | [x]          |
-| description       | text   | Description du dataset                                             | [x]          |
-| display           | int    | Numéro d'ordre d'apparation du dataset dans sa famille             | [x]          |
-| count             | int    | Nom d'enregistrements dans le dataset                              | [x]          |
-| vo                | int    | Permet d'activer les fonctionnalités VO pour le dataset            | [x]          |
-| data_path         | int    | Chemin vers le stockage des fichiers associés au dataset           | [x]          |
-| project_name      | int    | Nom qui identifie le projet qui contiendra le dataset              | [x]          |
-| id_dataset_family | int    | Identifiant de la famille ou sera rangé le dataset                 | [x]          |
-
-3. Pour une requête DELETE
-
-None
-
-### Success Response
-
-1. `GET` 
-
-**Code:** 200 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-	"data": {
-		"name": "my_dataset",
-		"table_ref": "my_table",
-		"label": "Mon super dataset",
-		"description": "Une description compl\u00e8te du dataset",
-		"display": 150,
-		"count": 550,
-		"vo": false,
-		"data_path": "\/mnt\/mount",
-		"project_name": "aspic",
-		"id_dataset_family": 5
-	}
-}
-```
- 
-2. `PUT` 
-
-**Code:** 200 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-	"data": {
-		"name": "my_dataset",
-		"table_ref": "my_table",
-		"label": "Mon super dataset",
-		"description": "Une description compl\u00e8te du dataset",
-		"display": 150,
-		"count": 550,
-		"vo": false,
-		"data_path": "\/mnt\/mount",
-		"project_name": "aspic",
-		"id_dataset_family": 5
-	}
-}
-```
-
-3. `DELETE`
-
-**Code:** 200 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-	"message": "Dataset with name corot_targets is removed!"
-}
-```
-
-### Error Response
-
-1. Si l'identifiant de la famille à associer au dataset est inconnu (`POST`)
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-	"error": "Invalid request",
-    "error_description": "HTTP 404: Dataset family with id 15 is not found"
-}
-```
-
-2. Si le paramètre label est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param label needed to edit the dataset"
-}
-```
-
-3. Si le paramètre description est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param description needed to edit the dataset"
-}
-```
-
-4. Si le paramètre display est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param display needed to edit the dataset"
-}
-```
-
-5. Si le paramètre count est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param count needed to edit the dataset"
-}
-```
-
-6. Si le paramètre vo est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param vo needed to edit the dataset"
-}
-```
-
-7. Si le paramètre data_path est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param data_path needed to edit the dataset"
-}
-```
-
-8. Si le paramètre id_dataset_family est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param id_dataset_family needed to edit the dataset"
-}
-```
-
-### Exemples d'utilisation
-
-1. Récuperer le dataset qui correspond à l'identifiant passé en paramètre
-
-> curl -X GET http://localhost:8989/metadata/dataset/corot_targets
-
-2. Modifier le dataset qui correspond à l'identifiant passé en paramètre
-
-> curl -d '{"label":"Mon super dataset","description":"Une description complète du dataset","display":"150","count":"550","vo":false,"data_path":"/mnt/mount","id_dataset_family":5}' -H "Content-Type: application/json" -X PUT http://localhost:8989/metadata/dataset/19
-
-3. Supprimer le dataset qui correspond à l'identifiant passé en paramètre
-
-> curl -X DELETE http://localhost:8989/metadata/dataset/corot_targets
diff --git a/documentation/api/family-list.md b/documentation/api/family-list.md
deleted file mode 100644
index 6474ca2..0000000
--- a/documentation/api/family-list.md
+++ /dev/null
@@ -1,143 +0,0 @@
-# Metadata: Family collection
-----
-1. Permet de récupérer l'ensemble des familles disponibles dans la base de données metadata (`GET`)
-2. Permet également de créer une nouvelle famille (`POST`)
-
-### URL
-
-- /metadata/family/:type
-
-### Method
-
-- `GET`
-- `POST`
-  
-### Headers
-
-- Content-type: application/json
-
-### URL Params
-
-| Nom du paramètre | Type    | Description                             |
-| :--------------- | :------ | :-------------------------              |
-| type             | string  | Type de famille concerné par la requête |
-
-- 3 types sont possibles : 
- 1. `dataset`
- 2. `output`
- 3. `criteria`
-
-### Data Params
-
-1. Pour une requête GET
-
-None
-
-2. Pour une requête POST
-
-- Dans le cas des types `criteria` et `output`
-
-| Nom du paramètre | Type   | Description                                       | Obligatoire  |
-| :--------------- | :----- | :------------------------------------------------ | :----------: |
-| label            | string | Le label de la famille à créer                    | [x]          |
-
-- Dans le cas du type `dataset`
-
-| Nom du paramètre | Type   | Description                                       | Obligatoire  |
-| :--------------- | :----- | :------------------------------------------------ | :----------: |
-| label            | string | Le label de la famille à créer                    | [x]          |
-| description      | text   | La description de la famille de dataset à créer   | [x]          |
-| display          | int    | Position de la famille dans l'ordre d'affichage   | [x]          |
-
-### Success Response
-
-1. `GET` 
-
-**Code:** 200 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-	"data": [{
-		"id": 1,
-		"label": "Criteria by default"
-	}, {
-		"id": 2,
-		"label": "Photometry"
-	}]
-}
-```
- 
-2. `POST` 
-
-**Code:** 200 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-	"data": [{
-		"id": 1,
-		"label": "Spectroscopy"
-	}]
-}
-```
-
-### Error Response
-
-1. Si l'argument type est inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "Type undifined is not defined"
-}
-```
-
-2. Si le paramètre label est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param label needed to add a new family"
-}
-```
-
-3. Si le paramètre description est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param description needed to add a new family"
-}
-```
-
-4. Si le paramètre display est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param display needed to add a new family"
-}
-```
-
-### Exemples d'utilisation
-
-1. Récuperer l'ensemble des familles de criteria
-
-> curl -X GET http://localhost:8989/metadata/family/criteria
-
-2. Ajouter une nouvelle famille de criteria
-
-> curl -d '{"label":"New criteria Family"}' -H "Content-Type: application/json" -X POST http://localhost:8989/metadata/family/criteria
diff --git a/documentation/api/family.md b/documentation/api/family.md
deleted file mode 100644
index fa8b522..0000000
--- a/documentation/api/family.md
+++ /dev/null
@@ -1,177 +0,0 @@
-# Metadata: Family item
-----
-1. Permet de récupérer la famille qui correspond à l'identifiant passé dans l'URL (`GET`)
-2. Permet de modifier intégralement la famille qui correspond à l'identifiant passé dans l'URL (`PUT`)
-3. Permet de supprimer la famille qui correspond à l'identifiant passé dans l'URL (`DELETE`)
-
-### URL
-
-- /metadata/family/:type/:id
-
-### Method
-
-- `GET`
-- `PUT`
-- `DELETE`
-  
-### Headers
-
-- Content-type: application/json
-
-### URL Params
-
-| Nom du paramètre | Type    | Description                             |
-| :--------------- | :------ | :-------------------------              |
-| type             | string  | Type de famille concerné par la requête |
-| id               | int     | Identifiant de la famille               |
-
-### Data Params
-
-1. Pour une requête GET
-
-None
-
-2. Pour une requête PUT
-
-- Dans le cas des types `criteria` et `output`
-
-| Nom du paramètre | Type   | Description                                       | Obligatoire  |
-| :--------------- | :----- | :------------------------------------------------ | :----------: |
-| label            | string | Le label de la famille à créer                    | [x]          |
-
-- Dans le cas du type `dataset`
-
-| Nom du paramètre | Type   | Description                                       | Obligatoire  |
-| :--------------- | :----- | :------------------------------------------------ | :----------: |
-| label            | string | Le label de la famille à créer                    | [x]          |
-| description      | text   | La description de la famille de dataset à créer   | [x]          |
-| display          | int    | Position de la famille dans l'ordre d'affichage   | [x]          |
-
-3. Pour une requête DELETE
-
-None
-
-### Success Response
-
-1. `GET` 
-
-**Code:** 200 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-	"data": {
-		"id": 1,
-    	"label": "VUDS dataset collection",
-        "description": "VUDS datasets description",
-        "display": 10,
-        "type": "dataset",
-        "datasets": []
-	}
-}
-```
- 
-2. `PUT` 
-
-**Code:** 200 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-	"data": {
-		"id": 1,
-    	"label": "Edited family",
-        "description": "Edited Family description",
-        "display": 40,
-        "type": "dataset",
-        "datasets": []
-	}
-}
-```
-
-3. `DELETE`
-
-**Code:** 200 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-	"message": "Dataset family with id 1 is removed!"
-}
-```
-
-### Error Response
-
-1. Si l'argument type est inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "Type undifined is not defined"
-}
-```
-
-2. Si la famille avec l'identifiant passé en paramètre est introuvable
-
-**Code:** 404 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 404: Dataset family with id 15 is not found"
-}
-```
-
-3. Si le paramètre label est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param label needed to add a new family"
-}
-```
-
-4. Si le paramètre description est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param description needed to add a new family"
-}
-```
-
-5. Si le paramètre display est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param display needed to add a new family"
-}
-```
-
-### Exemples d'utilisation
-
-1. Récuperer la famille de criteria qui correspond à l'identifiant passé en paramètre
-
-> curl -X GET http://localhost:8989/metadata/family/criteria/5
-
-2. Modifier la famille de criteria qui correspond à l'identifiant passé en paramètre
-
-> curl -d '{"label":"My criteria family"}' -H "Content-Type: application/json" -X PUT http://localhost:8989/metadata/family/criteria/5
-
-3. Supprimer la famille de criteria qui correspond à l'identifiant passé en paramètre
-
-> curl -X DELETE http://localhost:8989/metadata/family/criteria/5
diff --git a/documentation/api/file-list.md b/documentation/api/file-list.md
deleted file mode 100644
index 47e6c43..0000000
--- a/documentation/api/file-list.md
+++ /dev/null
@@ -1,168 +0,0 @@
-# Metadata: File collection
-----
-1. Permet de récupérer l'ensemble des fichiers disponibles pour un dataset dans la base de données metadata (`GET`)
-2. Permet également de créer une nouveau fichier pour un dataset (`POST`)
-
-### URL
-
-- /metadata/dataset/:name/file
-
-### Method
-
-- `GET`
-- `POST`
-  
-### Headers
-
-- Content-type: application/json
-
-### URL Params
-
-| Nom du paramètre | Type    | Description                             |
-| :--------------- | :------ | :-------------------------              |
-| name             | string  | Identifiant du dataset                  |
-
-### Data Params
-
-1. Pour une requête GET
-
-None
-
-2. Pour une requête POST
-
-| Nom du paramètre | Type   | Description                                                     | Obligatoire  |
-| :--------------- | :----- | :-------------------------------------------------------------- | :----------: |
-| label            | string | Permet de définir un label pour le fichier                      | [x]          |
-| file_loc         | string | Chemin vers le fichier                                          | [x]          |
-| type             | string | Type de fichier référencé (txt, csv, fits...)                   | [x]          |
-| display          | int    | Numéro d'ordre d'apparation du fichier                          | [x]          |
-| visible          | bool   | Permet d'afficher ou non le fichier dans l'inteface utilisateur | [x]          |
-
-### Success Response
-
-1. `GET` 
-
-**Code:** 200 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-	"data": [{
-        "id": 1,
-        "label": "My file",
-        "file_loc": "my_file.txt",
-        "type": "txt",
-        "display": 10,
-        "visible": true
-	}, {
-		"id": 2,
-        "label": "My fits",
-        "file_loc": "my_fits.fits",
-        "type": "fits",
-        "display": 20,
-        "visible": true
-	}]
-}
-```
- 
-2. `POST` 
-
-**Code:** 200 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-	"data": {
-		"id": 3,
-        "label": "My new fits",
-        "file_loc": "my_new_fits.fits",
-        "type": "fits",
-        "display": 30,
-        "visible": true
-	}
-}
-```
-
-### Error Response
-
-1. Si l'identifiant du dataset est inconnu (name)
-
-**Code:** 404 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 404: Dayasey with name undifined is not found"
-}
-```
-
-2. Si le paramètre label est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param label needed to add a new file"
-}
-```
-
-3. Si le paramètre file_loc est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param file_loc needed to add a new file"
-}
-```
-
-4. Si le paramètre type est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param type needed to add a new file"
-}
-```
-
-5. Si le paramètre display est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param display needed to add a new file"
-}
-```
-
-6. Si le paramètre visible est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param visible needed to add a new file"
-}
-```
-
-### Exemples d'utilisation
-
-1. Récuperer l'ensemble des fichiers d'un dataset
-
-> curl -X GET http://localhost:8989/metadata/dataset/corot_targets/file
-
-2. Ajouter une nouveau fichier au dataset corot_targets
-
-> curl -d '{"label":"my_new_fits","file_loc":"my_new_fits.fits","type":"fits","display":30,"visible":true}' -H "Content-Type: application/json" -X POST http://localhost:8989/metadata/dataset/corot_targets/file
diff --git a/documentation/api/file.md b/documentation/api/file.md
deleted file mode 100644
index 0585aae..0000000
--- a/documentation/api/file.md
+++ /dev/null
@@ -1,184 +0,0 @@
-# Metadata: File item
-----
-1. Permet de récupérer le fichier qui correspond à l'identifiant passé dans l'URL (`GET`)
-2. Permet de modifier intégralement le fichier qui correspond à l'identifiant passé dans l'URL (`PUT`)
-3. Permet de supprimer le fichier qui correspond à l'identifiant passé dans l'URL (`DELETE`)
-
-### URL
-
-- /metadata/file/:id
-
-### Method
-
-- `GET`
-- `PUT`
-- `DELETE`
-  
-### Headers
-
-- Content-type: application/json
-
-### URL Params
-
-| Nom du paramètre | Type | Description                |
-| :--------------- | :--- | :--------------------------|
-| id               | int  | Identifiant du fichier     |
-
-### Data Params
-
-1. Pour une requête GET
-
-None
-
-2. Pour une requête PUT
-
-| Nom du paramètre | Type   | Description                                                     | Obligatoire  |
-| :--------------- | :----- | :-------------------------------------------------------------- | :----------: |
-| label            | string | Permet de définir un label pour le fichier                      | [x]          |
-| file_loc         | string | Chemin vers le fichier                                          | [x]          |
-| type             | string | Type de fichier référencé (txt, csv, fits...)                   | [x]          |
-| display          | int    | Numéro d'ordre d'apparation du fichier                          | [x]          |
-| visible          | bool   | Permet d'afficher ou non le fichier dans l'inteface utilisateur | [x]          |
-
-3. Pour une requête DELETE
-
-None
-
-### Success Response
-
-1. `GET` 
-
-**Code:** 200 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-	"data": {
-        "id": 1,
-        "label": "My file",
-        "file_loc": "my_file.txt",
-        "type": "txt",
-        "display": 10,
-        "visible": true,
-        "dataset_name": "corot_targets"
-	}
-}
-```
- 
-2. `PUT` 
-
-**Code:** 200 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-	"data": {
-        "id": 1,
-        "label": "My edited file",
-        "file_loc": "my_edited_file.txt",
-        "type": "txt",
-        "display": 20,
-        "visible": false,
-        "dataset_name": "corot_targets"
-	}
-}
-```
-
-3. `DELETE`
-
-**Code:** 200 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-	"message": "File with id 1 is removed!"
-}
-```
-
-### Error Response
-
-1. Si le fichier avec l'id passé en paramètre est inconnu
-
-**Code:** 404 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 404: File with id 15 is not found"
-}
-```
-
-2. Si le paramètre label est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param label needed to edit the file"
-}
-```
-
-3. Si le paramètre file_loc est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param file_loc needed to edit the file"
-}
-```
-
-4. Si le paramètre type est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param type needed to edit the file"
-}
-```
-
-5. Si le paramètre display est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param display needed to edit the file"
-}
-```
-
-6. Si le paramètre visible est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param visible needed to edit the file"
-}
-```
-
-### Exemples d'utilisation
-
-1. Récuperer le fichier qui correspond à l'identifiant passé en paramètre
-
-> curl -X GET http://localhost:8989/metadata/file/16
-
-2. Modifier le fichier qui correspond à l'identifiant passé en paramètre
-
-> curl -d '{"label":"Test_modif","file_loc":"my_edited_file","type":"txt","display":"20","visible":true}' -H "Content-Type: application/json" -X POST http://localhost:8989/metadata/file/16
-
-3. Supprimer la database qui correspond à l'identifiant passé en paramètre
-
-> curl -X DELETE http://localhost:8989/metadata/file/16
diff --git a/documentation/api/group-list.md b/documentation/api/group-list.md
deleted file mode 100644
index 0852d0b..0000000
--- a/documentation/api/group-list.md
+++ /dev/null
@@ -1,89 +0,0 @@
-# Metadata: Group collection
-----
-1. Permet de récupérer l'ensemble des groupes d'utilisateur disponibles dans la base de données metadata (`GET`)
-2. Permet également de créer un nouveau groupe (`POST`)
-
-### URL
-
-- /metadata/group
-
-### Method
-
-- `OPTIONS`
-- `GET`
-- `POST`
-  
-### Headers
-
-- Content-type: application/json
-
-### URL Params
-
-None
-
-### Data Params
-
-1. Pour une requête GET
-
-None
-
-2. Pour une requête POST
-
-| Nom du paramètre | Type   | Description                                       | Obligatoire  |
-| :--------------- | :----- | :------------------------------------------------ | :----------: |
-| label            | string | Le label du groupe à créer                        | [x]          |
-
-### Success Response
-
-1. `GET` 
-
-**Code:** 200 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-	"data": [{
-		"id": 1,
-		"label": "Default"
-	}, {
-		"id": 2,
-		"label": "Admin"
-	}]
-}
-```
- 
-2. `POST` 
-
-**Code:** 201 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-	"id": 3,
-	"label": "Student"
-}
-```
-
-### Error Response
-
-1. Si le paramètre label est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param label needed to add a new group"
-}
-```
-
-### Exemples d'utilisation
-
-1. Récuperer l'ensemble des groupes dans la base de metadata
-
-> curl -X GET http://localhost:8989/metadata/group
-
-2. Ajouter une nouveau groupe metadata
-
-> curl -d '{"label":"New group"}' -H "Content-Type: application/json" -X POST http://localhost:8989/metadata/group
diff --git a/documentation/api/group.md b/documentation/api/group.md
deleted file mode 100644
index 27cb924..0000000
--- a/documentation/api/group.md
+++ /dev/null
@@ -1,122 +0,0 @@
-# Metadata: Group item
-----
-1. Permet de récupérer le groupe qui correspond à l'identifiant passé dans l'URL (`GET`)
-2. Permet de modifier intégralement le groupe qui correspond à l'identifiant passé dans l'URL (`PUT`)
-3. Permet de supprimer le groupe qui correspond à l'identifiant passé dans l'URL (`DELETE`)
-
-### URL
-
-- /metadata/group/:id
-
-### Method
-
-- `GET`
-- `PUT`
-- `DELETE`
-  
-### Headers
-
-- Content-type: application/json
-
-### URL Params
-
-| Nom du paramètre | Type | Description                |
-| :--------------- | :--- | :--------------------------|
-| id               | int  | Identifiant du groupe      |
-
-### Data Params
-
-1. Pour une requête GET
-
-None
-
-2. Pour une requête PUT
-
-| Nom du paramètre | Type   | Description                                                     | Obligatoire  |
-| :--------------- | :----- | :-------------------------------------------------------------- | :----------: |
-| label            | string | Permet de définir un label pour le groupe                       | [x]          |
-
-3. Pour une requête DELETE
-
-None
-
-### Success Response
-
-1. `GET` 
-
-**Code:** 200 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-	"data": {
-        "id": 1,
-        "label": "Default"
-	}
-}
-```
- 
-2. `PUT` 
-
-**Code:** 200 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-	"data": {
-        "id": 1,
-        "label": "My edited group"
-	}
-}
-```
-
-3. `DELETE`
-
-**Code:** 200 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-	"message": "Group with id 1 is removed!"
-}
-```
-
-### Error Response
-
-1. Si le groupe avec l'id passé en paramètre est inconnu
-
-**Code:** 404 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 404: Groupe with id 15 is not found"
-}
-```
-
-2. Si le paramètre label est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param label needed to edit the group"
-}
-```
-
-### Exemples d'utilisation
-
-1. Récuperer le groupe qui correspond à l'identifiant passé en paramètre
-
-> curl -X GET http://localhost:8989/metadata/group/16
-
-2. Modifier le groupe qui correspond à l'identifiant passé en paramètre
-
-> curl -d '{"label":"Test_modif"}' -H "Content-Type: application/json" -X POST http://localhost:8989/metadata/group/16
-
-3. Supprimer le groupe qui correspond à l'identifiant passé en paramètre
-
-> curl -X DELETE http://localhost:8989/metadata/group/16
diff --git a/documentation/api/login.md b/documentation/api/login.md
deleted file mode 100644
index 35883f2..0000000
--- a/documentation/api/login.md
+++ /dev/null
@@ -1,109 +0,0 @@
-# Récupérer un jeton d'authentification
-----
-1. Permet à l'utilisateur de récupérer un jeton d'authentification avec son email et son password
-
-### URL
-
-- /login
-
-### Method
-
-- `OPTIONS`
-- `POST`
-  
-### Headers
-
-- Content-type: application/json
-
-### URL Params
-
-None
-
-### Data Params
-
-| Nom du paramètre | Type   | Description                                                | Obligatoire  |
-| :--------------- | :----- | :--------------------------------------------------------- | :----------: |
-| email            | string | Adresse email de l'utilisateur enregistré                  | [x]          |
-| password         | string | Mot de passe de l'utilisateur                              | [x]          |
-
-### Success Response
-
-**Code:** 200 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-	"data": {
-		"email": "admin@anis.fr",
-		"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC9hbmlzLmxhbS5mciIsImF1ZCI6Imh0dHA6XC9cL2FuaXMubGFtLmZyIiwiaWF0IjoxNTMwODAyMTk4LCJuYmYiOjE1MzA4MDIxOTgsImV4cCI6MTUzMDgzODE5OCwiZW1haWwiOiJhZG1pbkBhbmlzLmZyIn0.WM4gvKY1HwmLBPRtwa87yptpipCkc8l3xqSiMxGJ_Ig"
-	}
-}
-```
-
-### Error Response
-
-1. Si le paramètre email est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param email needed to login"
-}
-```
-
-2. Si le paramètre password est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param password needed to login"
-}
-```
-
-3. Si l'utilisateur identifié par l'email envoyé dans la requête n'éxiste pas dans la base de données
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-    "error": "Invalid user",
-    "error_description": "HTTP 400: No account is identified with this email address"
-}
-```
-
-4. Si le compte utilisateur n'est pas encore activé
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-    "error": "Invalid user",
-    "error_description": "HTTP 400: Account not yet activated"
-}
-```
-
-5. Si l'utilisateur a renseigné un mauvais mot de passe
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-    "error": "Invalid user",
-    "error_description": "HTTP 400: Bad password; unable to login"
-}
-```
-
-### Exemple d'utilisation
-
-1. Récupérer un jeton d'authentification 
-
-> curl -d '{"email":"admin@anis.fr","password":"admin"}' -H "Content-Type: application/json" -X POST http://localhost:8989/login
diff --git a/documentation/api/new-password.md b/documentation/api/new-password.md
deleted file mode 100644
index 5dc13f2..0000000
--- a/documentation/api/new-password.md
+++ /dev/null
@@ -1,81 +0,0 @@
-# Demander la génération d'un nouveau mot de passe
-----
-1. Permet de demander la génération d'un nouveau mot de passe qui sera envoyé par email à l'utilisateur (`POST`)
-
-### URL
-
-- /new-password
-
-### Method
-
-- `OPTIONS`
-- `POST`
-  
-### Headers
-
-- Content-type: application/json
-
-### URL Params
-
-None
-
-### Data Params
-
-| Nom du paramètre | Type   | Description                         | Obligatoire  |
-| :--------------- | :----- | :---------------------------------- | :----------: |
-| email            | string | Adresse email de l'utilisateur      | [x]          |
-
-### Success Response
-
-**Code:** 200 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-	"message": "Password re-generated!"
-}
-```
-
-### Error Response
-
-1. Si le paramètre email est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param email needed to generate a new password"
-}
-```
-
-2. Si l'utilisateur identifié par l'email envoyé dans la requête n'éxiste pas dans la base de données
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-    "error": "Invalid user",
-    "error_description": "HTTP 400: No account is identified with this email address"
-}
-```
-
-3. Si le compte utilisateur n'est pas encore activé
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-    "error": "Invalid user",
-    "error_description": "HTTP 400: Account not yet activated"
-}
-```
-
-### Exemple d'utilisation
-
-1. Demander la génération d'un nouveau mot de passe
-
-> curl -d '{"email":"admin@anis.fr"}' -H "Content-Type: application/json" -X POST http://localhost:8989/new-password
diff --git a/documentation/api/project-list.md b/documentation/api/project-list.md
deleted file mode 100644
index de1b1d7..0000000
--- a/documentation/api/project-list.md
+++ /dev/null
@@ -1,179 +0,0 @@
-# Metadata: Project collection
-----
-1. Permet de récupérer l'ensemble des projets disponibles dans la base de données metadata (`GET`)
-2. Permet également de créer une nouveau projet (`POST`)
-
-### URL
-
-- /metadata/project
-
-### Method
-
-- `GET`
-- `POST`
-  
-### Headers
-
-- Content-type: application/json
-
-### URL Params
-
-None
-
-### Data Params
-
-1. Pour une requête GET
-
-None
-
-2. Pour une requête POST
-
-| Nom du paramètre | Type   | Description                                                | Obligatoire  |
-| :--------------- | :----- | :--------------------------------------------------------- | :----------: |
-| name             | string | Identifiant du projet à créé                               | [x]          |
-| label            | string | Permet de définir un label pour le projet                   | [x]          |
-| description      | text   | Description du projet                                      | [x]          |
-| link             | string | Lien vers le site du projet                                | [x]          |
-| manager          | string | Nom du scientifique qui dirige le projet                   | [x]          |
-| id_database      | int    | Identifiant de la database contenant les données du projet | [x]          |
-
-### Success Response
-
-1. `GET` 
-
-**Code:** 200 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-	"data": [{
-		"name": "exodat",
-		"label": "ExoDat",
-		"description": "The ExoDat Information System is a search engine for exploring and displaying data from the CoRoT\/Exoplanet channel database.",
-		"link": "https:\/\/corot.cnes.fr\/fr",
-		"manager": "M.Deleuil",
-		"id_database": 16
-	}, {
-		"name": "aspic",
-		"label": "Aspic",
-		"description": "Aspic project",
-		"link": "http:\/\/cesam.lam.fr\/aspic",
-		"manager": "C. Adami",
-		"id_database": 18
-	}]
-}
-```
- 
-2. `POST` 
-
-**Code:** 200 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-	"data": {
-		"name": "my_project",
-		"label": "Mon super projet",
-		"description": "Une description compl\u00e8te du projet",
-		"link": "http:\/\/monprojet.com",
-		"manager": "M. Durand",
-		"id_database": 19
-	}
-}
-```
-
-### Error Response
-
-1. Si l'identifiant de la database à associer au projet est inconnu (id_database) (`POST`)
-
-**Code:** 404 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 404: Database with id 15 is not found"
-}
-```
-
-2. Si le paramètre name est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param name needed to add a new project"
-}
-```
-
-3. Si le paramètre label est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param label needed to add a new project"
-}
-```
-
-4. Si le paramètre description est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param description needed to add a new project"
-}
-```
-
-5. Si le paramètre link est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param link needed to add a new project"
-}
-```
-
-6. Si le paramètre manager est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param manager needed to add a new project"
-}
-```
-
-7. Si le paramètre id_database est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param id_database needed to add a new project"
-}
-```
-
-### Exemples d'utilisation
-
-1. Récuperer l'ensemble des projets
-
-> curl -X GET http://localhost:8989/metadata/project
-
-2. Ajouter une nouveau projet
-
-> curl -d '{"name":"my_project","label":"Mon super projet","description":"Une description complète du projet","link":"http://monprojet.com","manager":"M. Durand","id_database":19}' -H "Content-Type: application/json" -X POST http://localhost:8989/metadata/project
diff --git a/documentation/api/project.md b/documentation/api/project.md
deleted file mode 100644
index fed052f..0000000
--- a/documentation/api/project.md
+++ /dev/null
@@ -1,182 +0,0 @@
-# Metadata: Project item
-----
-1. Permet de récupérer le projet qui correspond à l'identifiant passé dans l'URL (`GET`)
-2. Permet de modifier intégralement le projet qui correspond à l'identifiant passé dans l'URL (`PUT`)
-3. Permet de supprimer la projet qui correspond à l'identifiant passé dans l'URL (`DELETE`)
-
-### URL
-
-- /metadata/project/:name
-
-### Method
-
-- `GET`
-- `PUT`
-- `DELETE`
-  
-### Headers
-
-- Content-type: application/json
-
-### URL Params
-
-| Nom du paramètre | Type    | Description                |
-| :--------------- | :------ | :--------------------------|
-| name             | string  | Nom du projet              |
-
-### Data Params
-
-1. Pour une requête GET
-
-None
-
-2. Pour une requête PUT
-
-| Nom du paramètre | Type   | Description                                                | Obligatoire  |
-| :--------------- | :----- | :--------------------------------------------------------- | :----------: |
-| label            | string | Permet de définir un label pour le pojet                   | [x]          |
-| description      | text   | Description du projet                                      | [x]          |
-| link             | string | Lien vers le site du projet                                | [x]          |
-| manager          | string | Nom du scientifique qui dirige le projet                   | [x]          |
-| id_database      | int    | Identifiant de la database contenant les données du projet | [x]          |
-
-3. Pour une requête DELETE
-
-None
-
-### Success Response
-
-1. `GET` 
-
-**Code:** 200 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-	"data": {
-		"name": "my_project",
-		"label": "Mon super projet",
-		"description": "Une description compl\u00e8te du projet",
-		"link": "http:\/\/monprojet.com",
-		"manager": "M. Durand",
-		"id_database": 19
-	}
-}
-```
- 
-2. `PUT` 
-
-**Code:** 200 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-	"data": {
-		"name": "my_project",
-		"label": "Mon projet tip top",
-		"description": "Une description complete du projet",
-		"link": "http:\/\/monprojet.com",
-		"manager": "M. Durand",
-		"id_database": 19
-	}
-}
-```
-
-3. `DELETE`
-
-**Code:** 200 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-	"message": "Project vuds is removed!"
-}
-```
-
-### Error Response
-
-1. Si l'identifiant de la database à associer au projet est inconnu (id_database) (`POST`)
-
-**Code:** 404 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 404: Database with id 15 is not found"
-}
-```
-
-2. Si le paramètre label est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param label needed to add a new project"
-}
-```
-
-3. Si le paramètre description est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param description needed to add a new project"
-}
-```
-
-4. Si le paramètre link est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param link needed to add a new project"
-}
-```
-
-5. Si le paramètre manager est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param manager needed to add a new project"
-}
-```
-
-6. Si le paramètre id_database est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param id_database needed to add a new project"
-}
-```
-
-### Exemples d'utilisation
-
-1. Récuperer le projet qui correspond à l'identifiant passé en paramètre
-
-> curl -X GET http://localhost:8989/metadata/project/my_project
-
-2. Modifier le projet qui correspond à l'identifiant passé en paramètre
-
-> curl -d '{"label":"Mon projet tip top","description":"Une description complete du projet","link":"http://monprojet.com","manager":"M. Durand","id_database":19}' -H "Content-Type: application/json" -X PUT http://localhost:8989/metadata/project/my_project
-
-3. Supprimer le projet qui correspond à l'identifiant passé en paramètre
-
-> curl -X DELETE http://localhost:8989/metadata/project/my_project
diff --git a/documentation/api/register.md b/documentation/api/register.md
deleted file mode 100644
index d6204ff..0000000
--- a/documentation/api/register.md
+++ /dev/null
@@ -1,100 +0,0 @@
-# Enregistrer un nouvel utilisateur
-----
-1. Permet de créer un nouvel utilisateur ANIS (`GET`)
-
-### URL
-
-- /register
-
-### Method
-
-- `OPTIONS`
-- `POST`
-  
-### Headers
-
-- Content-type: application/json
-
-### URL Params
-
-None
-
-### Data Params
-
-| Nom du paramètre | Type   | Description                                                | Obligatoire  |
-| :--------------- | :----- | :--------------------------------------------------------- | :----------: |
-| email            | string | Adresse email du nouvel utilisateur                        | [x]          |
-| password         | string | Mot de passe du nouvel utilisateur                         | [x]          |
-
-### Success Response
-
-**Code:** 200 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-	"data": {
-		"email": "test@anis.fr",
-		"activated": false,
-		"adminsi": false,
-		"superuser": false,
-		"id_group": 1
-	}
-}
-```
-
-### Error Response
-
-1. Si le paramètre email est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param email needed to register a new user"
-}
-```
-
-2. Si le paramètre password est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param password needed to register a new user"
-}
-```
-
-3. Si un utilisateur avec la même adresse email éxiste déjà dans la base de données
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-    "error": "Invalid user",
-    "error_description": "HTTP 400: A user with the email address user1@anis.fr already exists"
-}
-```
-
-4. Si l'adresse email envoyé par l'utilisateur n'est pas valide
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid email",
-    "error_description": "HTTP 400: Bad email adress; a well-defined email address is needed to finalize the registration"
-}
-```
-
-### Exemple d'utilisation
-
-1. Enregistrer un nouvel utilisateur 
-
-> curl -d '{"email":"user1@anis.fr","password":"test"}' -H "Content-Type: application/json" -X POST http://localhost:8989/register
diff --git a/documentation/api/root.md b/documentation/api/root.md
deleted file mode 100644
index e2a9396..0000000
--- a/documentation/api/root.md
+++ /dev/null
@@ -1,44 +0,0 @@
-# Root API
-
-1. Retourne la chaîne "It works!" qui permet de s'assurer que le service fonctionne. (`GET`)
-
-## URL
-
-- /
-
-## Method
-
-- `GET`
-  
-## Headers
-
-None
-
-## URL Params
-
-None
-
-### Data Params
-
-1. Pour une requête GET
-
-None
-
-## Success Response
-
-1. `GET` 
-
-**Code:** 200  
-**Content-type:** application/json  
-**Response:**
-```json
-{
-	"message": "it's works!"
-}
-```
-
-## Example of use
-
-1. Tester le fonctionnement du service
-
-> curl -X GET http://localhost:8989/
diff --git a/documentation/api/tables-available.md b/documentation/api/tables-available.md
deleted file mode 100644
index 121b43d..0000000
--- a/documentation/api/tables-available.md
+++ /dev/null
@@ -1,60 +0,0 @@
-# Metadata: Tables collection
-----
-1. Permet de récupérer l'ensemble des tables et vues disponibles dans la database qui correspond à l'identifiant passé dans l'URL (`GET`)
-
-### URL
-
-- /metadata/database/:id/table
-
-### Method
-
-- `GET`
-  
-### Headers
-
-None
-
-### URL Params
-
-| Nom du paramètre | Type | Description                |
-| :--------------- | :--- | :--------------------------|
-| id               | int  | Identifiant de la database |
-
-### Data Params
-
-None
-
-### Success Response
-
-1. `GET` 
-
-**Code:** 200 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-[
-	"a_t_hub",
-	"a_t_hub_usnoa2_checked",
-	"cfht_cat"
-]
-```
-
-### Error Response
-
-1. Si la database avec l'id passé en paramètre est inconnu
-
-**Code:** 404 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 404: Database with id 15 is not found"
-}
-```
-
-### Exemples d'utilisation
-
-1. Récuperer l'ensemble des tables et vues disponibles dans la database qui correspond à l'identifiant passé dans l'URL
-
-> curl -X GET http://localhost:8989/metadata/database/16/table
diff --git a/documentation/api/user-list.md b/documentation/api/user-list.md
deleted file mode 100644
index 4e524ed..0000000
--- a/documentation/api/user-list.md
+++ /dev/null
@@ -1,150 +0,0 @@
-# Metadata: User collection
-----
-1. Permet de récupérer l'ensemble des utilisateurs disponibles dans la base de données metadata (`GET`)
-2. Permet également de créer un nouvel utilisateur (`POST`)
-
-### URL
-
-- /metadata/user
-
-### Method
-
-- `GET`
-- `POST`
-  
-### Headers
-
-- Content-type: application/json
-
-### URL Params
-
-None
-
-### Data Params
-
-1. Pour une requête GET
-
-None
-
-2. Pour une requête POST
-
-| Nom du paramètre | Type   | Description                                                | Obligatoire  |
-| :--------------- | :----- | :--------------------------------------------------------- | :----------: |
-| email            | string | Adresse email du nouvel utilisateur                        | [x]          |
-| adminsi          | bool   | Permet de définir l'utilisateur comme un admin du SI       | [x]          |
-| superuser        | bool   | Permet de définir l'utilisateur comme un super utilisateur | [x]          |
-| id_group         | int    | Identifiant du groupe                                      | [x]          |
-
-### Success Response
-
-1. `GET` 
-
-**Code:** 200 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-	"data": [{
-		"email": "user1@anis.fr",
-		"activated": false,
-		"adminsi": false,
-		"superuser": false,
-		"id_group": 1
-	}, {
-		"email": "user2@anis.fr",
-		"activated": true,
-		"adminsi": false,
-		"superuser": false,
-		"id_group": 1
-	}]
-}
-```
- 
-2. `POST` 
-
-**Code:** 200 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-	"data": [{
-		"email": "newuser@anis.fr",
-		"activated": false,
-		"adminsi": false,
-		"superuser": false,
-		"id_group": 1
-	}]
-}
-```
-
-### Error Response
-
-1. Si l'identifiant du groupe à associer à l'utilisateur est inconnu (id_group) (`POST`)
-
-**Code:** 404 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 404: Group with id 15 is not found"
-}
-```
-
-2. Si le paramètre email est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param email needed to add a new user"
-}
-```
-
-3. Si le paramètre adminsi est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param adminsi needed to add a new user"
-}
-```
-
-4. Si le paramètre superuser est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param superuser needed to add a new user"
-}
-```
-
-5. Si le paramètre id_group est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param id_group needed to add a new user"
-}
-```
-
-### Exemples d'utilisation
-
-1. Récuperer l'ensemble des projets
-
-> curl -X GET http://localhost:8989/metadata/user
-
-2. Ajouter un nouvel utilisateur
-
-> curl -d '{"email":"newuser@anis.fr","adminsi":true,"superuser":true,"id_group":1}' -H "Content-Type: application/json" -X POST http://localhost:8989/metadata/user
diff --git a/documentation/api/user.md b/documentation/api/user.md
deleted file mode 100644
index 023c65c..0000000
--- a/documentation/api/user.md
+++ /dev/null
@@ -1,167 +0,0 @@
-# Metadata: User item
-----
-1. Permet de récupérer l'utilisateur qui correspond à l'identifiant passé dans l'URL (`GET`)
-2. Permet de modifier intégralement l'utilisateur qui correspond à l'identifiant passé dans l'URL (`PUT`)
-3. Permet de supprimer l'utilisateur qui correspond à l'identifiant passé dans l'URL (`DELETE`)
-
-### URL
-
-- /metadata/user/:email
-
-### Method
-
-- `GET`
-- `PUT`
-- `DELETE`
-  
-### Headers
-
-- Content-type: application/json
-
-### URL Params
-
-| Nom du paramètre | Type    | Description                    |
-| :--------------- | :------ | :----------------------------- |
-| email            | string  | Adresse email de l'utilisateur |
-
-### Data Params
-
-1. Pour une requête GET
-
-None
-
-2. Pour une requête PUT
-
-| Nom du paramètre | Type   | Description                                                | Obligatoire  |
-| :--------------- | :----- | :--------------------------------------------------------- | :----------: |
-| adminsi          | bool   | Permet de définir l'utilisateur comme un admin du SI       | [x]          |
-| superuser        | bool   | Permet de définir l'utilisateur comme un super utilisateur | [x]          |
-| activated        | bool   | Active ou désactive un utilisateur                         | [x]          |
-| id_group         | int    | Identifiant du groupe                                      | [x]          |
-
-3. Pour une requête DELETE
-
-None
-
-### Success Response
-
-1. `GET` 
-
-**Code:** 200 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-	"data": {
-		"email": "user1@anis.fr",
-		"activated": false,
-		"adminsi": false,
-		"superuser": false,
-		"id_group": 1
-	}
-}
-```
- 
-2. `PUT` 
-
-**Code:** 200 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-	"data": {
-		"email": "user1@anis.fr",
-		"activated": true,
-		"adminsi": false,
-		"superuser": false,
-		"id_group": 2
-	}
-}
-```
-
-3. `DELETE`
-
-**Code:** 200 <br />
-**Content-type:** application/json <br />
-**Exemple de retour:** <br />
-```json
-{
-	"message": "User withe mail user1@anis.fr is removed!"
-}
-```
-
-### Error Response
-
-1. Si l'identifiant du groupe à associer à l'utilisateur est inconnu (id_group) (`POST`)
-
-**Code:** 404 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 404: Group with id 15 is not found"
-}
-```
-
-2. Si le paramètre adminsi est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param adminsi needed to edit the user"
-}
-```
-
-3. Si le paramètre superuser est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param superuser needed to edit the user"
-}
-```
-
-4. Si le paramètre activated est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param activated needed to edit the user"
-}
-```
-
-5. Si le paramètre id_group est vide ou inconnu
-
-**Code:** 400 <br />
-**Content-type:** application/json <br />
-**Retour:** <br />
-```json
-{
-    "error": "Invalid request",
-    "error_description": "HTTP 400: Param id_group needed to edit the user"
-}
-```
-
-### Exemples d'utilisation
-
-1. Récuperer l'utilisateur qui correspond à l'identifiant passé en paramètre
-
-> curl -X GET http://localhost:8989/metadata/user/user1@anis.fr
-
-2. Modifier le projet qui correspond à l'identifiant passé en paramètre
-
-> curl -d '{"adminsi":false,"superuser":false,"activated":true,"id_group":1}' -H "Content-Type: application/json" -X PUT http://localhost:8989/metadata/user/user1@anis.fr
-
-3. Supprimer le projet qui correspond à l'identifiant passé en paramètre
-
-> curl -X DELETE http://localhost:8989/metadata/user/user1@anis.fr
diff --git a/documentation/mcd/anis_v3_mcd.png b/documentation/mcd/anis_v3_mcd.png
deleted file mode 100644
index e79b21a8c25e69ffabd22583adb62ae9dd9a483e..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 93829
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

diff --git a/documentation/mcd/anis_v3_mcd.txt b/documentation/mcd/anis_v3_mcd.txt
deleted file mode 100644
index 12b1c28..0000000
--- a/documentation/mcd/anis_v3_mcd.txt
+++ /dev/null
@@ -1,27 +0,0 @@
-http://www.mocodo.net/
-
-project: name, label, description, link, manager
-DF, 0N database, 11 project
-database: id, label, dbname, type, host, port, login, password
-
-DF1, 0N project, 11 dataset
-DF2, ON dataset_family, 11 dataset
-dataset_family: id, label, description, display
-output_family: id, label, display
-DF8, 11 output_category, 0N output_family
-
-file: id, label, file_loc, type, display, visible
-DF5, ON dataset, 11 file
-dataset: name, table_ref, label, description, display, count, vo, data_path
-DF4, 0N dataset, 11 attribute
-attribute: id, name, table_name, label, form_label, description, output_display, criteria_display, search_flag, search_type, operator, type, min, max, placeholder_min, placeholder_max, uri_action, renderer, display_detail, selected, order_by, order_display, detail, renderer_detail, options, vo_utype, vo_ucd, vo_unit, vo_description, vo_datatype, vo_size
-DF9, 0N output_category, 01 attribute
-output_category: id, label, display
-
-dataset_privileges, 0N anis_group, 0N dataset: visible
-anis_group: id, label
-criteria_family: id, label, display
-DF3, 0N criteria_family, 01 attribute
-
-DF7, 0N anis_group, 11 anis_user
-anis_user: email, password, activation_key, activated, adminsi, superuser
\ No newline at end of file
diff --git a/documentation/mcd/anis_v3_settings_mcd.png b/documentation/mcd/anis_v3_settings_mcd.png
deleted file mode 100644
index 48eb22c8b53d8067c179cd735966e1e0239e8e2d..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 7201
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
W`;b78ZvBtULs6DjldF(14*DPNWb8=*

diff --git a/documentation/mcd/anis_v3_settings_mcd.txt b/documentation/mcd/anis_v3_settings_mcd.txt
deleted file mode 100644
index e7e27c0..0000000
--- a/documentation/mcd/anis_v3_settings_mcd.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-http://www.mocodo.net/
-
-settings_select: id, attribute_name, label
-DF1, 0N settings_select, 11 settings_option
-settings_option: id, label, value, display
diff --git a/public/index.php b/public/index.php
index aa0f403..4e3f077 100644
--- a/public/index.php
+++ b/public/index.php
@@ -1,40 +1,59 @@
-<?php declare(strict_types=1);
+<?php
+
 /*
- * This file is part of ANIS SERVER API.
+ * This file is part of Anis Server.
  *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
  */
-use \Psr\Http\Message\ServerRequestInterface as Request;
-use \Psr\Http\Message\ResponseInterface as Response;
-
-if (PHP_SAPI == 'cli-server') {
-    // To help the built-in PHP dev server, check if the request was actually for
-    // something which should probably be served as a static file
-    $url  = parse_url($_SERVER['REQUEST_URI']);
-    $file = __DIR__ . $url['path'];
-    if (is_file($file)) {
-        return false;
-    }
-}
+declare(strict_types=1);
+
+use DI\Container;
+use Slim\Factory\AppFactory;
+use App\Handlers\LogErrorHandler;
 
+// Autoloading for libraries
 require __DIR__ . '/../vendor/autoload.php';
 
-// Instantiate the app
-$settings = require __DIR__ . '/../src/settings.php';
-$app = new \Slim\App($settings);
+// Load app constants
+require __DIR__ . '/../app/constants.php';
+
+// Create Container using PHP-DI
+$container = new Container();
+
+// Setup dependencies
+require __DIR__ . '/../app/dependencies.php';
+
+// Set container to create App with on AppFactory
+AppFactory::setContainer($container);
+$app = AppFactory::create();
+
+$ded = $container->get(SETTINGS)['displayErrorDetails'];
+if (is_string($ded)) {
+    $displayErrorDetails = $ded === 'true';
+} else {
+    $displayErrorDetails = $ded;
+}
+
+$errorHandler = new LogErrorHandler($app->getCallableResolver(), $app->getResponseFactory());
+$errorHandler->forceContentType('application/json');
+$errorHandler->setLogger($container->get('logger'));
 
-// Set up dependencies
-require __DIR__ . '/../src/dependencies.php';
+// Add Error Handling Middleware (JSON only)
+$errorMiddleware = $app->addErrorMiddleware(
+    $displayErrorDetails,
+    true,
+    true
+);
+$errorMiddleware->setDefaultErrorHandler($errorHandler);
 
-// Register middleware
-require __DIR__ . '/../src/middleware.php';
+// Register middlewares
+require __DIR__ . '/../app/middlewares.php';
 
 // Register routes
-require __DIR__ . '/../src/routes.php';
+require __DIR__ . '/../app/routes.php';
 
 // Run app
 $app->run();
diff --git a/src/Action/AbstractAction.php b/src/Action/AbstractAction.php
new file mode 100644
index 0000000..727da93
--- /dev/null
+++ b/src/Action/AbstractAction.php
@@ -0,0 +1,52 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Action;
+
+use Doctrine\ORM\EntityManagerInterface;
+
+/**
+ * Centralize access to the database and some common functions
+ *
+ * @author François Agneray <francois.agneray@lam.fr>
+ * @package App\Action
+ */
+abstract class AbstractAction
+{
+    /**
+     * The EntityManager is the central access point to Doctrine ORM functionality
+     *
+     * @var EntityManagerInterface
+     */
+    protected $em;
+
+    /**
+     * Create the classe before call __invoke to execute the action
+     *
+     * @param EntityManagerInterface $em Doctrine Entity Manager Interface
+     */
+    public function __construct(EntityManagerInterface $em)
+    {
+        $this->em = $em;
+    }
+
+    /**
+     * @param string $field
+     * @param array  $parsedBody
+     *
+     * @return string true if field is empty or false else
+     */
+    protected function isEmptyField(string $field, array $parsedBody): bool
+    {
+        return !isset($parsedBody[$field]);
+    }
+}
diff --git a/src/Action/Admin/InstanceAction.php b/src/Action/Admin/InstanceAction.php
deleted file mode 100644
index e41fb72..0000000
--- a/src/Action/Admin/InstanceAction.php
+++ /dev/null
@@ -1,110 +0,0 @@
-<?php declare(strict_types=1);
-/*
- * This file is part of ANIS SERVER API.
- *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-namespace App\Action\Admin;
-
-use Psr\Log\LoggerInterface;
-use Doctrine\ORM\EntityManagerInterface;
-use Psr\Http\Message\ServerRequestInterface as Request;
-use Psr\Http\Message\ResponseInterface as Response;
-
-use App\Utils\ActionTrait;
-use App\Entity\Admin\Instance;
-
-final class InstanceAction
-{
-    use ActionTrait;
-
-    private $logger;
-    private $em;
-    
-    public function __construct(LoggerInterface $logger, EntityManagerInterface $em)
-    {
-        $this->logger = $logger;
-        $this->em = $em;
-    }
-    
-    public function __invoke(Request $request, Response $response, array $args): Response
-    {
-        $this->logger->info('Instance action dispatched');
-
-        if ($request->isOptions()) {
-            return $response->withHeader('Access-Control-Allow-Methods', 'GET, PUT, DELETE, OPTIONS');
-        }
-
-        $instance = $this->em->find('App\Entity\Admin\Instance', $args['name']);
-
-        if (is_null($instance)) {
-            return $this->dispatchHttpError(
-                $response,
-                'Invalid request',
-                'Instance with name ' . $args['name'] . ' is not found'
-            )->withStatus(404);
-        }
-
-        if ($request->isGet()) {
-            $newResponse = $response->withJson($instance);
-        }
-        
-        if ($request->isPut()) {
-            $parsedBody = $request->getParsedBody();
-
-            // Vérification des champs vides
-            $fields = array(
-                'label',
-                'path_proxy',
-                'dev_mode',
-                'driver',
-                'path',
-                'host',
-                'port',
-                'dbname',
-                'login',
-                'password'
-            );
-            foreach ($fields as $f) {
-                if ($this->isEmptyField($f, $parsedBody)) {
-                    return $this->dispatchHttpError(
-                        $response,
-                        'Invalid request',
-                        'Param ' . $f . ' needed to edit an instance'
-                    );
-                }
-            }
-
-            $this->editInstance($instance, $parsedBody);
-            $newResponse = $response->withJson($instance);
-        }
-        
-        if ($request->isDelete()) {
-            $name = $instance->getName();
-            $this->em->remove($instance);
-            $this->em->flush();
-            $newResponse = $response->withJson(array('message' => 'Instance with name ' . $name . ' is removed!'));
-        }
-
-        return $newResponse;
-    }
-
-    private function editInstance(Instance $instance, array $parsedBody): void
-    {
-        $instance->setLabel($parsedBody['label']);
-        $instance->setPathProxy($parsedBody['path_proxy']);
-        $instance->setDevMode($parsedBody['dev_mode']);
-        $instance->setDriver($parsedBody['driver']);
-        $instance->setPath($parsedBody['path']);
-        $instance->setHost($parsedBody['host']);
-        $instance->setPort($parsedBody['port']);
-        $instance->setDbName($parsedBody['dbname']);
-        $instance->setLogin($parsedBody['login']);
-        $instance->setPassword($parsedBody['password']);
-        $this->em->flush();
-    }
-}
diff --git a/src/Action/Admin/InstanceListAction.php b/src/Action/Admin/InstanceListAction.php
deleted file mode 100644
index eba0e3b..0000000
--- a/src/Action/Admin/InstanceListAction.php
+++ /dev/null
@@ -1,100 +0,0 @@
-<?php declare(strict_types=1);
-/*
- * This file is part of ANIS SERVER API.
- *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-namespace App\Action\Admin;
-
-use Psr\Log\LoggerInterface;
-use Doctrine\ORM\EntityManagerInterface;
-use Psr\Http\Message\ServerRequestInterface as Request;
-use Psr\Http\Message\ResponseInterface as Response;
-
-use App\Utils\ActionTrait;
-use App\Entity\Admin\Instance;
-
-final class InstanceListAction
-{
-    use ActionTrait;
-
-    private $logger;
-    private $em;
-    
-    public function __construct(LoggerInterface $logger, EntityManagerInterface $em)
-    {
-        $this->logger = $logger;
-        $this->em = $em;
-    }
-    
-    public function __invoke(Request $request, Response $response, array $args): Response
-    {
-        $this->logger->info('Instance list action dispatched');
-
-        if ($request->isOptions()) {
-            return $response->withHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
-        }
-
-        if ($request->isGet()) {
-            $instances = $this->em->getRepository('App\Entity\Admin\Instance')->findAll();
-            $newResponse = $response->withJson($instances);
-        }
-
-        if ($request->isPost()) {
-            $parsedBody = $request->getParsedBody();
-
-            // Vérification des champs vides
-            $fields = array(
-                'name',
-                'label',
-                'path_proxy',
-                'dev_mode',
-                'driver',
-                'path',
-                'host',
-                'port',
-                'dbname',
-                'login',
-                'password'
-            );
-            foreach ($fields as $f) {
-                if ($this->isEmptyField($f, $parsedBody)) {
-                    return $this->dispatchHttpError(
-                        $response,
-                        'Invalid request',
-                        'Param ' . $f . ' needed to add a new instance'
-                    );
-                }
-            }
-
-            $instance = $this->postInstance($parsedBody);
-            $newResponse = $response->withJson($instance, 201);
-        }
-
-        return $newResponse;
-    }
-
-    private function postInstance(array $parsedBody): Instance
-    {
-        $instance = new Instance($parsedBody['name']);
-        $instance->setLabel($parsedBody['label']);
-        $instance->setPathProxy($parsedBody['path_proxy']);
-        $instance->setDevMode($parsedBody['dev_mode']);
-        $instance->setDriver($parsedBody['driver']);
-        $instance->setPath($parsedBody['path']);
-        $instance->setHost($parsedBody['host']);
-        $instance->setPort($parsedBody['port']);
-        $instance->setDbName($parsedBody['dbname']);
-        $instance->setLogin($parsedBody['login']);
-        $instance->setPassword($parsedBody['password']);
-
-        $this->em->persist($instance);
-        $this->em->flush();
-
-        return $instance;
-    }
-}
diff --git a/src/Action/Admin/MetamodelAction.php b/src/Action/Admin/MetamodelAction.php
deleted file mode 100644
index 2f74c93..0000000
--- a/src/Action/Admin/MetamodelAction.php
+++ /dev/null
@@ -1,87 +0,0 @@
-<?php declare(strict_types=1);
-/*
- * This file is part of ANIS SERVER API.
- *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-namespace App\Action\Admin;
-
-use Psr\Log\LoggerInterface;
-use Doctrine\ORM\EntityManagerInterface;
-use Doctrine\ORM\Tools\SchemaTool;
-use Psr\Http\Message\ServerRequestInterface as Request;
-use Psr\Http\Message\ResponseInterface as Response;
-
-use App\Utils\ActionTrait;
-use App\Utils\MetaEntityManagerFactory;
-
-final class MetamodelAction
-{
-    use ActionTrait;
-
-    private $logger;
-    private $aem;
-    private $memf;
-    
-    public function __construct(LoggerInterface $logger, EntityManagerInterface $aem, MetaEntityManagerFactory $memf)
-    {
-        $this->logger = $logger;
-        $this->aem = $aem;
-        $this->memf = $memf;
-    }
-    
-    public function __invoke(Request $request, Response $response, array $args): Response
-    {
-        $this->logger->info('Metamodel action dispatched');
-
-        if ($request->isOptions()) {
-            return $response->withHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
-        }
-
-        $instance = $this->aem->find('App\Entity\Admin\Instance', $args['name']);
-
-        if (is_null($instance)) {
-            return $this->dispatchHttpError(
-                $response,
-                'Invalid request',
-                'Instance with name ' . $args['name'] . ' is not found'
-            )->withStatus(404);
-        }
-
-        if (!in_array($args['action'], array('create-database', 'update-database', 'delete-database'))) {
-            return $this->dispatchHttpError(
-                $response,
-                'Invalid request',
-                'Action ' . $args['action'] . ' is not allowed!'
-            )->withStatus(400);
-        }
-
-        $this->memf->createMetaEntityManager($args['name']);
-        $instanceEntityManager = $this->memf->getMetaEntityManager();
-        $schemaTool = new SchemaTool($instanceEntityManager);
-        $classes = $instanceEntityManager->getMetadataFactory()->getAllMetadata();
-        
-        switch ($args['action']) {
-            case 'create-database':
-                $schemaTool->createSchema($classes);
-                $message = 'Database metamodel has been created!';
-                break;
-
-            case 'update-database':
-                $schemaTool->updateSchema($classes);
-                $message = 'Database metamodel has been updated!';
-                break;
-            
-            case 'delete-database':
-                $schemaTool->dropSchema($classes);
-                $message = 'Database metamodel has been removed!';
-                break;
-        }
-
-        return $response->withJson(array('message' => $message));
-    }
-}
diff --git a/src/Action/Admin/OptionAction.php b/src/Action/Admin/OptionAction.php
deleted file mode 100644
index b8ac0d0..0000000
--- a/src/Action/Admin/OptionAction.php
+++ /dev/null
@@ -1,91 +0,0 @@
-<?php declare(strict_types=1);
-/*
- * This file is part of ANIS SERVER API.
- *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-namespace App\Action\Admin;
-
-use Psr\Log\LoggerInterface;
-use Doctrine\ORM\EntityManagerInterface;
-use Psr\Http\Message\ServerRequestInterface as Request;
-use Psr\Http\Message\ResponseInterface as Response;
-
-use App\Utils\ActionTrait;
-use App\Entity\Admin\SettingsOption;
-
-final class OptionAction
-{
-    use ActionTrait;
-
-    private $logger;
-    private $aem;
-    
-    public function __construct(LoggerInterface $logger, EntityManagerInterface $aem)
-    {
-        $this->logger = $logger;
-        $this->aem = $aem;
-    }
-    
-    public function __invoke(Request $request, Response $response, array $args): Response
-    {
-        $this->logger->info('Settings option action dispatched');
-
-        if ($request->isOptions()) {
-            return $response->withHeader('Access-Control-Allow-Methods', 'GET, PUT, DELETE, OPTIONS');
-        }
-
-        $option = $this->aem->find('App\Entity\Admin\SettingsOption', $args['id']);
-
-        if (is_null($option)) {
-            return $this->dispatchHttpError(
-                $response,
-                'Invalid request',
-                'Settings option with id ' . $args['id'] . ' is not found'
-            )->withStatus(404);
-        }
-
-        if ($request->isGet()) {
-            $newResponse = $response->withJson($option);
-        }
-        
-        if ($request->isPut()) {
-            $parsedBody = $request->getParsedBody();
-
-            // Vérification des champs vides
-            foreach (array('label', 'value', 'display') as $a) {
-                if ($this->isEmptyField($a, $parsedBody)) {
-                    return $this->dispatchHttpError(
-                        $response,
-                        'Invalid request',
-                        'Param ' . $a . ' needed to edit the settings option'
-                    );
-                }
-            }
-
-            $this->editOption($option, $parsedBody);
-            $newResponse = $response->withJson($option);
-        }
-        
-        if ($request->isDelete()) {
-            $id = $option->getId();
-            $this->aem->remove($option);
-            $this->aem->flush();
-            $newResponse = $response->withJson(array('message' => 'Settings option with id ' . $id . ' is removed!'));
-        }
-
-        return $newResponse;
-    }
-
-    private function editOption(SettingsOption $option, array $parsedBody): void
-    {
-        $option->setLabel($parsedBody['label']);
-        $option->setValue($parsedBody['value']);
-        $option->setDisplay($parsedBody['display']);
-        $this->aem->flush();
-    }
-}
diff --git a/src/Action/Admin/OptionListAction.php b/src/Action/Admin/OptionListAction.php
deleted file mode 100644
index f4c8efd..0000000
--- a/src/Action/Admin/OptionListAction.php
+++ /dev/null
@@ -1,90 +0,0 @@
-<?php declare(strict_types=1);
-/*
- * This file is part of ANIS SERVER API.
- *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-namespace App\Action\Admin;
-
-use Psr\Log\LoggerInterface;
-use Doctrine\ORM\EntityManagerInterface;
-use Psr\Http\Message\ServerRequestInterface as Request;
-use Psr\Http\Message\ResponseInterface as Response;
-
-use App\Utils\ActionTrait;
-use App\Entity\Admin\SettingsSelect;
-use App\Entity\Admin\SettingsOption;
-
-final class OptionListAction
-{
-    use ActionTrait;
-
-    private $logger;
-    private $aem;
-    
-    public function __construct(LoggerInterface $logger, EntityManagerInterface $aem)
-    {
-        $this->logger = $logger;
-        $this->aem = $aem;
-    }
-    
-    public function __invoke(Request $request, Response $response, array $args): Response
-    {
-        $this->logger->info('Settings option list action dispatched');
-
-        if ($request->isOptions()) {
-            return $response->withHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
-        }
-
-        if ($request->isGet()) {
-            $options = $this->aem->getRepository('App\Entity\Admin\SettingsOption')->findAll();
-            $newResponse = $response->withJson($options);
-        }
-
-        if ($request->isPost()) {
-            $parsedBody = $request->getParsedBody();
-
-            // Vérification des champs vides
-            foreach (array('label', 'value', 'display', 'id_settings_select') as $a) {
-                if ($this->isEmptyField($a, $parsedBody)) {
-                    return $this->dispatchHttpError(
-                        $response,
-                        'Invalid request',
-                        'Param ' . $a . ' needed to add a new settings option'
-                    );
-                }
-            }
-
-            $select = $this->aem->find('App\Entity\Admin\SettingsSelect', $parsedBody['id_settings_select']);
-            if (is_null($select)) {
-                return $this->dispatchHttpError(
-                    $response,
-                    'Invalid request',
-                    'Settings select with id ' . $parsedBody['id_settings_select'] . ' is not found'
-                )->withStatus(404);
-            }
-
-            $option = $this->postOption($select, $parsedBody);
-            $newResponse = $response->withJson($option, 201);
-        }
-
-        return $newResponse;
-    }
-
-    private function postOption(SettingsSelect $select, array $parsedBody): SettingsOption
-    {
-        $option = new SettingsOption($select);
-        $option->setLabel($parsedBody['label']);
-        $option->setValue($parsedBody['value']);
-        $option->setDisplay($parsedBody['display']);
-
-        $this->aem->persist($option);
-        $this->aem->flush();
-
-        return $option;
-    }
-}
diff --git a/src/Action/Admin/SelectAction.php b/src/Action/Admin/SelectAction.php
deleted file mode 100644
index f97ece1..0000000
--- a/src/Action/Admin/SelectAction.php
+++ /dev/null
@@ -1,90 +0,0 @@
-<?php declare(strict_types=1);
-/*
- * This file is part of ANIS SERVER API.
- *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-namespace App\Action\Admin;
-
-use Psr\Log\LoggerInterface;
-use Doctrine\ORM\EntityManagerInterface;
-use Psr\Http\Message\ServerRequestInterface as Request;
-use Psr\Http\Message\ResponseInterface as Response;
-
-use App\Utils\ActionTrait;
-use App\Entity\Admin\SettingsSelect;
-
-final class SelectAction
-{
-    use ActionTrait;
-
-    private $logger;
-    private $aem;
-    
-    public function __construct(LoggerInterface $logger, EntityManagerInterface $aem)
-    {
-        $this->logger = $logger;
-        $this->aem = $aem;
-    }
-    
-    public function __invoke(Request $request, Response $response, array $args): Response
-    {
-        $this->logger->info('Settings select action dispatched');
-
-        if ($request->isOptions()) {
-            return $response->withHeader('Access-Control-Allow-Methods', 'GET, PUT, DELETE, OPTIONS');
-        }
-
-        $select = $this->aem->find('App\Entity\Admin\SettingsSelect', $args['id']);
-
-        if (is_null($select)) {
-            return $this->dispatchHttpError(
-                $response,
-                'Invalid request',
-                'Settings select with id ' . $args['id'] . ' is not found'
-            )->withStatus(404);
-        }
-
-        if ($request->isGet()) {
-            $newResponse = $response->withJson($select);
-        }
-        
-        if ($request->isPut()) {
-            $parsedBody = $request->getParsedBody();
-
-            // Vérification des champs vides
-            foreach (array('name', 'label') as $a) {
-                if ($this->isEmptyField($a, $parsedBody)) {
-                    return $this->dispatchHttpError(
-                        $response,
-                        'Invalid request',
-                        'Param ' . $a . ' needed to edit the settings select'
-                    );
-                }
-            }
-
-            $this->editSelect($select, $parsedBody);
-            $newResponse = $response->withJson($select);
-        }
-        
-        if ($request->isDelete()) {
-            $id = $select->getId();
-            $this->aem->remove($select);
-            $this->aem->flush();
-            $newResponse = $response->withJson(array('message' => 'Settings select with id ' . $id . ' is removed!'));
-        }
-
-        return $newResponse;
-    }
-
-    private function editSelect(SettingsSelect $select, array $parsedBody): void
-    {
-        $select->setName($parsedBody['name']);
-        $select->setLabel($parsedBody['label']);
-        $this->aem->flush();
-    }
-}
diff --git a/src/Action/Admin/SelectListAction.php b/src/Action/Admin/SelectListAction.php
deleted file mode 100644
index 273454b..0000000
--- a/src/Action/Admin/SelectListAction.php
+++ /dev/null
@@ -1,79 +0,0 @@
-<?php declare(strict_types=1);
-/*
- * This file is part of ANIS SERVER API.
- *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-namespace App\Action\Admin;
-
-use Psr\Log\LoggerInterface;
-use Doctrine\ORM\EntityManagerInterface;
-use Psr\Http\Message\ServerRequestInterface as Request;
-use Psr\Http\Message\ResponseInterface as Response;
-
-use App\Utils\ActionTrait;
-use App\Entity\Admin\SettingsSelect;
-
-final class SelectListAction
-{
-    use ActionTrait;
-
-    private $logger;
-    private $aem;
-    
-    public function __construct(LoggerInterface $logger, EntityManagerInterface $aem)
-    {
-        $this->logger = $logger;
-        $this->aem = $aem;
-    }
-    
-    public function __invoke(Request $request, Response $response, array $args): Response
-    {
-        $this->logger->info('Settings select list action dispatched');
-
-        if ($request->isOptions()) {
-            return $response->withHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
-        }
-
-        if ($request->isGet()) {
-            $selects = $this->aem->getRepository('App\Entity\Admin\SettingsSelect')->findAll();
-            $newResponse = $response->withJson($selects);
-        }
-
-        if ($request->isPost()) {
-            $parsedBody = $request->getParsedBody();
-
-            // Vérification des champs vides
-            foreach (array('name', 'label') as $a) {
-                if ($this->isEmptyField($a, $parsedBody)) {
-                    return $this->dispatchHttpError(
-                        $response,
-                        'Invalid request',
-                        'Param ' . $a . ' needed to add a new settings select'
-                    );
-                }
-            }
-
-            $select = $this->postSelect($parsedBody);
-            $newResponse = $response->withJson($select, 201);
-        }
-
-        return $newResponse;
-    }
-
-    private function postSelect(array $parsedBody): SettingsSelect
-    {
-        $select = new SettingsSelect();
-        $select->setName($parsedBody['name']);
-        $select->setLabel($parsedBody['label']);
-
-        $this->aem->persist($select);
-        $this->aem->flush();
-
-        return $select;
-    }
-}
diff --git a/src/Action/Admin/UserAction.php b/src/Action/Admin/UserAction.php
deleted file mode 100644
index 582c91a..0000000
--- a/src/Action/Admin/UserAction.php
+++ /dev/null
@@ -1,91 +0,0 @@
-<?php declare(strict_types=1);
-/*
- * This file is part of ANIS SERVER API.
- *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-namespace App\Action\Admin;
-
-use Psr\Log\LoggerInterface;
-use Doctrine\ORM\EntityManagerInterface;
-use Psr\Http\Message\ServerRequestInterface as Request;
-use Psr\Http\Message\ResponseInterface as Response;
-
-use App\Utils\ActionTrait;
-use App\Entity\Admin\User;
-
-final class UserAction
-{
-    use ActionTrait;
-
-    private $logger;
-    private $aem;
-    
-    public function __construct(LoggerInterface $logger, EntityManagerInterface $aem)
-    {
-        $this->logger = $logger;
-        $this->aem = $aem;
-    }
-    
-    public function __invoke(Request $request, Response $response, array $args): Response
-    {
-        $this->logger->info('User action dispatched');
-
-        if ($request->isOptions()) {
-            return $response->withHeader('Access-Control-Allow-Methods', 'GET, PUT, DELETE, OPTIONS');
-        }
-
-        $user = $this->aem->find('App\Entity\Admin\User', $args['email']);
-
-        if (is_null($user)) {
-            return $this->dispatchHttpError(
-                $response,
-                'Invalid request',
-                'User with email ' . $args['email'] . ' is not found'
-            )->withStatus(404);
-        }
-
-        if ($request->isGet()) {
-            $newResponse = $response->withJson($user);
-        }
-        
-        if ($request->isPut()) {
-            $parsedBody = $request->getParsedBody();
-
-            // Vérification des champs vides
-            foreach (array('adminsi', 'superuser', 'activated') as $a) {
-                if ($this->isEmptyField($a, $parsedBody)) {
-                    return $this->dispatchHttpError(
-                        $response,
-                        'Invalid request',
-                        'Param ' . $a . ' needed to edit the user'
-                    );
-                }
-            }
-
-            $this->editUser($user, $parsedBody);
-            $newResponse = $response->withJson($user);
-        }
-        
-        if ($request->isDelete()) {
-            $email = $user->getEmail();
-            $this->aem->remove($user);
-            $this->aem->flush();
-            $newResponse = $response->withJson(array('message' => 'User with email ' . $email . ' is removed!'));
-        }
-
-        return $newResponse;
-    }
-
-    private function editUser(User $user, array $parsedBody): void
-    {
-        $user->setAdminsi($parsedBody['adminsi']);
-        $user->setSuperuser($parsedBody['superuser']);
-        $user->setActivated($parsedBody['activated']);
-        $this->aem->flush();
-    }
-}
diff --git a/src/Action/Admin/UserListAction.php b/src/Action/Admin/UserListAction.php
deleted file mode 100644
index c402a82..0000000
--- a/src/Action/Admin/UserListAction.php
+++ /dev/null
@@ -1,49 +0,0 @@
-<?php declare(strict_types=1);
-/*
- * This file is part of ANIS SERVER API.
- *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-namespace App\Action\Admin;
-
-use Psr\Log\LoggerInterface;
-use Doctrine\ORM\EntityManagerInterface;
-use Psr\Http\Message\ServerRequestInterface as Request;
-use Psr\Http\Message\ResponseInterface as Response;
-
-use App\Utils\ActionTrait;
-use App\Entity\Admin\User;
-
-final class UserListAction
-{
-    use ActionTrait;
-
-    private $logger;
-    private $aem;
-    
-    public function __construct(LoggerInterface $logger, EntityManagerInterface $aem)
-    {
-        $this->logger = $logger;
-        $this->aem = $aem;
-    }
-    
-    public function __invoke(Request $request, Response $response, array $args): Response
-    {
-        $this->logger->info('User list action dispatched');
-
-        if ($request->isOptions()) {
-            return $response->withHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
-        }
-
-        if ($request->isGet()) {
-            $users = $this->aem->getRepository('App\Entity\Admin\User')->findAll();
-            $newResponse = $response->withJson($users);
-        }
-
-        return $newResponse;
-    }
-}
diff --git a/src/Action/Meta/AttributeAction.php b/src/Action/AttributeAction.php
similarity index 50%
rename from src/Action/Meta/AttributeAction.php
rename to src/Action/AttributeAction.php
index 8b54e81..562c5f1 100644
--- a/src/Action/Meta/AttributeAction.php
+++ b/src/Action/AttributeAction.php
@@ -1,119 +1,68 @@
-<?php declare(strict_types=1);
+<?php
+
 /*
- * This file is part of ANIS SERVER API.
+ * This file is part of Anis Server.
  *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
  */
-namespace App\Action\Meta;
+declare(strict_types=1);
+
+namespace App\Action;
 
-use Psr\Log\LoggerInterface;
 use Psr\Http\Message\ServerRequestInterface as Request;
 use Psr\Http\Message\ResponseInterface as Response;
+use Slim\Exception\HttpNotFoundException;
+use App\Entity\Attribute;
 
-use App\Utils\ActionTrait;
-use App\Utils\MetaEntityManagerFactory;
-use App\Entity\Metamodel\Dataset;
-use App\Entity\Metamodel\Attribute;
-use App\Entity\Metamodel\CriteriaFamily;
-use App\Entity\Metamodel\OutputCategory;
-
-/**
- * Route: /metadata/dataset/{name}/attribute/{id}
- * {name}: Dataset name
- * {id}: Attribute id
- *
- * This action is used to manage one attribute
- *
- * @author François Agneray <francois.agneray@lam.fr>
- * @package App\Action\Meta
- */
-final class AttributeAction
+final class AttributeAction extends AbstractAction
 {
-    use ActionTrait;
-
-    /**
-     * The logger interface is the central access point to log anis information
-     *
-     * @var LoggerInterface
-     */
-    private $logger;
-
-    /**
-     * The MetaEntityManagerFactory create the doctrine entity manager for the metamodel database.
-     * The entity manager is the central access point to Doctrine ORM functionality (metadata database)
-     *
-     * @var MetaEntityManagerFactory
-     */
-    private $memf;
-
-    /**
-     * Create the classe before call __invoke to execute the action
-     *
-     * @param LoggerInterface        $logger
-     * @param MetaEntityManagerFactory $memf
-     */
-    public function __construct(LoggerInterface $logger, MetaEntityManagerFactory $memf)
-    {
-        $this->logger = $logger;
-        $this->memf = $memf;
-    }
-
     /**
      * `GET` Returns the attribute found
-     * `PUT` Full update an attribute and returns the new version
+     * `PUT` Full update the attribute and returns the new version
      *
-     * @param ServerRequestInterface $request   This object contains the HTTP request
-     * @param ResponseInterface      $response  This object represents the HTTP response
-     * @param string[]               $args      This table contains information transmitted in the URL (see routes.php)
+     * @param  ServerRequestInterface $request  PSR-7 This object represents the HTTP request
+     * @param  ResponseInterface      $response PSR-7 This object represents the HTTP response
+     * @param  string[]               $args     This table contains information transmitted in the URL (see routes.php)
      *
      * @return ResponseInterface
      */
     public function __invoke(Request $request, Response $response, array $args): Response
     {
-        $this->logger->info('Attribute list action dispatched');
-
-        if ($request->isOptions()) {
+        if ($request->getMethod() === OPTIONS) {
             return $response->withHeader('Access-Control-Allow-Methods', 'GET, PUT, OPTIONS');
         }
 
-        // Create the doctrine Entity Manager object for the instance metamodel database
-        $this->memf->createMetaEntityManager($args['instance']);
-
-        // Search the correct attribute with primary key
-        $attribute = $this->memf->getMetaEntityManager()->getRepository('App\Entity\Metamodel\Attribute')->findOneBy(
+        $attribute = $this->em->getRepository('App\Entity\Attribute')->findOneBy(
             array('dataset' => $args['name'], 'id' => $args['id'])
         );
 
         // If attribute is not found 404
         if (is_null($attribute)) {
-            return $this->dispatchHttpError(
-                $response,
-                'Invalid request',
-                'Attribute with dataset name ' . $args['name'] . ' and attribute id ' . $args['id'] . 'is not found'
-            )->withStatus(404);
+            throw new HttpNotFoundException(
+                $request,
+                'Attribute with dataset name ' . $args['name'] . ' and attribute id ' . $args['id'] . ' is not found'
+            );
         }
 
-        if ($request->isGet()) {
-            $newResponse = $response->withJson($attribute);
+        if ($request->getMethod() === GET) {
+            $payload = json_encode($attribute);
         }
 
-        if ($request->isPut()) {
+        if ($request->getMethod() === PUT) {
             $parsedBody = $request->getParsedBody();
             $this->editAttribute($attribute, $parsedBody);
-            $newResponse = $response->withJson($attribute);
+            $payload = json_encode($attribute);
         }
 
-        return $newResponse;
+        $response->getBody()->write($payload);
+        return $response;
     }
 
-    private function editAttribute(
-        Attribute $attribute,
-        array $parsedBody
-    ): void {
+    private function editAttribute(Attribute $attribute, array $parsedBody): void
+    {
         $attribute->setLabel($parsedBody['label']);
         $attribute->setFormLabel($parsedBody['form_label']);
         $attribute->setDescription($parsedBody['description']);
@@ -145,8 +94,8 @@ final class AttributeAction
         if (is_null($parsedBody['id_criteria_family'])) {
             $criteriaFamily = null;
         } else {
-            $criteriaFamily = $this->memf->getMetaEntityManager()->find(
-                'App\Entity\Metamodel\CriteriaFamily',
+            $criteriaFamily = $this->em->find(
+                'App\Entity\CriteriaFamily',
                 $parsedBody['id_criteria_family']
             );
         }
@@ -154,12 +103,12 @@ final class AttributeAction
         if (is_null($parsedBody['id_output_category'])) {
             $outputCategory = null;
         } else {
-            $outputCategory = $this->memf->getMetaEntityManager()->find(
-                'App\Entity\Metamodel\OutputCategory',
+            $outputCategory = $this->em->find(
+                'App\Entity\OutputCategory',
                 $parsedBody['id_output_category']
             );
         }
         $attribute->setOutputCategory($outputCategory);
-        $this->memf->getMetaEntityManager()->flush();
+        $this->em->flush();
     }
 }
diff --git a/src/Action/AttributeListAction.php b/src/Action/AttributeListAction.php
new file mode 100644
index 0000000..3ddc339
--- /dev/null
+++ b/src/Action/AttributeListAction.php
@@ -0,0 +1,55 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Action;
+
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Psr\Http\Message\ResponseInterface as Response;
+use Slim\Exception\HttpNotFoundException;
+use App\Entity\Database;
+
+final class AttributeListAction extends AbstractAction
+{
+    /**
+     * `GET`  Returns a list of all attributes of a dataset listed in the metamodel database
+     *
+     * @param  ServerRequestInterface $request  PSR-7 This object represents the HTTP request
+     * @param  ResponseInterface      $response PSR-7 This object represents the HTTP response
+     * @param  string[]               $args     This table contains information transmitted in the URL (see routes.php)
+     *
+     * @return ResponseInterface
+     */
+    public function __invoke(Request $request, Response $response, array $args): Response
+    {
+        if ($request->getMethod() === OPTIONS) {
+            return $response->withHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
+        }
+
+        $dataset = $this->em->find('App\Entity\Dataset', $args['name']);
+
+        // Returns HTTP 404 if the dataset is not found
+        if (is_null($dataset)) {
+            throw new HttpNotFoundException(
+                $request,
+                'Dataset with name ' . $args['name'] . ' is not found'
+            );
+        }
+
+        if ($request->getMethod() === GET) {
+            $attributes = $this->em->getRepository('App\Entity\Attribute')->findByDataset($dataset);
+            $payload = json_encode($attributes);
+        }
+
+        $response->getBody()->write($payload);
+        return $response;
+    }
+}
diff --git a/src/Action/DatabaseAction.php b/src/Action/DatabaseAction.php
new file mode 100644
index 0000000..ad3e49e
--- /dev/null
+++ b/src/Action/DatabaseAction.php
@@ -0,0 +1,100 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Action;
+
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Psr\Http\Message\ResponseInterface as Response;
+use Slim\Exception\HttpBadRequestException;
+use Slim\Exception\HttpNotFoundException;
+use App\Entity\Database;
+
+final class DatabaseAction extends AbstractAction
+{
+    /**
+     * `GET` Returns the database found
+     * `PUT` Full update the database and returns the new version
+     * `DELETE` Delete the database found and return a confirmation message
+     *
+     * @param  ServerRequestInterface $request  PSR-7 This object represents the HTTP request
+     * @param  ResponseInterface      $response PSR-7 This object represents the HTTP response
+     * @param  string[]               $args     This table contains information transmitted in the URL (see routes.php)
+     *
+     * @return ResponseInterface
+     */
+    public function __invoke(Request $request, Response $response, array $args): Response
+    {
+        if ($request->getMethod() === OPTIONS) {
+            return $response->withHeader('Access-Control-Allow-Methods', 'GET, PUT, DELETE, OPTIONS');
+        }
+
+        // Search the correct database with primary key
+        $database = $this->em->find('App\Entity\Database', $args['id']);
+
+        // If database is not found 404
+        if (is_null($database)) {
+            throw new HttpNotFoundException(
+                $request,
+                'Database with id ' . $args['id'] . ' is not found'
+            );
+        }
+
+        if ($request->getMethod() === GET) {
+            $payload = json_encode($database);
+        }
+
+        if ($request->getMethod() === PUT) {
+            $parsedBody = $request->getParsedBody();
+
+            // If mandatories empty fields 400
+            foreach (array('label', 'dbname', 'dbtype', 'dbhost', 'dbport', 'dblogin', 'dbpassword') as $a) {
+                if ($this->isEmptyField($a, $parsedBody)) {
+                    throw new HttpBadRequestException(
+                        $request,
+                        'Param ' . $a . ' needed to edit the database'
+                    );
+                }
+            }
+
+            $this->editDatabase($database, $parsedBody);
+            $payload = json_encode($database);
+        }
+
+        if ($request->getMethod() === DELETE) {
+            $id = $database->getId();
+            $this->em->remove($database);
+            $this->em->flush();
+            $payload = json_encode(array('message' => 'Database with id ' . $id . ' is removed!'));
+        }
+
+        $response->getBody()->write($payload);
+        return $response;
+    }
+
+    /**
+     * Update database object with setters
+     *
+     * @param Database $database   The database to update
+     * @param array    $parsedBody Contains the new values ​​of the database sent by the user
+     */
+    private function editDatabase(Database $database, array $parsedBody): void
+    {
+        $database->setLabel($parsedBody['label']);
+        $database->setDbName($parsedBody['dbname']);
+        $database->setType($parsedBody['dbtype']);
+        $database->setHost($parsedBody['dbhost']);
+        $database->setPort($parsedBody['dbport']);
+        $database->setLogin($parsedBody['dblogin']);
+        $database->setPassword($parsedBody['dbpassword']);
+        $this->em->flush();
+    }
+}
diff --git a/src/Action/DatabaseListAction.php b/src/Action/DatabaseListAction.php
new file mode 100644
index 0000000..0c6759e
--- /dev/null
+++ b/src/Action/DatabaseListAction.php
@@ -0,0 +1,87 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Action;
+
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Psr\Http\Message\ResponseInterface as Response;
+use Slim\Exception\HttpBadRequestException;
+use App\Entity\Database;
+
+final class DatabaseListAction extends AbstractAction
+{
+    /**
+     * `GET`  Returns a list of all databases listed in the metamodel database
+     * `POST` Add a new database
+     *
+     * @param  ServerRequestInterface $request  PSR-7 This object represents the HTTP request
+     * @param  ResponseInterface      $response PSR-7 This object represents the HTTP response
+     * @param  string[]               $args     This table contains information transmitted in the URL (see routes.php)
+     *
+     * @return ResponseInterface
+     */
+    public function __invoke(Request $request, Response $response, array $args): Response
+    {
+        if ($request->getMethod() === OPTIONS) {
+            return $response->withHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
+        }
+
+        if ($request->getMethod() === GET) {
+            // Retrieve user with email adress
+            $databases = $this->em->getRepository('App\Entity\Database')->findAll();
+            $payload = json_encode($databases);
+        }
+
+        if ($request->getMethod() === POST) {
+            $parsedBody = $request->getParsedBody();
+
+            // To work this action needs user information to update
+            foreach (array('label', 'dbname', 'dbtype', 'dbhost', 'dbport', 'dblogin', 'dbpassword') as $a) {
+                if ($this->isEmptyField($a, $parsedBody)) {
+                    throw new HttpBadRequestException(
+                        $request,
+                        'Param ' . $a . ' needed to add a new database'
+                    );
+                }
+            }
+
+            $database = $this->postDatabase($parsedBody);
+            $payload = json_encode($database);
+            $response = $response->withStatus(201);
+        }
+
+        $response->getBody()->write($payload);
+        return $response;
+    }
+
+    /**
+     * Add a new database into the metamodel
+     *
+     * @param array $parsedBody Contains the values ​​of the new database sent by the user
+     */
+    private function postDatabase(array $parsedBody): Database
+    {
+        $database = new Database();
+        $database->setLabel($parsedBody['label']);
+        $database->setDbName($parsedBody['dbname']);
+        $database->setType($parsedBody['dbtype']);
+        $database->setHost($parsedBody['dbhost']);
+        $database->setPort($parsedBody['dbport']);
+        $database->setLogin($parsedBody['dblogin']);
+        $database->setPassword($parsedBody['dbpassword']);
+
+        $this->em->persist($database);
+        $this->em->flush();
+
+        return $database;
+    }
+}
diff --git a/src/Action/DatasetAction.php b/src/Action/DatasetAction.php
new file mode 100644
index 0000000..02960d2
--- /dev/null
+++ b/src/Action/DatasetAction.php
@@ -0,0 +1,122 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Action;
+
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Psr\Http\Message\ResponseInterface as Response;
+use Slim\Exception\HttpBadRequestException;
+use Slim\Exception\HttpNotFoundException;
+use App\Entity\Dataset;
+use App\Entity\DatasetFamily;
+
+final class DatasetAction extends AbstractAction
+{
+    /**
+     * `GET` Returns the dataset found
+     * `PUT` Full update the dataset and returns the new version
+     * `DELETE` Delete the dataset found and return a confirmation message
+     *
+     * @param  ServerRequestInterface $request  PSR-7 This object represents the HTTP request
+     * @param  ResponseInterface      $response PSR-7 This object represents the HTTP response
+     * @param  string[]               $args     This table contains information transmitted in the URL (see routes.php)
+     *
+     * @return ResponseInterface
+     */
+    public function __invoke(Request $request, Response $response, array $args): Response
+    {
+        if ($request->getMethod() === OPTIONS) {
+            return $response->withHeader('Access-Control-Allow-Methods', 'GET, PUT, DELETE, OPTIONS');
+        }
+
+        // Search the correct dataset with primary key
+        $dataset = $this->em->find('App\Entity\Dataset', $args['name']);
+
+        // If dataset is not found 404
+        if (is_null($dataset)) {
+            throw new HttpNotFoundException(
+                $request,
+                'Dataset with name ' . $args['name'] . ' is not found'
+            );
+        }
+        
+        if ($request->getMethod() === GET) {
+            $payload = json_encode($dataset, JSON_UNESCAPED_SLASHES);
+        }
+
+        if ($request->getMethod() === PUT) {
+            $parsedBody = $request->getParsedBody();
+
+            // If mandatories empty fields 400
+            $fields = array(
+                'label',
+                'description',
+                'display',
+                'count',
+                'vo',
+                'data_path',
+                'selectable_row',
+                'id_dataset_family'
+            );
+            foreach ($fields as $a) {
+                if ($this->isEmptyField($a, $parsedBody)) {
+                    throw new HttpBadRequestException(
+                        $request,
+                        'Param ' . $a . ' needed to edit the dataset'
+                    );
+                }
+            }
+
+            // Dataset family is mandatory to edit a dataset
+            $family = $this->em->find('App\Entity\DatasetFamily', $parsedBody['id_dataset_family']);
+            if (is_null($family)) {
+                throw new HttpBadRequestException(
+                    $request,
+                    'Dataset family with id ' . $parsedBody['id_dataset_family'] . ' is not found'
+                );
+            }
+            
+            $this->editDataset($dataset, $parsedBody, $family);
+            $payload = json_encode($dataset, JSON_UNESCAPED_SLASHES);
+        }
+
+        if ($request->getMethod() === DELETE) {
+            $name = $dataset->getName();
+            $this->em->remove($dataset);
+            $this->em->flush();
+            $payload = json_encode(array('message' => 'Dataset with name ' . $name . ' is removed!'));
+        }
+
+        $response->getBody()->write($payload);
+        return $response;
+    }
+
+    /**
+     * Update dataset object with setters
+     *
+     * @param Dataset       $dataset    The dataset to update
+     * @param string[]      $parsedBody Contains the new values ​​of the dataset sent by the user
+     * @param DatasetFamily $family     Contains the dataset family doctrine object
+     */
+    private function editDataset(Dataset $dataset, array $parsedBody, DatasetFamily $family): void
+    {
+        $dataset->setLabel($parsedBody['label']);
+        $dataset->setDescription($parsedBody['description']);
+        $dataset->setDatasetFamily($family);
+        $dataset->setDisplay($parsedBody['display']);
+        $dataset->setCount($parsedBody['count']);
+        $dataset->setVo($parsedBody['vo']);
+        $dataset->setDataPath($parsedBody['data_path']);
+        $dataset->setSelectableRow($parsedBody['selectable_row']);
+        $this->em->flush();
+    }
+}
diff --git a/src/Action/DatasetListAction.php b/src/Action/DatasetListAction.php
new file mode 100644
index 0000000..fe022a2
--- /dev/null
+++ b/src/Action/DatasetListAction.php
@@ -0,0 +1,178 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Action;
+
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Psr\Http\Message\ResponseInterface as Response;
+use Slim\Exception\HttpBadRequestException;
+use Doctrine\ORM\EntityManagerInterface;
+use App\Utils\DBALConnectionFactory;
+use App\Entity\Database;
+use App\Entity\Project;
+use App\Entity\DatasetFamily;
+use App\Entity\Dataset;
+use App\Entity\Attribute;
+
+final class DatasetListAction extends AbstractAction
+{
+    private $connectionFactory;
+
+    /**
+     * Create the classe before call __invoke to execute the action
+     *
+     * @param EntityManagerInterface $em Doctrine       Entity Manager Interface
+     * @param DBALConnectionFactory  $connectionFactory Factory used to construct connection to business database
+     */
+    public function __construct(EntityManagerInterface $em, DBALConnectionFactory $connectionFactory)
+    {
+        parent::__construct($em);
+        $this->connectionFactory = $connectionFactory;
+    }
+
+    /**
+     * `GET`  Returns a list of all datasets listed in the metamodel database
+     * `POST` Add a new dataset
+     *
+     * @param  ServerRequestInterface $request  PSR-7 This object represents the HTTP request
+     * @param  ResponseInterface      $response PSR-7 This object represents the HTTP response
+     * @param  string[]               $args     This table contains information transmitted in the URL (see routes.php)
+     *
+     * @return ResponseInterface
+     */
+    public function __invoke(Request $request, Response $response, array $args): Response
+    {
+        if ($request->getMethod() === OPTIONS) {
+            return $response->withHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
+        }
+
+        if ($request->getMethod() === GET) {
+            $datasets = $this->em->getRepository('App\Entity\Dataset')->findAll();
+            $payload = json_encode($datasets);
+        }
+
+        if ($request->getMethod() === POST) {
+            $parsedBody = $request->getParsedBody();
+
+            // If mandatories empty fields 400
+            $fields = array(
+                'name',
+                'table_ref',
+                'label',
+                'description',
+                'display',
+                'count',
+                'vo',
+                'data_path',
+                'selectable_row',
+                'project_name',
+                'id_dataset_family'
+            );
+            foreach ($fields as $a) {
+                if ($this->isEmptyField($a, $parsedBody)) {
+                    throw new HttpBadRequestException(
+                        $request,
+                        'Param ' . $a . ' needed to add a new dataset'
+                    );
+                }
+            }
+
+            // Project is mandatory to add a new dataset
+            $project = $this->em->find('App\Entity\Project', $parsedBody['project_name']);
+            if (is_null($project)) {
+                throw new HttpBadRequestException(
+                    $request,
+                    'Project with name ' . $parsedBody['project_name'] . ' is not found'
+                );
+            }
+
+            $family = $this->em->find('App\Entity\DatasetFamily', $parsedBody['id_dataset_family']);
+            if (is_null($family)) {
+                throw new HttpBadRequestException(
+                    $request,
+                    'Dataset family with id ' . $parsedBody['id_dataset_family'] . ' is not found'
+                );
+            }
+
+            $dataset = $this->postDataset($parsedBody, $project, $family);
+            $payload = json_encode($dataset);
+            $response = $response->withStatus(201);
+        }
+
+        $response->getBody()->write($payload);
+        return $response;
+    }
+
+    /**
+     * Create a new dataset doctrine object and save it
+     *
+     * @param array         $parsedBody    Contains the values ​​of the new dataset sent by the user
+     * @param Project       $project       Contains the project doctrine object
+     * @param DatasetFamily $datasetFamily Contains the dataset family doctrine object
+     *
+     * @return Dataset
+     */
+    private function postDataset(array $parsedBody, Project $project, DatasetFamily $family): Dataset
+    {
+        $dataset = new Dataset($parsedBody['name']);
+        $dataset->setTableRef($parsedBody['table_ref']);
+        $dataset->setLabel($parsedBody['label']);
+        $dataset->setDescription($parsedBody['description']);
+        $dataset->setDisplay($parsedBody['display']);
+        $dataset->setCount($parsedBody['count']);
+        $dataset->setVo($parsedBody['vo']);
+        $dataset->setDataPath($parsedBody['data_path']);
+        $dataset->setSelectableRow($parsedBody['selectable_row']);
+        $dataset->setProject($project);
+        $dataset->setDatasetFamily($family);
+
+        $this->em->persist($dataset);
+        $this->postAttributes($dataset);
+        $this->em->flush();
+
+        return $dataset;
+    }
+
+    /**
+     * Access to the business database and get all the columns of the table (table_ref)
+     * and makes the corresponding doctrine attribute objects
+     *
+     * @param Dataset $dataset Contains the new dataset doctrine object
+     */
+    private function postAttributes(Dataset $dataset): void
+    {
+        $database = $dataset->getProject()->getDatabase();
+        $connection = $this->connectionFactory->create($database);
+        $sm = $connection->getSchemaManager();
+        $columns = $sm->listTableColumns($dataset->getTableRef());
+        $i = 10;
+        $id = 1;
+        foreach ($columns as $column) {
+            $columnName = $column->getName();
+            $columnType = $column->getType()->getName();
+            $attribute = new Attribute($id, $dataset);
+            $attribute->setName($columnName);
+            $attribute->setTableName($dataset->getTableRef());
+            $attribute->setLabel($columnName);
+            $attribute->setFormLabel($columnName);
+            $attribute->setType($columnType);
+            $attribute->setCriteriaDisplay($i);
+            $attribute->setOutputDisplay($i);
+            $attribute->setDisplayDetail($i);
+            $attribute->setOrderDisplay($i);
+            $attribute->setSelected(true);
+            $this->em->persist($attribute);
+            $i = $i + 10;
+            $id++;
+        }
+    }
+}
diff --git a/src/Action/FamilyAction.php b/src/Action/FamilyAction.php
new file mode 100644
index 0000000..6201b2e
--- /dev/null
+++ b/src/Action/FamilyAction.php
@@ -0,0 +1,103 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Action;
+
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Psr\Http\Message\ResponseInterface as Response;
+use Slim\Exception\HttpBadRequestException;
+use Slim\Exception\HttpNotFoundException;
+use App\Entity\Family;
+
+final class FamilyAction extends AbstractAction
+{
+    public function __invoke(Request $request, Response $response, array $args): Response
+    {
+        if ($request->getMethod() === OPTIONS) {
+            return $response->withHeader('Access-Control-Allow-Methods', 'GET, PUT, DELETE, OPTIONS');
+        }
+
+        $type = $this->verifyType($args['type']);
+        if (empty($type)) {
+            throw new HttpBadRequestException(
+                $request,
+                'Type ' . $args['type'] . ' is not defined'
+            );
+        }
+
+        $entityClass = $this->getEntityClass($type);
+        $family = $this->em->find($entityClass, $args['id']);
+        if (is_null($family)) {
+            throw new HttpNotFoundException(
+                $request,
+                ucfirst($type) . ' family with id ' . $args['id'] . ' is not found'
+            );
+        }
+
+        if ($request->getMethod() === GET) {
+            $payload = json_encode($family);
+        }
+
+        if ($request->getMethod() === PUT) {
+            $parsedBody = $request->getParsedBody();
+
+            // Vérification des champs vides
+            $fields = array('label', 'display');
+            foreach ($fields as $a) {
+                if ($this->isEmptyField($a, $parsedBody)) {
+                    throw new HttpBadRequestException(
+                        $request,
+                        'Param ' . $a . ' needed to edit the ' . $type . ' family'
+                    );
+                }
+            }
+
+            $this->editFamily($family, $parsedBody);
+            $payload = json_encode($family);
+        }
+
+        if ($request->getMethod() === DELETE) {
+            $id = $family->getId();
+            $this->em->remove($family);
+            $this->em->flush();
+            $payload = json_encode(array(
+                'message' => ucfirst($type) . ' family with id ' . $id . ' is removed!'
+            ));
+        }
+
+        $response->getBody()->write($payload);
+        return $response;
+    }
+
+    private function verifyType(string $type): string
+    {
+        $t = strtolower($type);
+
+        if ($t == 'dataset' || $t == 'output' || $t == 'criteria') {
+            return $t;
+        } else {
+            return '';
+        }
+    }
+
+    private function getEntityClass(string $type): string
+    {
+        return 'App\Entity\\' . ucfirst($type) . 'Family';
+    }
+
+    private function editFamily(object $family, array $parsedBody): void
+    {
+        $family->setLabel($parsedBody['label']);
+        $family->setDisplay($parsedBody['display']);
+        $this->em->flush();
+    }
+}
diff --git a/src/Action/FamilyListAction.php b/src/Action/FamilyListAction.php
new file mode 100644
index 0000000..8a81b59
--- /dev/null
+++ b/src/Action/FamilyListAction.php
@@ -0,0 +1,100 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Action;
+
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Psr\Http\Message\ResponseInterface as Response;
+use Slim\Exception\HttpBadRequestException;
+use App\Entity\Family;
+
+final class FamilyListAction extends AbstractAction
+{
+    /**
+     * `GET`  Returns a list of all families by type listed in the metamodel database
+     * `POST` Add a new family
+     *
+     * @param  ServerRequestInterface $request  PSR-7 This object represents the HTTP request
+     * @param  ResponseInterface      $response PSR-7 This object represents the HTTP response
+     * @param  string[]               $args     This table contains information transmitted in the URL (see routes.php)
+     *
+     * @return ResponseInterface
+     */
+    public function __invoke(Request $request, Response $response, array $args): Response
+    {
+        if ($request->getMethod() === OPTIONS) {
+            return $response->withHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
+        }
+
+        $type = $this->verifyType($args['type']);
+        if (empty($type)) {
+            throw new HttpBadRequestException(
+                $request,
+                'Type ' . $args['type'] . ' is not defined'
+            );
+        }
+
+        if ($request->getMethod() === GET) {
+            $families = $this->em->getRepository($this->getEntityClass($type))->findAll();
+            $payload = json_encode($families);
+        }
+
+        if ($request->getMethod() === POST) {
+            $parsedBody = $request->getParsedBody();
+
+            // To work this action needs information to update
+            foreach (array('label', 'display') as $a) {
+                if ($this->isEmptyField($a, $parsedBody)) {
+                    throw new HttpBadRequestException(
+                        $request,
+                        'Param ' . $a . ' needed to add a new family'
+                    );
+                }
+            }
+
+            $family = $this->postFamily($parsedBody, $this->getEntityClass($type));
+            $payload = json_encode($family);
+            $response = $response->withStatus(201);
+        }
+
+        $response->getBody()->write($payload);
+        return $response;
+    }
+
+    private function verifyType(string $type): string
+    {
+        $t = strtolower($type);
+
+        if ($t == 'dataset' || $t == 'output' || $t == 'criteria') {
+            return $t;
+        } else {
+            return '';
+        }
+    }
+
+    private function getEntityClass(string $type): string
+    {
+        return 'App\Entity\\' . ucfirst($type) . 'Family';
+    }
+
+    private function postFamily(array $parsedBody, string $class): object
+    {
+        $family = new $class();
+        $family->setLabel($parsedBody['label']);
+        $family->setDisplay($parsedBody['display']);
+
+        $this->em->persist($family);
+        $this->em->flush();
+
+        return $family;
+    }
+}
diff --git a/src/Action/Login/ActivateAccountAction.php b/src/Action/Login/ActivateAccountAction.php
deleted file mode 100644
index b55fbb2..0000000
--- a/src/Action/Login/ActivateAccountAction.php
+++ /dev/null
@@ -1,168 +0,0 @@
-<?php declare(strict_types=1);
-/*
- * This file is part of ANIS SERVER API.
- *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-namespace App\Action\Login;
-
-use Psr\Log\LoggerInterface;
-use Doctrine\ORM\EntityManagerInterface;
-use Psr\Http\Message\ServerRequestInterface as Request;
-use Psr\Http\Message\ResponseInterface as Response;
-use Swift_Mailer;
-use Swift_Message;
-
-use App\Utils\ActionTrait;
-use App\Entity\Admin\User;
-
-/**
- * Route: /login/activate-account
- *
- * With this action, the newly registered user be able
- * to send the activation code of his new account. (GET)
- *
- * @author François Agneray <francois.agneray@lam.fr>
- * @package App\Action\Login
- */
-final class ActivateAccountAction
-{
-    use ActionTrait;
-
-    /**
-     * The logger interface is the central access point to log anis information
-     *
-     * @var LoggerInterface
-     */
-    private $logger;
-
-    /**
-     * The EntityManager is the central access point to Doctrine ORM functionality (metadata database)
-     *
-     * @var EntityManagerInterface
-     */
-    private $aem;
-
-    /**
-     * Swift Mailer class is used here to send notification by email
-     *
-     * @var Swift_Mailer
-     */
-    private $mailer;
-
-    /**
-     * Create the classe before call __invoke to execute the action
-     *
-     * @param LoggerInterface        $logger
-     * @param EntityManagerInterface $em
-     * @param Swift_Mailer           $mailer
-     */
-    public function __construct(LoggerInterface $logger, EntityManagerInterface $aem, Swift_Mailer $mailer)
-    {
-        $this->logger = $logger;
-        $this->aem = $aem;
-        $this->mailer = $mailer;
-    }
-
-    /**
-     * This action activate the new user Anis account
-     * Data is read into the http get (email + activation_key)
-     * This action send a message to warn the user and returns http response
-     *
-     * @param ServerRequestInterface $request   This object contains the HTTP request
-     * @param ResponseInterface      $response  This object represents the HTTP response
-     * @param string[]               $args      This table contains information transmitted in the URL (see routes.php)
-     *
-     * @return ResponseInterface
-     */
-    public function __invoke(Request $request, Response $response, array $args): Response
-    {
-        $this->logger->info('Activate account action dispatched');
-
-        if ($request->isOptions()) {
-            return $response->withHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
-        }
-
-        if ($request->isGet()) {
-            $queryParams = $request->getQueryParams();
-
-            // To work this action needs email and activation_key of the new anis user account
-            foreach (array('email', 'activation_key') as $a) {
-                if ($this->isEmptyField($a, $queryParams)) {
-                    return $this->dispatchHttpError(
-                        $response,
-                        'Invalid request',
-                        'Param ' . $a . ' needed to activate a new user account'
-                    );
-                }
-            }
-
-            // Is the user exists ?
-            if (!$this->isExistUser($queryParams['email'])) {
-                return $this->dispatchHttpError(
-                    $response,
-                    'Invalid user',
-                    'No account is identified with this email address'
-                );
-            }
-
-            $user = $this->aem->find('App\Entity\Admin\User', $queryParams['email']);
-            
-            // Is the activation key is the good one ?
-            if ($user->getActivationKey() !== $queryParams['activation_key']) {
-                return $this->dispatchHttpError(
-                    $response,
-                    'Invalid activation key',
-                    'Bad activation key; Unable to activate account'
-                );
-            }
-
-            // It's all right. Account activated
-            $user->setActivated(true);
-            $this->aem->flush();
-
-            // Send an email confirmation for the user and returns user modified
-            $this->sendEmail($user->getEmail());
-            return $response->withJson($user);
-        }
-    }
-
-    /**
-     * @param string $email
-     *
-     * @return bool
-     */
-    private function isExistUser(string $email): bool
-    {
-        $user = $this->aem->getRepository('App\Entity\Admin\User')->findOneBy(array('email' => $email));
-        if (isset($user)) {
-            return true;
-        } else {
-            return false;
-        }
-    }
-
-    /**
-     * @param string $email
-     */
-    private function sendEmail(string $email): void
-    {
-        $body  = 'Dear user ' . PHP_EOL;
-        $body .= PHP_EOL;
-        $body .= 'Your account is now activated.' . PHP_EOL;
-        $body .= PHP_EOL;
-        $body .= 'Best regards' . PHP_EOL;
-        $body .= 'Anis Team';
-
-        $message = new Swift_Message('ANIS registration confirmation');
-        $message->setFrom(['noreply@anis.fr' => 'ANIS']);
-        $message->setTo([$email]);
-        $message->setBody($body);
-            
-        $this->mailer->send($message);
-    }
-}
diff --git a/src/Action/Login/ChangePasswordAction.php b/src/Action/Login/ChangePasswordAction.php
deleted file mode 100644
index 55b21ee..0000000
--- a/src/Action/Login/ChangePasswordAction.php
+++ /dev/null
@@ -1,173 +0,0 @@
-<?php declare(strict_types=1);
-/*
- * This file is part of ANIS SERVER API.
- *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-namespace App\Action\Login;
-
-use Psr\Log\LoggerInterface;
-use Doctrine\ORM\EntityManagerInterface;
-use Psr\Http\Message\ServerRequestInterface as Request;
-use Psr\Http\Message\ResponseInterface as Response;
-use Swift_Mailer;
-use Swift_Message;
-
-use App\Utils\ActionTrait;
-
-/**
- * Route: /login/change-password
- *
- * With this action the anis user can change his password (POST)
- *
- * @author François Agneray <francois.agneray@lam.fr>
- * @package App\Action\Login
- */
-final class ChangePasswordAction
-{
-    use ActionTrait;
-
-    /**
-     * The logger interface is the central access point to log anis information
-     *
-     * @var LoggerInterface
-     */
-    private $logger;
-
-    /**
-     * The EntityManager is the central access point to Doctrine ORM functionality (metadata database)
-     *
-     * @var EntityManagerInterface
-     */
-    private $aem;
-
-    /**
-     * Swift Mailer class is used here to send notification by email
-     *
-     * @var Swift_Mailer
-     */
-    private $mailer;
-
-    /**
-     * Create the classe before call __invoke to execute the action
-     *
-     * @param LoggerInterface        $logger
-     * @param EntityManagerInterface $aem
-     * @param Swift_Mailer           $mailer
-     */
-    public function __construct(LoggerInterface $logger, EntityManagerInterface $aem, Swift_Mailer $mailer)
-    {
-        $this->logger = $logger;
-        $this->aem = $aem;
-        $this->mailer = $mailer;
-    }
-
-    /**
-     * This action change the anis password by a newer (new_password)
-     *
-     * @param ServerRequestInterface $request   This object contains the HTTP request
-     * @param ResponseInterface      $response  This object represents the HTTP response
-     * @param string[]               $args      This table contains information transmitted in the URL (see routes.php)
-     *
-     * @return ResponseInterface
-     */
-    public function __invoke(Request $request, Response $response, array $args): Response
-    {
-        $this->logger->info('Change password action dispatched');
-
-        if ($request->isOptions()) {
-            return $response->withHeader('Access-Control-Allow-Methods', 'POST, OPTIONS');
-        }
-
-        if ($request->isPost()) {
-            $parsedBody = $request->getParsedBody();
-
-            // To work this action needs email password and new_password
-            foreach (array('email', 'password', 'new_password') as $a) {
-                if ($this->isEmptyField($a, $parsedBody)) {
-                    return $this->dispatchHttpError(
-                        $response,
-                        'Invalid request',
-                        'Param ' . $a . ' needed to change your password'
-                    );
-                }
-            }
-
-            // Is the user exists ?
-            if (!$this->isExistUser($parsedBody)) {
-                return $this->dispatchHttpError(
-                    $response,
-                    'Invalid user',
-                    'No account is identified with this email address'
-                );
-            }
-
-            $user = $this->aem->find('App\Entity\Admin\User', $parsedBody['email']);
-
-            // Is the user account is activated ?
-            if (!$user->getActivated()) {
-                return $this->dispatchHttpError(
-                    $response,
-                    'Invalid user',
-                    'Account not yet activated'
-                );
-            }
-
-            // Check the couple email + password
-            if (!password_verify($parsedBody['password'], $user->getPassword())) {
-                return $this->dispatchHttpError(
-                    $response,
-                    'Invalid password',
-                    'Bad password; unable to change the password'
-                );
-            }
-
-            // It's all right ! change the password by the new one
-            $user->setPassword(password_hash($parsedBody['new_password'], PASSWORD_DEFAULT));
-            $this->aem->flush();
-
-            // Send a email confirmation for the user and returns user modified
-            $this->sendEmail($user->getEmail());
-            return $response->withJson(array('message' => 'Password changed!'));
-        }
-    }
-
-    /**
-     * @param string $email
-     *
-     * @return bool
-     */
-    private function isExistUser(array $parsedBody): bool
-    {
-        $user = $this->aem->getRepository('App\Entity\Admin\User')->findOneBy(array('email' => $parsedBody['email']));
-        if (isset($user)) {
-            return true;
-        } else {
-            return false;
-        }
-    }
-
-    /**
-     * @param string $email
-     */
-    private function sendEmail(string $email): void
-    {
-        $body  = 'Dear user ' . PHP_EOL;
-        $body .= PHP_EOL;
-        $body .= 'Your new password is now active.' . PHP_EOL;
-        $body .= PHP_EOL;
-        $body .= 'Best regards' . PHP_EOL;
-        $body .= 'Anis Team';
-
-        $message = new \Swift_Message('ANIS confirm password change');
-        $message->setFrom(['noreply@anis.fr' => 'ANIS']);
-        $message->setTo([$email]);
-        $message->setBody($body);
-            
-        $this->mailer->send($message);
-    }
-}
diff --git a/src/Action/Login/NewPasswordAction.php b/src/Action/Login/NewPasswordAction.php
deleted file mode 100644
index 4b117b3..0000000
--- a/src/Action/Login/NewPasswordAction.php
+++ /dev/null
@@ -1,165 +0,0 @@
-<?php declare(strict_types=1);
-/*
- * This file is part of ANIS SERVER API.
- *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-namespace App\Action\Login;
-
-use Psr\Log\LoggerInterface;
-use Doctrine\ORM\EntityManagerInterface;
-use Psr\Http\Message\ServerRequestInterface as Request;
-use Psr\Http\Message\ResponseInterface as Response;
-use Swift_Mailer;
-
-use App\Utils\ActionTrait;
-
-/**
- * Route: /login/new-password
- *
- * This action asks for a new password
- *
- * @author François Agneray <francois.agneray@lam.fr>
- * @package App\Action\Login
- */
-final class NewPasswordAction
-{
-    use ActionTrait;
-
-    /**
-     * The logger interface is the central access point to log anis information
-     *
-     * @var LoggerInterface
-     */
-    private $logger;
-
-    /**
-     * The EntityManager is the central access point to Doctrine ORM functionality (metadata database)
-     *
-     * @var EntityManagerInterface
-     */
-    private $aem;
-
-    /**
-     * Swift Mailer class is used here to send notification by email
-     *
-     * @var Swift_Mailer
-     */
-    private $mailer;
-
-    /**
-     * Create the classe before call __invoke to execute the action
-     *
-     * @param LoggerInterface        $logger
-     * @param EntityManagerInterface $aem
-     * @param Swift_Mailer           $mailer
-     */
-    public function __construct(LoggerInterface $logger, EntityManagerInterface $aem, Swift_Mailer $mailer)
-    {
-        $this->logger = $logger;
-        $this->aem = $aem;
-        $this->mailer = $mailer;
-    }
-
-    /**
-     * This action asks for a new password (forgit password)
-     * This action needs email address to send a new generated password
-     *
-     * @param ServerRequestInterface $request   This object contains the HTTP request
-     * @param ResponseInterface      $response  This object represents the HTTP response
-     * @param string[]               $args      This table contains information transmitted in the URL (see routes.php)
-     *
-     * @return ResponseInterface
-     */
-    public function __invoke(Request $request, Response $response, array $args): Response
-    {
-        $this->logger->info('New password action dispatched');
-
-        if ($request->isOptions()) {
-            return $response->withHeader('Access-Control-Allow-Methods', 'POST, OPTIONS');
-        }
-
-        if ($request->isPost()) {
-            $parsedBody = $request->getParsedBody();
-
-            // To work this action needs email
-            if ($this->isEmptyField('email', $parsedBody)) {
-                return $this->dispatchHttpError(
-                    $response,
-                    'Invalid request',
-                    'Param email needed to generate a new password'
-                );
-            }
-
-            // Is the user exists ?
-            if (!$this->isExistUser($parsedBody)) {
-                return $this->dispatchHttpError(
-                    $response,
-                    'Invalid user',
-                    'No account is identified with this email address'
-                );
-            }
-            
-            $user = $this->aem->find('App\Entity\Admin\User', $parsedBody['email']);
-
-            // Is the user account is activated ?
-            if (!$user->getActivated()) {
-                return $this->dispatchHttpError(
-                    $response,
-                    'Invalid user',
-                    'Account not yet activated'
-                );
-            }
-
-            // It'as all right ! Password generation write database
-            $pwd = uniqid();
-            $hash = password_hash($pwd, PASSWORD_DEFAULT);
-            $user->setPassword($hash);
-            $this->aem->flush();
-
-            // Send a confirmation email with new generated password
-            $this->sendEmail($user->getEmail(), $pwd);
-            return $response->withJson(array('message' => 'Password re-generated!'));
-        }
-    }
-
-    /**
-     * @param string $email
-     *
-     * @return bool
-     */
-    private function isExistUser(array $parsedBody): bool
-    {
-        $user = $this->aem->getRepository('App\Entity\Admin\User')->findOneBy(array('email' => $parsedBody['email']));
-        if (isset($user)) {
-            return true;
-        } else {
-            return false;
-        }
-    }
-
-    /**
-     * @param string $email
-     */
-    private function sendEmail(string $email, string $newPassword): void
-    {
-        $body  = 'Dear user ' . PHP_EOL;
-        $body .= PHP_EOL;
-        $body .= 'A new password has been generated for you.' . PHP_EOL;
-        $body .= 'New password: ' . $newPassword . PHP_EOL;
-        $body .= PHP_EOL;
-        $body .= 'Best regards' . PHP_EOL;
-        $body .= 'Anis Team';
-
-        $message = new \Swift_Message('ANIS new password');
-        $message->setFrom(['noreply@anis.fr' => 'ANIS']);
-        $message->setTo([$email]);
-        $message->setBody($body);
-            
-        $this->mailer->send($message);
-    }
-}
diff --git a/src/Action/Login/RegisterAction.php b/src/Action/Login/RegisterAction.php
deleted file mode 100644
index 84a6096..0000000
--- a/src/Action/Login/RegisterAction.php
+++ /dev/null
@@ -1,207 +0,0 @@
-<?php declare(strict_types=1);
-/*
- * This file is part of ANIS SERVER API.
- *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-namespace App\Action\Login;
-
-use Psr\Log\LoggerInterface;
-use Doctrine\ORM\EntityManagerInterface;
-use Psr\Http\Message\ServerRequestInterface as Request;
-use Psr\Http\Message\ResponseInterface as Response;
-use Swift_Mailer;
-use Swift_Message;
-
-use App\Utils\ActionTrait;
-use App\Entity\Admin\User;
-
-/**
- * Route: /login/register
- *
- * An action to saves a new user Anis (POST)
- *
- * @author François Agneray <francois.agneray@lam.fr>
- * @package App\Action\Login
- */
-final class RegisterAction
-{
-    use ActionTrait;
-
-    /**
-     * The logger interface is the central access point to log anis information
-     *
-     * @var LoggerInterface
-     */
-    private $logger;
-
-    /**
-     * The EntityManager is the central access point to Doctrine ORM functionality (metadata database)
-     *
-     * @var EntityManagerInterface
-     */
-    private $aem;
-
-    /**
-     * Swift Mailer class is used here to send notification by email
-     *
-     * @var Swift_Mailer
-     */
-    private $mailer;
-
-    /**
-     * Create the classe before call __invoke to execute the action
-     *
-     * @param LoggerInterface        $logger
-     * @param EntityManagerInterface $aem
-     * @param Swift_Mailer           $mailer
-     */
-    public function __construct(LoggerInterface $logger, EntityManagerInterface $aem, Swift_Mailer $mailer)
-    {
-        $this->logger = $logger;
-        $this->aem = $aem;
-        $this->mailer = $mailer;
-    }
-
-    /**
-     * This action register a new user Anis
-     * Data is read into the http post body ($parsedBody => email, password)
-     * This action send a message to warn the user and returns http response
-     * This action returns a code 201 and the user in json format
-     *
-     * @param ServerRequestInterface $request   This object contains the HTTP request
-     * @param ResponseInterface      $response  This object represents the HTTP response
-     * @param string[]               $args      This table contains information transmitted in the URL (see routes.php)
-     *
-     * @return ResponseInterface
-     */
-    public function __invoke(Request $request, Response $response, array $args): Response
-    {
-        $this->logger->info('Register action dispatched');
-
-        if ($request->isOptions()) {
-            return $response->withHeader('Access-Control-Allow-Methods', 'POST, OPTIONS');
-        }
-
-        if ($request->isPost()) {
-            $parsedBody = $request->getParsedBody();
-
-            // To work this action needs email and password of the new anis user
-            foreach (array('email', 'password') as $a) {
-                if ($this->isEmptyField($a, $parsedBody)) {
-                    return $this->dispatchHttpError(
-                        $response,
-                        'Invalid request',
-                        'Param ' . $a . ' needed to register a new user'
-                    );
-                }
-            }
-
-            // The email address must be unique in the database
-            if ($this->isExistUser($parsedBody)) {
-                return $this->dispatchHttpError(
-                    $response,
-                    'Invalid user',
-                    'A user with the email address ' . $parsedBody['email'] . ' already exists'
-                );
-            }
-
-            // The email address must be valid (well-formed)
-            if (!$this->isVerifEmailOk($parsedBody)) {
-                return $this->dispatchHttpError(
-                    $response,
-                    'Invalid email',
-                    'Bad email adress; a well-defined email address is needed to finalize the registration'
-                );
-            }
-
-            // Add the user in the database => anis_user table
-            $user = $this->createUser($parsedBody);
-
-            // Send a confirmation email to the new anis user
-            $this->sendEmail($request, $user);
-            return $response->withJson($user, 201);
-        }
-    }
-
-    /**
-     * @param array $parsedBody
-     *
-     * @return bool
-     */
-    private function isExistUser(array $parsedBody): bool
-    {
-        $user = $this->aem->getRepository('App\Entity\Admin\User')->findOneBy(array('email' => $parsedBody['email']));
-        if (isset($user)) {
-            return true;
-        } else {
-            return false;
-        }
-    }
-
-    /**
-     * @param array $parsedBody
-     *
-     * @return bool
-     */
-    private function isVerifEmailOk(array $parsedBody): bool
-    {
-        if (filter_var($parsedBody['email'], FILTER_VALIDATE_EMAIL)) {
-            return true;
-        } else {
-            return false;
-        }
-    }
-
-    /**
-     * @param array $parsedBody
-     *
-     * @return User
-     */
-    private function createUser(array $parsedBody): User
-    {
-        $user = new User($parsedBody['email']);
-
-        $user->setPassword(password_hash($parsedBody['password'], PASSWORD_DEFAULT));
-        $user->setAdminsi(false);
-        $user->setSuperuser(false);
-        $user->setActivationKey(uniqid('', true)); // Clé activation pour verification email
-        $user->setActivated(false); // L'utilisateur n'est pas actif avant verification email
-        $this->aem->persist($user);
-        $this->aem->flush();
-
-        return $user;
-    }
-
-    /**
-     * @param Request $request This object contains the HTTP request
-     * @param User    $user    The new anis user created
-     */
-    private function sendEmail(Request $request, User $user): void
-    {
-        $url  = $request->getUri()->getScheme() . '://' . $request->getUri()->getHost();
-        if ($request->getUri()->getPort() != 80) {
-            $url .= ':' . $request->getUri()->getPort();
-        }
-        $url .= '/activate-account?email=' . $user->getEmail() . '&activation_key=' . $user->getActivationKey();
-
-        $body  = 'Dear user ' . PHP_EOL;
-        $body .= PHP_EOL;
-        $body .= 'To activate your account, please click on the link below : ' . PHP_EOL;
-        $body .= $url . PHP_EOL;
-        $body .= PHP_EOL;
-        $body .= 'Best regards' . PHP_EOL;
-        $body .= 'Anis Team';
-
-        $message = new Swift_Message('ANIS registration confirmation');
-        $message->setFrom(['noreply@anis.fr' => 'ANIS']);
-        $message->setTo([$user->getEmail()]);
-        $message->setBody($body);
-            
-        $this->mailer->send($message);
-    }
-}
diff --git a/src/Action/Login/TokenAction.php b/src/Action/Login/TokenAction.php
deleted file mode 100644
index 9467b8c..0000000
--- a/src/Action/Login/TokenAction.php
+++ /dev/null
@@ -1,182 +0,0 @@
-<?php declare(strict_types=1);
-/*
- * This file is part of ANIS SERVER API.
- *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-namespace App\Action\Login;
-
-use Psr\Log\LoggerInterface;
-use Doctrine\ORM\EntityManagerInterface;
-use Psr\Http\Message\ServerRequestInterface as Request;
-use Psr\Http\Message\ResponseInterface as Response;
-use Lcobucci\JWT\Builder as JwtBuilder;
-use Lcobucci\JWT\Signer as JwtSigner;
-use Lcobucci\JWT\Token;
-
-use App\Utils\ActionTrait;
-
-/**
- * Route: /login/token
- *
- * Get a valid token (jwt) for a anis user (POST)
- *
- * @author François Agneray <francois.agneray@lam.fr>
- * @package App\Action\Login
- */
-final class TokenAction
-{
-    use ActionTrait;
-
-    /**
-     * The logger interface is the central access point to log anis information
-     *
-     * @var LoggerInterface
-     */
-    private $logger;
-
-    /**
-     * The EntityManager is the central access point to Doctrine ORM functionality (metadata database)
-     *
-     * @var EntityManagerInterface
-     */
-    private $aem;
-
-    /**
-     * This class makes easier the token creation process (JWT)
-     *
-     * @var Lcobucci\JWT\Builder
-     */
-    private $jwtBuilder;
-
-    /**
-     * Basic interface for token signers
-     *
-     * @var Lcobucci\JWT\Signer
-     */
-    private $jwtSigner;
-
-    /**
-     * This class is creates before call __invoke to execute the action
-     *
-     * @param LoggerInterface        $logger
-     * @param EntityManagerInterface $aem
-     * @param Lcobucci\JWT\Builder   $jwtBuilder
-     * @param Lcobucci\JWT\Signer    $jwtSigner
-     */
-    public function __construct(
-        LoggerInterface $logger,
-        EntityManagerInterface $aem,
-        JwtBuilder $jwtBuilder,
-        JwtSigner $jwtSigner
-    ) {
-        $this->logger = $logger;
-        $this->aem = $aem;
-        $this->jwtBuilder = $jwtBuilder;
-        $this->jwtSigner = $jwtSigner;
-    }
-
-    /**
-     * This action generated a new Json Web Token
-     * This action needs the couple email an password to authentify the anis user
-     * This action returns the new jwt signed and generated
-     *
-     * @param ServerRequestInterface $request   This object contains the HTTP request
-     * @param ResponseInterface      $response  This object represents the HTTP response
-     * @param string[]               $args      This table contains information transmitted in the URL (see routes.php)
-     *
-     * @return ResponseInterface
-     */
-    public function __invoke(Request $request, Response $response, array $args): Response
-    {
-        $this->logger->info('Login action dispatched');
-
-        if ($request->isOptions()) {
-            return $response->withHeader('Access-Control-Allow-Methods', 'POST, OPTIONS');
-        }
-
-        if ($request->isPost()) {
-            $parsedBody = $request->getParsedBody();
-
-            // To work this action needs email and password of the anis user
-            foreach (array('email', 'password') as $a) {
-                if ($this->isEmptyField($a, $parsedBody)) {
-                    return $this->dispatchHttpError(
-                        $response,
-                        'Invalid request',
-                        'Param ' . $a . ' needed to login'
-                    );
-                }
-            }
-
-            // Is the user exists ?
-            if (!$this->isExistUser($parsedBody['email'])) {
-                return $this->dispatchHttpError(
-                    $response,
-                    'Invalid user',
-                    'No account is identified with this email address'
-                );
-            }
-
-            $user = $this->aem->find('\App\Entity\Admin\User', $parsedBody['email']);
-
-            // Is the user activated ?
-            if (!$user->getActivated()) {
-                return $this->dispatchHttpError(
-                    $response,
-                    'Invalid user',
-                    'Account not yet activated'
-                );
-            }
-
-            // Is the password is ok ?
-            if (!password_verify($parsedBody['password'], $user->getPassword())) {
-                return $this->dispatchHttpError(
-                    $response,
-                    'Invalid password',
-                    'Bad password; unable to login'
-                );
-            }
-
-            // // It's all right. JWT generation
-            $token = $this->generateToken($user->getEmail());
-            $data = array('email' => $user->getEmail(), 'token' => (string) $token);
-            return $response->withJson($data);
-        }
-    }
-
-    /**
-     * @param string $email
-     *
-     * @return bool
-     */
-    private function isExistUser(string $email): bool
-    {
-        $user = $this->aem->getRepository('App\Entity\Admin\User')->findOneBy(array('email' => $email));
-        if (isset($user)) {
-            return true;
-        } else {
-            return false;
-        }
-    }
-
-    /**
-     * Returns the new signed token for an anis user
-     *
-     * @param string $email
-     *
-     * @return Token
-     */
-    private function generateToken(string $email): Token
-    {
-        $token = $this->jwtBuilder
-            ->set('email', $email) // Ajout de l'adresse email du participant au jeton
-            ->sign($this->jwtSigner, 'testing') // Fabrique une signature avec clé
-            ->getToken(); // Retourne le jeton généré
-        return $token;
-    }
-}
diff --git a/src/Action/Meta/AttributeListAction.php b/src/Action/Meta/AttributeListAction.php
deleted file mode 100644
index e89fd7e..0000000
--- a/src/Action/Meta/AttributeListAction.php
+++ /dev/null
@@ -1,105 +0,0 @@
-<?php declare(strict_types=1);
-/*
- * This file is part of ANIS SERVER API.
- *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-namespace App\Action\Meta;
-
-use Psr\Log\LoggerInterface;
-use Psr\Http\Message\ServerRequestInterface as Request;
-use Psr\Http\Message\ResponseInterface as Response;
-
-use App\Utils\ActionTrait;
-use App\Utils\MetaEntityManagerFactory;
-use App\Entity\Metamodel\Dataset;
-use App\Entity\Metamodel\Attribute;
-use App\Entity\Metamodel\CriteriaFamily;
-use App\Entity\Metamodel\OutputCategory;
-
-/**
- * Route: /metadata/dataset/{name}/attribute/
- * {name}: Dataset name
- *
- * This action returns a list of all attributes in a dataset
- *
- * @author François Agneray <francois.agneray@lam.fr>
- * @package App\Action\Meta
- */
-final class AttributeListAction
-{
-    use ActionTrait;
-
-    /**
-     * The logger interface is the central access point to log anis information
-     *
-     * @var LoggerInterface
-     */
-    private $logger;
-
-    /**
-     * The MetaEntityManagerFactory create the doctrine entity manager for the metamodel database.
-     * The entity manager is the central access point to Doctrine ORM functionality (metadata database)
-     *
-     * @var MetaEntityManagerFactory
-     */
-    private $memf;
-
-    /**
-     * Create the classe before call __invoke to execute the action
-     *
-     * @param LoggerInterface          $logger
-     * @param MetaEntityManagerFactory $memf
-     */
-    public function __construct(LoggerInterface $logger, MetaEntityManagerFactory $memf)
-    {
-        $this->logger = $logger;
-        $this->memf = $memf;
-    }
-
-    /**
-     * `GET` Returns all attributes in the dataset selected
-     *
-     * @param ServerRequestInterface $request   This object contains the HTTP request
-     * @param ResponseInterface      $response  This object represents the HTTP response
-     * @param string[]               $args      This table contains information transmitted in the URL (see routes.php)
-     *
-     * @return ResponseInterface
-     */
-    public function __invoke(Request $request, Response $response, array $args): Response
-    {
-        $this->logger->info('Attribute list action dispatched');
-
-        if ($request->isOptions()) {
-            return $response->withHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
-        }
-
-        // Create the doctrine Entity Manager object for the instance metamodel database
-        $this->memf->createMetaEntityManager($args['instance']);
-
-        $dataset = $this->memf->getMetaEntityManager()->find('App\Entity\Metamodel\Dataset', $args['name']);
-
-        // Returns HTTP 404 if the dataset is not found
-        if (is_null($dataset)) {
-            return $this->dispatchHttpError(
-                $response,
-                'Invalid request',
-                'Dataset with name ' . $args['name'] . ' is not found'
-            )->withStatus(404);
-        }
-
-        if ($request->isGet()) {
-            $attributes = array();
-            foreach ($dataset->getAttributes() as $a) {
-                $attributes[] = $a;
-            }
-            $newResponse = $response->withJson($attributes);
-        }
-
-        return $newResponse;
-    }
-}
diff --git a/src/Action/Meta/DatabaseAction.php b/src/Action/Meta/DatabaseAction.php
deleted file mode 100644
index 8edc407..0000000
--- a/src/Action/Meta/DatabaseAction.php
+++ /dev/null
@@ -1,153 +0,0 @@
-<?php declare(strict_types=1);
-/*
- * This file is part of ANIS SERVER API.
- *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-namespace App\Action\Meta;
-
-use Psr\Log\LoggerInterface;
-use Doctrine\ORM\EntityManagerInterface;
-use Psr\Http\Message\ServerRequestInterface as Request;
-use Psr\Http\Message\ResponseInterface as Response;
-
-use App\Utils\ActionTrait;
-use App\Utils\MetaEntityManagerFactory;
-use App\Entity\Metamodel\Database;
-
-/**
- * Route: /metadata/database/{id}
- * {id}: Database id
- *
- * This action is used to manage one database
- *
- * @author François Agneray <francois.agneray@lam.fr>
- * @package App\Action\Meta
- */
-final class DatabaseAction
-{
-    use ActionTrait;
-
-    /**
-     * The logger interface is the central access point to log anis information
-     *
-     * @var LoggerInterface
-     */
-    private $logger;
-
-    /**
-     * The MetaEntityManagerFactory create the doctrine entity manager for the metamodel database.
-     * The entity manager is the central access point to Doctrine ORM functionality (metadata database)
-     *
-     * @var MetaEntityManagerFactory
-     */
-    private $memf;
-
-    /**
-     * The key needed to encrypt the password of the database
-     *
-     * @var string
-     */
-    private $encryptionKey;
-    
-    /**
-     * Create the classe before call __invoke to execute the action
-     *
-     * @param LoggerInterface        $logger
-     * @param EntityManagerInterface $memf
-     * @param string                 $encryptionKey
-     */
-    public function __construct(LoggerInterface $logger, MetaEntityManagerFactory $memf, string $encryptionKey)
-    {
-        $this->logger = $logger;
-        $this->memf = $memf;
-        $this->encryptionKey = $encryptionKey;
-    }
-    
-    /**
-     * `GET` Returns the database found
-     * `PUT` Full update the database and returns the new version
-     * `DELETE` Delete the database found and return a confirmation message
-     *
-     * @param ServerRequestInterface $request   This object contains the HTTP request
-     * @param ResponseInterface      $response  This object represents the HTTP response
-     * @param string[]               $args      This table contains information transmitted in the URL (see routes.php)
-     *
-     * @return ResponseInterface
-     */
-    public function __invoke(Request $request, Response $response, array $args): Response
-    {
-        $this->logger->info('Database action dispatched');
-
-        if ($request->isOptions()) {
-            return $response->withHeader('Access-Control-Allow-Methods', 'GET, PUT, DELETE, OPTIONS');
-        }
-
-        $this->memf->createMetaEntityManager($args['instance']);
-
-        // Search the correct database with primary key
-        $database = $this->memf->getMetaEntityManager()->find('App\Entity\Metamodel\Database', $args['id']);
-
-        // If database is not found 404
-        if (is_null($database)) {
-            return $this->dispatchHttpError(
-                $response,
-                'Invalid request',
-                'Database with id ' . $args['id'] . ' is not found'
-            )->withStatus(404);
-        }
-
-        if ($request->isGet()) {
-            $newResponse = $response->withJson($database);
-        }
-        
-        if ($request->isPut()) {
-            $parsedBody = $request->getParsedBody();
-
-            // If mandatories empty fields 400
-            foreach (array('label', 'dbname', 'dbtype', 'dbhost', 'dbport', 'dblogin', 'dbpassword') as $a) {
-                if ($this->isEmptyField($a, $parsedBody)) {
-                    return $this->dispatchHttpError(
-                        $response,
-                        'Invalid request',
-                        'Param ' . $a . ' needed to edit the database'
-                    );
-                }
-            }
-
-            $this->editDatabase($database, $parsedBody);
-            $newResponse = $response->withJson($database);
-        }
-        
-        if ($request->isDelete()) {
-            $id = $database->getId();
-            $this->memf->getMetaEntityManager()->remove($database);
-            $this->memf->getMetaEntityManager()->flush();
-            $newResponse = $response->withJson(array('message' => 'Database with id ' . $id . ' is removed!'));
-        }
-
-        return $newResponse;
-    }
-
-    /**
-     * Update database object with setters
-     *
-     * @param Database $database   The database to update
-     * @param string[] $parsedBody Contains the new values ​​of the database sent by the user
-     */
-    private function editDatabase(Database $database, array $parsedBody): void
-    {
-        $database->setLabel($parsedBody['label']);
-        $database->setDbName($parsedBody['dbname']);
-        $database->setType($parsedBody['dbtype']);
-        $database->setHost($parsedBody['dbhost']);
-        $database->setPort($parsedBody['dbport']);
-        $database->setLogin($parsedBody['dblogin']);
-        $database->setPassword($this->encryptData($parsedBody['dbpassword']));
-        $this->memf->getMetaEntityManager()->flush();
-    }
-}
diff --git a/src/Action/Meta/DatabaseListAction.php b/src/Action/Meta/DatabaseListAction.php
deleted file mode 100644
index d68be7f..0000000
--- a/src/Action/Meta/DatabaseListAction.php
+++ /dev/null
@@ -1,137 +0,0 @@
-<?php declare(strict_types=1);
-/*
- * This file is part of ANIS SERVER API.
- *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-namespace App\Action\Meta;
-
-use Psr\Log\LoggerInterface;
-use Doctrine\ORM\EntityManagerInterface;
-use Psr\Http\Message\ServerRequestInterface as Request;
-use Psr\Http\Message\ResponseInterface as Response;
-
-use App\Utils\ActionTrait;
-use App\Utils\MetaEntityManagerFactory;
-use App\Entity\Metamodel\Database;
-
-/**
- * Route: /metadata/database
- *
- * This action returns a list of all databases listed in the metamodel database
- *
- * @author François Agneray <francois.agneray@lam.fr>
- * @package App\Action\Meta
- */
-final class DatabaseListAction
-{
-    use ActionTrait;
-
-    /**
-     * The logger interface is the central access point to log anis information
-     *
-     * @var LoggerInterface
-     */
-    private $logger;
-
-    /**
-     * The MetaEntityManagerFactory create the doctrine entity manager for the metamodel database.
-     * The entity manager is the central access point to Doctrine ORM functionality (metadata database)
-     *
-     * @var MetaEntityManagerFactory
-     */
-    private $memf;
-
-    /**
-     * The key needed to encrypt the password of the database
-     *
-     * @var string
-     */
-    private $encryptionKey;
-    
-    /**
-     * Create the classe before call __invoke to execute the action
-     *
-     * @param LoggerInterface        $logger
-     * @param EntityManagerInterface $memf
-     * @param string                 $encryptionKey
-     */
-    public function __construct(LoggerInterface $logger, MetaEntityManagerFactory $memf, string $encryptionKey)
-    {
-        $this->logger = $logger;
-        $this->memf = $memf;
-        $this->encryptionKey = $encryptionKey;
-    }
-    
-    /**
-     * `GET`  Returns a list of all databases listed in the metamodel database
-     * `POST` Add a new database
-     *
-     * @param ServerRequestInterface $request   This object contains the HTTP request
-     * @param ResponseInterface      $response  This object represents the HTTP response
-     * @param string[]               $args      This table contains information transmitted in the URL (see routes.php)
-     *
-     * @return ResponseInterface
-     */
-    public function __invoke(Request $request, Response $response, array $args): Response
-    {
-        $this->logger->info('Database list action dispatched');
-
-        if ($request->isOptions()) {
-            return $response->withHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
-        }
-
-        $this->memf->createMetaEntityManager($args['instance']);
-
-        if ($request->isGet()) {
-            $databases = $this->memf->getMetaEntityManager()->getRepository('App\Entity\Metamodel\Database')->findAll();
-            $newResponse = $response->withJson($databases);
-        }
-        
-        if ($request->isPost()) {
-            $parsedBody = $request->getParsedBody();
-
-            // If mandatories empty fields 400
-            foreach (array('label', 'dbname', 'dbtype', 'dbhost', 'dbport', 'dblogin', 'dbpassword') as $a) {
-                if ($this->isEmptyField($a, $parsedBody)) {
-                    return $this->dispatchHttpError(
-                        $response,
-                        'Invalid request',
-                        'Param ' . $a . ' needed to add a new database'
-                    );
-                }
-            }
-
-            $database = $this->postDatabase($parsedBody);
-            $newResponse = $response->withJson($database, 201);
-        }
-
-        return $newResponse;
-    }
-
-    /**
-     * Add a new database into the metamodel
-     *
-     * @param string[] $parsedBody Contains the values ​​of the new database sent by the user
-     */
-    private function postDatabase(array $parsedBody): Database
-    {
-        $database = new Database();
-        $database->setLabel($parsedBody['label']);
-        $database->setDbName($parsedBody['dbname']);
-        $database->setType($parsedBody['dbtype']);
-        $database->setHost($parsedBody['dbhost']);
-        $database->setPort($parsedBody['dbport']);
-        $database->setLogin($parsedBody['dblogin']);
-        $database->setPassword($this->encryptData($parsedBody['dbpassword']));
-
-        $this->memf->getMetaEntityManager()->persist($database);
-        $this->memf->getMetaEntityManager()->flush();
-
-        return $database;
-    }
-}
diff --git a/src/Action/Meta/DatasetAction.php b/src/Action/Meta/DatasetAction.php
deleted file mode 100644
index b36b42c..0000000
--- a/src/Action/Meta/DatasetAction.php
+++ /dev/null
@@ -1,173 +0,0 @@
-<?php declare(strict_types=1);
-/*
- * This file is part of ANIS SERVER API.
- *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-namespace App\Action\Meta;
-
-use Psr\Log\LoggerInterface;
-use Psr\Http\Message\ServerRequestInterface as Request;
-use Psr\Http\Message\ResponseInterface as Response;
-
-use App\Utils\ActionTrait;
-use App\Utils\MetaEntityManagerFactory;
-use App\Entity\Metamodel\Dataset;
-use App\Entity\Metamodel\DatasetFamily;
-
-/**
- * Route: /metadata/dataset/{name}
- * {name}: Dataset name
- *
- * This action is used to manage one dataset
- *
- * @author François Agneray <francois.agneray@lam.fr>
- * @package App\Action\Meta
- */
-final class DatasetAction
-{
-    use ActionTrait;
-
-    /**
-     * The logger interface is the central access point to log anis information
-     *
-     * @var LoggerInterface
-     */
-    private $logger;
-
-    /**
-     * The MetaEntityManagerFactory create the doctrine entity manager for the metamodel database.
-     * The entity manager is the central access point to Doctrine ORM functionality (metadata database)
-     *
-     * @var MetaEntityManagerFactory
-     */
-    private $memf;
-    
-    /**
-     * Create the classe before call __invoke to execute the action
-     *
-     * @param LoggerInterface          $logger
-     * @param MetaEntityManagerFactory $memf
-     */
-    public function __construct(LoggerInterface $logger, MetaEntityManagerFactory $memf)
-    {
-        $this->logger = $logger;
-        $this->memf = $memf;
-    }
-
-    /**
-     * `GET` Returns the dataset found
-     * `PUT` Full update the dataset and returns the new version
-     * `DELETE` Delete the dataset found and return a confirmation message
-     *
-     * @param ServerRequestInterface $request   This object contains the HTTP request
-     * @param ResponseInterface      $response  This object represents the HTTP response
-     * @param string[]               $args      This table contains information transmitted in the URL (see routes.php)
-     *
-     * @return ResponseInterface
-     */
-    public function __invoke(Request $request, Response $response, array $args): Response
-    {
-        $this->logger->info('Dataset action dispatched');
-
-        if ($request->isOptions()) {
-            return $response->withHeader('Access-Control-Allow-Methods', 'GET, PUT, DELETE, OPTIONS');
-        }
-
-        // Create the doctrine Entity Manager object for the instance metamodel database
-        $this->memf->createMetaEntityManager($args['instance']);
-
-        // Search the correct dataset with primary key
-        $dataset = $this->memf->getMetaEntityManager()->find('App\Entity\Metamodel\Dataset', $args['name']);
-
-        // If dataset is not found 404
-        if (is_null($dataset)) {
-            return $this->dispatchHttpError(
-                $response,
-                'Invalid request',
-                'Dataset with name ' . $args['name'] . ' is not found'
-            )->withStatus(404);
-        }
-
-        if ($request->isGet()) {
-            $newResponse = $response->withJson($dataset);
-        }
-        
-        if ($request->isPut()) {
-            $parsedBody = $request->getParsedBody();
-
-            // If mandatories empty fields 400
-            $fields = array(
-                'label',
-                'description',
-                'display',
-                'count',
-                'vo',
-                'data_path',
-                'selectable_row',
-                'id_dataset_family'
-            );
-            foreach ($fields as $a) {
-                if ($this->isEmptyField($a, $parsedBody)) {
-                    return $this->dispatchHttpError(
-                        $response,
-                        'Invalid request',
-                        'Param ' . $a . ' needed to edit the dataset'
-                    );
-                }
-            }
-
-            // Search the correct dataset family with primary key
-            $family = $this->memf->getMetaEntityManager()->find(
-                'App\Entity\Metamodel\DatasetFamily',
-                $parsedBody['id_dataset_family']
-            );
-
-            // Dataset family is mandatory. If is null 404
-            if (is_null($family)) {
-                return $this->dispatchHttpError(
-                    $response,
-                    'Invalid request',
-                    'Dataset family with id ' . $parsedBody['id_dataset_family'] . ' is not found'
-                )->withStatus(404);
-            }
-            
-            $this->editDataset($dataset, $parsedBody, $family);
-            $newResponse = $response->withJson($dataset);
-        }
-        
-        if ($request->isDelete()) {
-            $name = $dataset->getName();
-            $this->memf->getMetaEntityManager()->remove($dataset);
-            $this->memf->getMetaEntityManager()->flush();
-            $newResponse = $response->withJson(array('message' => 'Dataset with name ' . $name . ' is removed!'));
-        }
-
-        return $newResponse;
-    }
-
-    /**
-     * Update dataset object with setters
-     *
-     * @param Dataset       $dataset    The dataset to update
-     * @param string[]      $parsedBody Contains the new values ​​of the dataset sent by the user
-     * @param DatasetFamily $family     Contains the dataset family doctrine object
-     */
-    private function editDataset(Dataset $dataset, array $parsedBody, DatasetFamily $family): void
-    {
-        $dataset->setLabel($parsedBody['label']);
-        $dataset->setDescription($parsedBody['description']);
-        $dataset->setDatasetFamily($family);
-        $dataset->setDisplay($parsedBody['display']);
-        $dataset->setCount($parsedBody['count']);
-        $dataset->setVo($parsedBody['vo']);
-        $dataset->setDataPath($parsedBody['data_path']);
-        $dataset->setSelectableRow($parsedBody['selectable_row']);
-
-        $this->memf->getMetaEntityManager()->flush();
-    }
-}
diff --git a/src/Action/Meta/DatasetListAction.php b/src/Action/Meta/DatasetListAction.php
deleted file mode 100644
index 42af7ef..0000000
--- a/src/Action/Meta/DatasetListAction.php
+++ /dev/null
@@ -1,240 +0,0 @@
-<?php declare(strict_types=1);
-/*
- * This file is part of ANIS SERVER API.
- *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-namespace App\Action\Meta;
-
-use Psr\Log\LoggerInterface;
-use Psr\Http\Message\ServerRequestInterface as Request;
-use Psr\Http\Message\ResponseInterface as Response;
-
-use App\Utils\ActionTrait;
-use App\Utils\DBALConnectionFactory;
-use App\Utils\MetaEntityManagerFactory;
-use App\Entity\Metamodel\Dataset;
-use App\Entity\Metamodel\Attribute;
-use App\Entity\Metamodel\Project;
-use App\Entity\Metamodel\DatasetFamily;
-
-/**
- * Route: /metadata/dataset
- *
- * This action is used to:
- *  - Get all datasets
- *  - Add a new dataset
- *
- * @author François Agneray <francois.agneray@lam.fr>
- * @package App\Action\Meta
- */
-final class DatasetListAction
-{
-    use ActionTrait;
-
-    /**
-     * The logger interface is the central access point to log anis information
-     *
-     * @var LoggerInterface
-     */
-    private $logger;
-
-    /**
-     * The MetaEntityManagerFactory create the doctrine entity manager for the metamodel database.
-     * The entity manager is the central access point to Doctrine ORM functionality (metadata database)
-     *
-     * @var MetaEntityManagerFactory
-     */
-    private $memf;
-
-    /**
-     * Factory that provides a connection to the business database
-     *
-     * @var DBALConnectionFactory
-     */
-    private $dcf;
-
-    /**
-     * The key needed to decrypt the password of the database
-     *
-     * @var string
-     */
-    private $encryptionKey;
-    
-    /**
-     * Create the classe before call __invoke to execute the action
-     *
-     * @param LoggerInterface          $logger
-     * @param MetaEntityManagerFactory $memf
-     * @param DBALConnectionFactory    $dcf
-     * @param string                   $encryptionKey
-     */
-    public function __construct(
-        LoggerInterface $logger,
-        MetaEntityManagerFactory $memf,
-        DBALConnectionFactory $dcf,
-        string $encryptionKey
-    ) {
-        $this->logger = $logger;
-        $this->memf = $memf;
-        $this->dcf = $dcf;
-        $this->encryptionKey = $encryptionKey;
-    }
-    
-    /**
-     * `GET` Returns the list of datasets
-     * `POST` Add a new dataset. The program will search the business database for
-     *        the columns of the table (table_ref) to transform them into attributes
-     *
-     * @param ServerRequestInterface $request   This object contains the HTTP request
-     * @param ResponseInterface      $response  This object represents the HTTP response
-     * @param string[]               $args      This table contains information transmitted in the URL (see routes.php)
-     *
-     * @return ResponseInterface
-     */
-    public function __invoke(Request $request, Response $response, array $args): Response
-    {
-        $this->logger->info('Dataset list action dispatched');
-
-        if ($request->isOptions()) {
-            return $response->withHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
-        }
-
-        // Create the doctrine Entity Manager object for the instance metamodel database
-        $this->memf->createMetaEntityManager($args['instance']);
-
-        if ($request->isGet()) {
-            $datasets = $this->memf->getMetaEntityManager()->getRepository('App\Entity\Metamodel\Dataset')->findBy(
-                array(),
-                array('display' => 'ASC')
-            );
-            $newResponse = $response->withJson($datasets);
-        }
-        
-        if ($request->isPost()) {
-            $parsedBody = $request->getParsedBody();
-
-            // If mandatories empty fields 400
-            $fields = array(
-                'name',
-                'table_ref',
-                'label',
-                'description',
-                'display',
-                'count',
-                'vo',
-                'data_path',
-                'selectable_row',
-                'project_name',
-                'id_dataset_family'
-            );
-            foreach ($fields as $a) {
-                if ($this->isEmptyField($a, $parsedBody)) {
-                    return $this->dispatchHttpError(
-                        $response,
-                        'Invalid request',
-                        'Param ' . $a . ' needed to add a new dataset'
-                    );
-                }
-            }
-
-            $project = $this->memf->getMetaEntityManager()->find(
-                'App\Entity\Metamodel\Project',
-                $parsedBody['project_name']
-            );
-            if (is_null($project)) {
-                return $this->dispatchHttpError(
-                    $response,
-                    'Invalid request',
-                    'Project with name ' . $parsedBody['project_name'] . ' is not found'
-                )->withStatus(404);
-            }
-
-            $family = $this->memf->getMetaEntityManager()->find(
-                'App\Entity\Metamodel\DatasetFamily',
-                $parsedBody['id_dataset_family']
-            );
-            if (is_null($family)) {
-                return $this->dispatchHttpError(
-                    $response,
-                    'Invalid request',
-                    'Dataset family with id ' . $parsedBody['id_dataset_family'] . ' is not found'
-                )->withStatus(404);
-            }
-
-            $dataset = $this->postDataset($parsedBody, $project, $family);
-            $newResponse = $response->withJson($dataset);
-        }
-
-        return $newResponse;
-    }
-
-    /**
-     * Create a new dataset doctrine object and save it
-     *
-     * @param string[]      $parsedBody    Contains the values ​​of the new dataset sent by the user
-     * @param Project       $project       Contains the project doctrine object
-     * @param DatasetFamily $datasetFamily Contains the dataset family doctrine object
-     *
-     * @return Dataset
-     */
-    private function postDataset(array $parsedBody, Project $project, DatasetFamily $family): Dataset
-    {
-        $dataset = new Dataset($parsedBody['name']);
-        $dataset->setTableRef($parsedBody['table_ref']);
-        $dataset->setLabel($parsedBody['label']);
-        $dataset->setDescription($parsedBody['description']);
-        $dataset->setDisplay($parsedBody['display']);
-        $dataset->setCount($parsedBody['count']);
-        $dataset->setVo($parsedBody['vo']);
-        $dataset->setDataPath($parsedBody['data_path']);
-        $dataset->setSelectableRow($parsedBody['selectable_row']);
-        $dataset->setProject($project);
-        $dataset->setDatasetFamily($family);
-
-        $this->memf->getMetaEntityManager()->persist($dataset);
-        $this->postAttributes($dataset);
-        $this->memf->getMetaEntityManager()->flush();
-
-        return $dataset;
-    }
-
-    /**
-     * Access to the business database dans get all the columns of the table (table_ref)
-     * and makes the corresponding doctrine attribute objects
-     *
-     * @param Dataset $dataset Contains the new dataset doctrine object
-     */
-    private function postAttributes(Dataset $dataset): void
-    {
-        $database = $dataset->getProject()->getDatabase();
-        $decryptedPassword = $this->decryptData($database->getPassword());
-        $connection = $this->dcf->create($database, $decryptedPassword);
-        $sm = $connection->getSchemaManager();
-        $columns = $sm->listTableColumns($dataset->getTableRef());
-        $i = 10;
-        $id = 1;
-        foreach ($columns as $column) {
-            $columnName = $column->getName();
-            $columnType = $column->getType()->getName();
-            $attribute = new Attribute($id, $dataset);
-            $attribute->setName($columnName);
-            $attribute->setTableName($dataset->getTableRef());
-            $attribute->setLabel($columnName);
-            $attribute->setFormLabel($columnName);
-            $attribute->setType($columnType);
-            $attribute->setCriteriaDisplay($i);
-            $attribute->setOutputDisplay($i);
-            $attribute->setDisplayDetail($i);
-            $attribute->setOrderDisplay($i);
-            $attribute->setSelected(true);
-            $this->memf->getMetaEntityManager()->persist($attribute);
-            $i = $i + 10;
-            $id++;
-        }
-    }
-}
diff --git a/src/Action/Meta/FamilyAction.php b/src/Action/Meta/FamilyAction.php
deleted file mode 100644
index f0f5172..0000000
--- a/src/Action/Meta/FamilyAction.php
+++ /dev/null
@@ -1,133 +0,0 @@
-<?php declare(strict_types=1);
-/*
- * This file is part of ANIS SERVER API.
- *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-namespace App\Action\Meta;
-
-use Psr\Log\LoggerInterface;
-use Psr\Http\Message\ServerRequestInterface as Request;
-use Psr\Http\Message\ResponseInterface as Response;
-
-use App\Utils\ActionTrait;
-use App\Utils\MetaEntityManagerFactory;
-
-final class FamilyAction
-{
-    use ActionTrait;
-
-    private $logger;
-    private $memf;
-    
-    public function __construct(LoggerInterface $logger, MetaEntityManagerFactory $memf)
-    {
-        $this->logger = $logger;
-        $this->memf = $memf;
-    }
-    
-    public function __invoke(Request $request, Response $response, array $args): Response
-    {
-        $this->logger->info('Family action dispatched');
-
-        if ($request->isOptions()) {
-            return $response->withHeader('Access-Control-Allow-Methods', 'GET, PUT, DELETE, OPTIONS');
-        }
-
-        $type = $this->verifyType($args['type']);
-
-        if (empty($type)) {
-            return $this->dispatchHttpError(
-                $response,
-                'Invalid request',
-                'Type ' . $args['type'] . ' is not defined'
-            );
-        }
-
-        // Create the doctrine Entity Manager object for the instance metamodel database
-        $this->memf->createMetaEntityManager($args['instance']);
-
-        $entityClass = $this->getEntityClass($type);
-        $family = $this->memf->getMetaEntityManager()->find($entityClass, $args['id']);
-
-        if (is_null($family)) {
-            return $this->dispatchHttpError(
-                $response,
-                'Invalid request',
-                ucfirst($type) . ' family with id ' . $args['id'] . ' is not found'
-            )->withStatus(404);
-        }
-
-        if ($request->isGet()) {
-            $newResponse = $response->withJson($family);
-        }
-        
-        if ($request->isPut()) {
-            $parsedBody = $request->getParsedBody();
-
-            // Vérification des champs vides
-            $fields = array('label', 'display');
-            foreach ($fields as $a) {
-                if ($this->isEmptyField($a, $parsedBody)) {
-                    return $this->dispatchHttpError(
-                        $response,
-                        'Invalid request',
-                        'Param ' . $a . ' needed to edit the ' . $type . ' family'
-                    );
-                }
-            }
-
-            $this->editFamily($family, $parsedBody);
-            $newResponse = $response->withJson($family);
-        }
-        
-        if ($request->isDelete()) {
-            $id = $family->getId();
-            $this->memf->getMetaEntityManager()->remove($family);
-            $this->memf->getMetaEntityManager()->flush();
-            $newResponse = $response->withJson(array(
-                'message' => ucfirst($type) . ' family with id ' . $id . ' is removed!'
-            ));
-        }
-
-        return $newResponse;
-    }
-
-    private function getType(object $family): string
-    {
-        if ($family instanceof \App\Entity\Metamodel\DatasetFamily) {
-            return 'dataset';
-        } elseif ($family instanceof \App\Entity\Metamodel\CriteriaFamily) {
-            return 'criteria';
-        } else {
-            return 'output';
-        }
-    }
-
-    private function verifyType(string $type): string
-    {
-        $t = strtolower($type);
-
-        if ($t == 'dataset' || $t == 'output' || $t == 'criteria') {
-            return $t;
-        } else {
-            return '';
-        }
-    }
-
-    private function getEntityClass(string $type): string
-    {
-        return 'App\Entity\Metamodel\\' . ucfirst($type) . 'Family';
-    }
-
-    private function editFamily(Object $family, array $parsedBody): void
-    {
-        $family->setLabel($parsedBody['label']);
-        $family->setDisplay($parsedBody['display']);
-        $this->memf->getMetaEntityManager()->flush();
-    }
-}
diff --git a/src/Action/Meta/FamilyListAction.php b/src/Action/Meta/FamilyListAction.php
deleted file mode 100644
index 8237225..0000000
--- a/src/Action/Meta/FamilyListAction.php
+++ /dev/null
@@ -1,112 +0,0 @@
-<?php declare(strict_types=1);
-/*
- * This file is part of ANIS SERVER API.
- *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-namespace App\Action\Meta;
-
-use Psr\Log\LoggerInterface;
-use Psr\Http\Message\ServerRequestInterface as Request;
-use Psr\Http\Message\ResponseInterface as Response;
-
-use App\Utils\ActionTrait;
-use App\Utils\MetaEntityManagerFactory;
-
-final class FamilyListAction
-{
-    use ActionTrait;
-
-    private $logger;
-    private $memf;
-    
-    public function __construct(LoggerInterface $logger, MetaEntityManagerFactory $memf)
-    {
-        $this->logger = $logger;
-        $this->memf = $memf;
-    }
-    
-    public function __invoke(Request $request, Response $response, array $args): Response
-    {
-        $this->logger->info('Family list action dispatched');
-
-        if ($request->isOptions()) {
-            return $response->withHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
-        }
-
-        $type = $this->verifyType($args['type']);
-
-        if (empty($type)) {
-            return $this->dispatchHttpError(
-                $response,
-                'Invalid request',
-                'Type ' . $args['type'] . ' is not defined'
-            );
-        }
-
-        // Create the doctrine Entity Manager object for the instance metamodel database
-        $this->memf->createMetaEntityManager($args['instance']);
-
-        if ($request->isGet()) {
-            $sql = 'SELECT f FROM ' . $this->getEntityClass($type) . ' f';
-            if ($type == 'dataset') {
-                $sql .= ' WHERE f.display > 0';
-            }
-            $families = $this->memf->getMetaEntityManager()->createQuery($sql)->getResult();
-            $newResponse = $response->withJson($families);
-        }
-        
-        if ($request->isPost()) {
-            $parsedBody = $request->getParsedBody();
-
-            // Vérification des champs vides
-            $fields = array('label', 'display');
-            foreach ($fields as $a) {
-                if ($this->isEmptyField($a, $parsedBody)) {
-                    return $this->dispatchHttpError(
-                        $response,
-                        'Invalid request',
-                        'Param ' . $a . ' needed to add a new family'
-                    );
-                }
-            }
-
-            $family = $this->postFamily($parsedBody, $this->getEntityClass($type));
-            $newResponse = $response->withJson($family, 201);
-        }
-
-        return $newResponse;
-    }
-
-    private function verifyType(string $type): string
-    {
-        $t = strtolower($type);
-
-        if ($t == 'dataset' || $t == 'output' || $t == 'criteria') {
-            return $t;
-        } else {
-            return '';
-        }
-    }
-
-    private function getEntityClass(string $type): string
-    {
-        return 'App\Entity\Metamodel\\' . ucfirst($type) . 'Family';
-    }
-
-    private function postFamily(array $parsedBody, string $class): Object
-    {
-        $family = new $class;
-        $family->setLabel($parsedBody['label']);
-        $family->setDisplay($parsedBody['display']);
-
-        $this->memf->getMetaEntityManager()->persist($family);
-        $this->memf->getMetaEntityManager()->flush();
-
-        return $family;
-    }
-}
diff --git a/src/Action/Meta/FileAction.php b/src/Action/Meta/FileAction.php
deleted file mode 100644
index f1c2c23..0000000
--- a/src/Action/Meta/FileAction.php
+++ /dev/null
@@ -1,93 +0,0 @@
-<?php declare(strict_types=1);
-/*
- * This file is part of ANIS SERVER API.
- *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-namespace App\Action\Meta;
-
-use Psr\Log\LoggerInterface;
-use Psr\Http\Message\ServerRequestInterface as Request;
-use Psr\Http\Message\ResponseInterface as Response;
-
-use App\Utils\ActionTrait;
-use App\Utils\MetaEntityManagerFactory;
-use App\Entity\Metamodel\File;
-
-final class FileAction
-{
-    use ActionTrait;
-
-    private $logger;
-    private $memf;
-    
-    public function __construct(LoggerInterface $logger, MetaEntityManagerFactory $memf)
-    {
-        $this->logger = $logger;
-        $this->memf = $memf;
-    }
-    
-    public function __invoke(Request $request, Response $response, array $args): Response
-    {
-        $this->logger->info('File action dispatched');
-
-        if ($request->isOptions()) {
-            return $response->withHeader('Access-Control-Allow-Methods', 'GET, PUT, DELETE, OPTIONS');
-        }
-
-        $file = $this->memf->getMetaEntityManager()->find('App\Entity\Metamodel\File', $args['id']);
-
-        if (is_null($file)) {
-            return $this->dispatchHttpError(
-                $response,
-                'Invalid request',
-                'File with id ' . $args['id'] . ' is not found'
-            )->withStatus(404);
-        }
-
-        if ($request->isGet()) {
-            $newResponse = $response->withJson($file);
-        }
-        
-        if ($request->isPut()) {
-            $parsedBody = $request->getParsedBody();
-
-            // Vérification des champs vides
-            foreach (array('label', 'file_loc', 'type', 'display', 'visible') as $a) {
-                if ($this->isEmptyField($a, $parsedBody)) {
-                    return $this->dispatchHttpError(
-                        $response,
-                        'Invalid request',
-                        'Param ' . $a . ' needed to edit the file'
-                    );
-                }
-            }
-
-            $this->editFile($file, $parsedBody);
-            $newResponse = $response->withJson($file);
-        }
-        
-        if ($request->isDelete()) {
-            $id = $file->getId();
-            $this->memf->getMetaEntityManager()->remove($file);
-            $this->memf->getMetaEntityManager()->flush();
-            $newResponse = $response->withJson(array('message' => 'File with id ' . $id . ' is removed!'));
-        }
-
-        return $newResponse;
-    }
-
-    private function editFile(File $file, array $parsedBody): void
-    {
-        $file->setLabel($parsedBody['label']);
-        $file->setFileLoc($parsedBody['file_loc']);
-        $file->setType($parsedBody['type']);
-        $file->setDisplay($parsedBody['display']);
-        $file->setVisible($parsedBody['visible']);
-        $this->memf->getMetaEntityManager()->flush();
-    }
-}
diff --git a/src/Action/Meta/FileListAction.php b/src/Action/Meta/FileListAction.php
deleted file mode 100644
index 39efba3..0000000
--- a/src/Action/Meta/FileListAction.php
+++ /dev/null
@@ -1,105 +0,0 @@
-<?php declare(strict_types=1);
-/*
- * This file is part of ANIS SERVER API.
- *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-namespace App\Action\Meta;
-
-use Psr\Log\LoggerInterface;
-use Psr\Http\Message\ServerRequestInterface as Request;
-use Psr\Http\Message\ResponseInterface as Response;
-
-use App\Utils\ActionTrait;
-use App\Utils\MetaEntityManagerFactory;
-use App\Entity\Metamodel\File;
-use App\Entity\Metamodel\Dataset;
-
-final class FileListAction
-{
-    use ActionTrait;
-
-    private $logger;
-    private $memf;
-    
-    public function __construct(LoggerInterface $logger, MetaEntityManagerFactory $memf)
-    {
-        $this->logger = $logger;
-        $this->memf = $memf;
-    }
-
-    public function __invoke(Request $request, Response $response, array $args): Response
-    {
-        $this->logger->info('File list action dispatched');
-
-        if ($request->isOptions()) {
-            return $response->withHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
-        }
-
-        // Create the doctrine Entity Manager object for the instance metamodel database
-        $this->memf->createMetaEntityManager($args['instance']);
-
-        $dataset = $this->memf->getMetaEntityManager()->find('App\Entity\Metamodel\Dataset', $args['name']);
-        if (array_key_exists('type', $request->getQueryParams())) {
-            $type = $request->getQueryParams()['type'];
-        }
-
-        if (is_null($dataset)) {
-            return $this->dispatchHttpError(
-                $response,
-                'Invalid request',
-                'Dataset with name ' . $args['name'] . ' is not found'
-            )->withStatus(404);
-        }
-
-        if ($request->isGet()) {
-            $types = array(
-                'dataset' => $dataset
-            );
-            if (isset($type)) {
-                $types['type'] = $type;
-            }
-            $files = $this->memf->getMetaEntityManager()->getRepository('App\Entity\Metamodel\File')->findBy($types);
-            $newResponse = $response->withJson($files);
-        }
-        
-        if ($request->isPost()) {
-            $parsedBody = $request->getParsedBody();
-
-            // Vérification des champs vides
-            foreach (array('label', 'file_loc', 'type', 'display', 'visible') as $a) {
-                if ($this->isEmptyField($a, $parsedBody)) {
-                    return $this->dispatchHttpError(
-                        $response,
-                        'Invalid request',
-                        'Param ' . $a . ' needed to add a new file'
-                    );
-                }
-            }
-
-            $file = $this->postFile($parsedBody, $dataset);
-            $newResponse = $response->withJson($file, 201);
-        }
-
-        return $newResponse;
-    }
-
-    private function postFile(array $parsedBody, Dataset $dataset): File
-    {
-        $file = new File($dataset);
-        $file->setLabel($parsedBody['label']);
-        $file->setFileLoc($parsedBody['file_loc']);
-        $file->setType($parsedBody['type']);
-        $file->setDisplay($parsedBody['display']);
-        $file->setVisible($parsedBody['visible']);
-
-        $this->memf->getMetaEntityManager()->persist($file);
-        $this->memf->getMetaEntityManager()->flush();
-
-        return $file;
-    }
-}
diff --git a/src/Action/Meta/FileProxyAction.php b/src/Action/Meta/FileProxyAction.php
deleted file mode 100644
index 0fc847a..0000000
--- a/src/Action/Meta/FileProxyAction.php
+++ /dev/null
@@ -1,71 +0,0 @@
-<?php declare(strict_types=1);
-/*
- * This file is part of ANIS SERVER API.
- *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-namespace App\Action\Meta;
-
-use Psr\Log\LoggerInterface;
-use Psr\Http\Message\ServerRequestInterface as Request;
-use Psr\Http\Message\ResponseInterface as Response;
-
-use App\Utils\ActionTrait;
-use App\Utils\MetaEntityManagerFactory;
-
-final class FileProxyAction
-{
-    use ActionTrait;
-
-    private $logger;
-    private $memf;
-
-    public function __construct(LoggerInterface $logger, MetaEntityManagerFactory $memf)
-    {
-        $this->logger = $logger;
-        $this->memf = $memf;
-    }
-
-    public function __invoke(Request $request, Response $response, array $args): Response
-    {
-        $this->logger->info('File proxy action dispatched');
-
-        if ($request->isOptions()) {
-            return $response->withHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
-        }
-
-        // Create the doctrine Entity Manager object for the instance metamodel database
-        $this->memf->createMetaEntityManager($args['instance']);
-
-        $dataset = $this->em->find('App\Entity\Metamodel\Dataset', $args['name']);
-        if (is_null($dataset)) {
-            return $this->dispatchHttpError(
-                $response,
-                'Invalid request',
-                'Dataset with name ' . $args['name'] . ' is not found'
-            )->withStatus(404);
-        }
-
-        $path = $args['path'];
-
-        if ($request->isGet()) {
-            $dataPath = rtrim($dataset->getDataPath(), "\\/" . DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
-            $url = $dataPath . $path;
-            if (!file_exists($url)) {
-                return $this->dispatchHttpError(
-                    $response,
-                    'Invalid request',
-                    'File with dataset name ' . $args['name'] . ' and path ' . $args['path'] . ' is not found'
-                )->withStatus(404);
-            }
-            $mime = mime_content_type($url);
-            $newResponse = $response->withHeader('Content-type', $mime)->write(file_get_contents($url));
-        }
-
-        return $newResponse;
-    }
-}
diff --git a/src/Action/Meta/GroupAction.php b/src/Action/Meta/GroupAction.php
deleted file mode 100644
index 1f699bc..0000000
--- a/src/Action/Meta/GroupAction.php
+++ /dev/null
@@ -1,92 +0,0 @@
-<?php declare(strict_types=1);
-/*
- * This file is part of ANIS SERVER API.
- *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-namespace App\Action\Meta;
-
-use Psr\Log\LoggerInterface;
-use Psr\Http\Message\ServerRequestInterface as Request;
-use Psr\Http\Message\ResponseInterface as Response;
-
-use App\Utils\ActionTrait;
-use App\Utils\MetaEntityManagerFactory;
-use App\Entity\Metamodel\Group;
-
-final class GroupAction
-{
-    use ActionTrait;
-
-    private $logger;
-    private $memf;
-    
-    public function __construct(LoggerInterface $logger, MetaEntityManagerFactory $memf)
-    {
-        $this->logger = $logger;
-        $this->memf = $memf;
-    }
-    
-    public function __invoke(Request $request, Response $response, array $args): Response
-    {
-        $this->logger->info('Group action dispatched');
-
-        if ($request->isOptions()) {
-            return $response->withHeader('Access-Control-Allow-Methods', 'GET, PUT, DELETE, OPTIONS');
-        }
-
-        // Create the doctrine Entity Manager object for the instance metamodel database
-        $this->memf->createMetaEntityManager($args['instance']);
-
-        $group = $this->memf->getMetaEntityManager()->find('App\Entity\Metamodel\Group', $args['id']);
-
-        if (is_null($group)) {
-            return $this->dispatchHttpError(
-                $response,
-                'Invalid request',
-                'Group with id ' . $args['id'] . ' is not found'
-            )->withStatus(404);
-        }
-
-        if ($request->isGet()) {
-            $newResponse = $response->withJson($group);
-        }
-        
-        if ($request->isPut()) {
-            $parsedBody = $request->getParsedBody();
-
-            // Vérification des champs vides
-            foreach (array('label') as $a) {
-                if ($this->isEmptyField($a, $parsedBody)) {
-                    return $this->dispatchHttpError(
-                        $response,
-                        'Invalid request',
-                        'Param ' . $a . ' needed to edit the group'
-                    );
-                }
-            }
-
-            $this->editGroup($group, $parsedBody);
-            $newResponse = $response->withJson($group);
-        }
-        
-        if ($request->isDelete()) {
-            $id = $group->getId();
-            $this->memf->getMetaEntityManager()->remove($group);
-            $this->memf->getMetaEntityManager()->flush();
-            $newResponse = $response->withJson(array('message' => 'Group with id ' . $id . ' is removed!'));
-        }
-
-        return $newResponse;
-    }
-
-    private function editGroup(Group $group, array $parsedBody): void
-    {
-        $group->setLabel($parsedBody['label']);
-        $this->memf->getMetaEntityManager()->flush();
-    }
-}
diff --git a/src/Action/Meta/GroupListAction.php b/src/Action/Meta/GroupListAction.php
deleted file mode 100644
index c5f2f61..0000000
--- a/src/Action/Meta/GroupListAction.php
+++ /dev/null
@@ -1,81 +0,0 @@
-<?php declare(strict_types=1);
-/*
- * This file is part of ANIS SERVER API.
- *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-namespace App\Action\Meta;
-
-use Psr\Log\LoggerInterface;
-use Psr\Http\Message\ServerRequestInterface as Request;
-use Psr\Http\Message\ResponseInterface as Response;
-
-use App\Utils\ActionTrait;
-use App\Utils\MetaEntityManagerFactory;
-use App\Entity\Metamodel\Group;
-
-final class GroupListAction
-{
-    use ActionTrait;
-
-    private $logger;
-    private $memf;
-    
-    public function __construct(LoggerInterface $logger, MetaEntityManagerFactory $memf)
-    {
-        $this->logger = $logger;
-        $this->memf = $memf;
-    }
-    
-    public function __invoke(Request $request, Response $response, array $args): Response
-    {
-        $this->logger->info('Group list action dispatched');
-
-        if ($request->isOptions()) {
-            return $response->withHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
-        }
-
-        // Create the doctrine Entity Manager object for the instance metamodel database
-        $this->memf->createMetaEntityManager($args['instance']);
-
-        if ($request->isGet()) {
-            $groups = $this->memf->getMetaEntityManager()->getRepository('App\Entity\Metamodel\Group')->findAll();
-            $newResponse = $response->withJson($groups);
-        }
-        
-        if ($request->isPost()) {
-            $parsedBody = $request->getParsedBody();
-
-            // Vérification des champs vides
-            foreach (array('label') as $a) {
-                if ($this->isEmptyField($a, $parsedBody)) {
-                    return $this->dispatchHttpError(
-                        $response,
-                        'Invalid request',
-                        'Param ' . $a . ' needed to add a new group'
-                    );
-                }
-            }
-
-            $group = $this->postGroup($parsedBody);
-            $newResponse = $response->withJson($group, 201);
-        }
-
-        return $newResponse;
-    }
-
-    private function postGroup(array $parsedBody): Group
-    {
-        $group = new Group();
-        $group->setLabel($parsedBody['label']);
-
-        $this->memf->getMetaEntityManager()->persist($group);
-        $this->memf->getMetaEntityManager()->flush();
-
-        return $group;
-    }
-}
diff --git a/src/Action/Meta/OutputCategoryAction.php b/src/Action/Meta/OutputCategoryAction.php
deleted file mode 100644
index f9c5003..0000000
--- a/src/Action/Meta/OutputCategoryAction.php
+++ /dev/null
@@ -1,160 +0,0 @@
-<?php declare(strict_types=1);
-/*
- * This file is part of ANIS SERVER API.
- *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-namespace App\Action\Meta;
-
-use Psr\Log\LoggerInterface;
-use Psr\Http\Message\ServerRequestInterface as Request;
-use Psr\Http\Message\ResponseInterface as Response;
-
-use App\Utils\ActionTrait;
-use App\Utils\MetaEntityManagerFactory;
-use App\Entity\Metamodel\OutputCategory;
-use App\Entity\Metamodel\OutputFamily;
-
-/**
- * Route: /metadata/output-category/{id}
- * {id}: Output category id
- *
- * This action is used to manage one output category
- *
- * @author François Agneray <francois.agneray@lam.fr>
- * @package App\Action\Meta
- */
-final class OutputCategoryAction
-{
-    use ActionTrait;
-
-    /**
-     * The logger interface is the central access point to log anis information
-     *
-     * @var LoggerInterface
-     */
-    private $logger;
-
-    /**
-     * The MetaEntityManagerFactory create the doctrine entity manager for the metamodel database.
-     * The entity manager is the central access point to Doctrine ORM functionality (metadata database)
-     *
-     * @var MetaEntityManagerFactory
-     */
-    private $memf;
-    
-    /**
-     * Create the classe before call __invoke to execute the action
-     *
-     * @param LoggerInterface        $logger
-     * @param MetaEntityManagerFactory $memf
-     */
-    public function __construct(LoggerInterface $logger, MetaEntityManagerFactory $memf)
-    {
-        $this->logger = $logger;
-        $this->memf = $memf;
-    }
-    
-    /**
-     * `GET` Returns the output category found
-     * `PUT` Full update the output category and returns the new version
-     * `DELETE` Delete the output category found and returns a confirmation message
-     *
-     * @param ServerRequestInterface $request   This object contains the HTTP request
-     * @param ResponseInterface      $response  This object represents the HTTP response
-     * @param string[]               $args      This table contains information transmitted in the URL (see routes.php)
-     *
-     * @return ResponseInterface
-     */
-    public function __invoke(Request $request, Response $response, $args): Response
-    {
-        $this->logger->info('Output category action dispatched');
-
-        if ($request->isOptions()) {
-            return $response->withHeader('Access-Control-Allow-Methods', 'GET, PUT, DELETE, OPTIONS');
-        }
-
-        // Create the doctrine Entity Manager object for the instance metamodel database
-        $this->memf->createMetaEntityManager($args['instance']);
-
-        // Search the correct output category with primary key (id)
-        $outputCategory = $this->memf->getMetaEntityManager()->find('App\Entity\Metamodel\OutputCategory', $args['id']);
-
-        // If output category is not found 404
-        if (is_null($outputCategory)) {
-            return $this->dispatchHttpError(
-                $response,
-                'Invalid request',
-                'Output category with id ' . $args['id'] . ' is not found'
-            )->withStatus(404);
-        }
-
-        if ($request->isGet()) {
-            $newResponse = $response->withJson($outputCategory);
-        }
-        
-        if ($request->isPut()) {
-            $parsedBody = $request->getParsedBody();
-
-            // If mandatories empty fields 400
-            foreach (array('label', 'display', 'id_output_family') as $a) {
-                if ($this->isEmptyField($a, $parsedBody)) {
-                    return $this->dispatchHttpError(
-                        $response,
-                        'Invalid request',
-                        'Param ' . $a . ' needed to edit the output category'
-                    );
-                }
-            }
-
-            // Search the correct output family with primary key
-            $outputFamily = $this->memf->getMetaEntityManager()->find(
-                'App\Entity\Metamodel\OutputFamily',
-                $parsedBody['id_output_family']
-            );
-
-            // Output family is mandatory. If is null 404
-            if (is_null($outputFamily)) {
-                return $this->dispatchHttpError(
-                    $response,
-                    'Invalid request',
-                    'Output family with id ' . $parsedBody['id_output_family'] . ' is not found'
-                )->withStatus(404);
-            }
-
-            $this->editOutputCategory($outputCategory, $parsedBody, $outputFamily);
-            $newResponse = $response->withJson($outputCategory);
-        }
-        
-        if ($request->isDelete()) {
-            $id = $outputCategory->getId();
-            $this->memf->getMetaEntityManager()->remove($outputCategory);
-            $this->memf->getMetaEntityManager()->flush();
-            $newResponse = $response->withJson(array('message' => 'Output category with id ' . $id . ' is removed!'));
-        }
-
-        return $newResponse;
-    }
-
-    /**
-     * Update output category object with setters
-     *
-     * @param OutputCategory $outputCategory The output category to update
-     * @param string[]       $parsedBody     Contains the new values ​​of the output category sent by the user
-     * @param OutputFamily   $outputFamily   Contains the output family doctrine object to set to the output category
-     */
-    private function editOutputCategory(
-        OutputCategory $outputCategory,
-        array $parsedBody,
-        OutputFamily $outputFamily
-    ): void {
-        $outputCategory->setLabel($parsedBody['label']);
-        $outputCategory->setDisplay($parsedBody['display']);
-        $outputCategory->setOutputFamily($outputFamily);
-        $this->memf->getMetaEntityManager()->flush();
-    }
-}
diff --git a/src/Action/Meta/OutputCategoryListAction.php b/src/Action/Meta/OutputCategoryListAction.php
deleted file mode 100644
index f172fbd..0000000
--- a/src/Action/Meta/OutputCategoryListAction.php
+++ /dev/null
@@ -1,99 +0,0 @@
-<?php declare(strict_types=1);
-/*
- * This file is part of ANIS SERVER API.
- *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-namespace App\Action\Meta;
-
-use Psr\Log\LoggerInterface;
-use Psr\Http\Message\ServerRequestInterface as Request;
-use Psr\Http\Message\ResponseInterface as Response;
-
-use App\Utils\ActionTrait;
-use App\Utils\MetaEntityManagerFactory;
-use App\Entity\Metamodel\OutputCategory;
-use App\Entity\Metamodel\OutputFamily;
-
-final class OutputCategoryListAction
-{
-    use ActionTrait;
-
-    private $logger;
-    private $memf;
-    
-    public function __construct(LoggerInterface $logger, MetaEntityManagerFactory $memf)
-    {
-        $this->logger = $logger;
-        $this->memf = $memf;
-    }
-    
-    public function __invoke(Request $request, Response $response, array $args): Response
-    {
-        $this->logger->info('Output category list action dispatched');
-
-        if ($request->isOptions()) {
-            return $response->withHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
-        }
-
-        // Create the doctrine Entity Manager object for the instance metamodel database
-        $this->memf->createMetaEntityManager($args['instance']);
-
-        if ($request->isGet()) {
-            $outputCategories = $this->memf->getMetaEntityManager()->getRepository(
-                'App\Entity\Metamodel\OutputCategory'
-            )->findAll();
-            $newResponse = $response->withJson($outputCategories);
-        }
-        
-        if ($request->isPost()) {
-            $parsedBody = $request->getParsedBody();
-
-            // Vérification des champs vides
-            foreach (array('label', 'display', 'id_output_family') as $a) {
-                if ($this->isEmptyField($a, $parsedBody)) {
-                    return $this->dispatchHttpError(
-                        $response,
-                        'Invalid request',
-                        'Param ' . $a . ' needed to add a new output category'
-                    );
-                }
-            }
-
-            // Vérification de l'existence de la output family
-            $outputFamily = $this->memf->getMetaEntityManager()->find(
-                'App\Entity\Metamodel\OutputFamily',
-                $parsedBody['id_output_family']
-            );
-            if (is_null($outputFamily)) {
-                return $this->dispatchHttpError(
-                    $response,
-                    'Invalid request',
-                    'Output family with id ' . $parsedBody['id_output_family'] . ' is not found'
-                )->withStatus(404);
-            }
-
-            $outputCategory = $this->postOutputCategory($parsedBody, $outputFamily);
-            $newResponse = $response->withJson($outputCategory, 201);
-        }
-
-        return $newResponse;
-    }
-
-    private function postOutputCategory(array $parsedBody, OutputFamily $outputFamily): OutputCategory
-    {
-        $outputCategory = new OutputCategory();
-        $outputCategory->setLabel($parsedBody['label']);
-        $outputCategory->setDisplay($parsedBody['display']);
-        $outputCategory->setOutputFamily($outputFamily);
-
-        $this->memf->getMetaEntityManager()->persist($outputCategory);
-        $this->memf->getMetaEntityManager()->flush();
-
-        return $outputCategory;
-    }
-}
diff --git a/src/Action/Meta/ProjectAction.php b/src/Action/Meta/ProjectAction.php
deleted file mode 100644
index ec2d3b8..0000000
--- a/src/Action/Meta/ProjectAction.php
+++ /dev/null
@@ -1,159 +0,0 @@
-<?php declare(strict_types=1);
-/*
- * This file is part of ANIS SERVER API.
- *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-namespace App\Action\Meta;
-
-use Psr\Log\LoggerInterface;
-use Psr\Http\Message\ServerRequestInterface as Request;
-use Psr\Http\Message\ResponseInterface as Response;
-
-use App\Utils\ActionTrait;
-use App\Utils\MetaEntityManagerFactory;
-use App\Entity\Metamodel\Project;
-use App\Entity\Metamodel\Database;
-
-/**
- * Route: /metadata/project/{name}
- * {name}: Project name
- *
- * This action is used to manage one project
- *
- * @author François Agneray <francois.agneray@lam.fr>
- * @package App\Action\Meta
- */
-final class ProjectAction
-{
-    use ActionTrait;
-
-    /**
-     * The logger interface is the central access point to log anis information
-     *
-     * @var LoggerInterface
-     */
-    private $logger;
-
-    /**
-     * The MetaEntityManagerFactory create the doctrine entity manager for the metamodel database.
-     * The entity manager is the central access point to Doctrine ORM functionality (metadata database)
-     *
-     * @var MetaEntityManagerFactory
-     */
-    private $memf;
-    
-    /**
-     * Create the classe before call __invoke to execute the action
-     *
-     * @param LoggerInterface          $logger
-     * @param MetaEntityManagerFactory $memf
-     */
-    public function __construct(LoggerInterface $logger, MetaEntityManagerFactory $memf)
-    {
-        $this->logger = $logger;
-        $this->memf = $memf;
-    }
-    
-    /**
-     * `GET` Returns the project found
-     * `PUT` Full update the project and returns the new version
-     * `DELETE` Delete the project found and return a confirmation message
-     *
-     * @param ServerRequestInterface $request   This object contains the HTTP request
-     * @param ResponseInterface      $response  This object represents the HTTP response
-     * @param string[]               $args      This table contains information transmitted in the URL (see routes.php)
-     *
-     * @return ResponseInterface
-     */
-    public function __invoke(Request $request, Response $response, array $args): Response
-    {
-        $this->logger->info('Project action dispatched');
-
-        if ($request->isOptions()) {
-            return $response->withHeader('Access-Control-Allow-Methods', 'GET, PUT, DELETE, OPTIONS');
-        }
-
-        // Create the doctrine Entity Manager object for the instance metamodel database
-        $this->memf->createMetaEntityManager($args['instance']);
-
-        // Search the correct project with primary key
-        $project = $this->memf->getMetaEntityManager()->find('App\Entity\Metamodel\Project', $args['name']);
-
-        // If project is not found 404
-        if (is_null($project)) {
-            return $this->dispatchHttpError(
-                $response,
-                'Invalid request',
-                'Project with id ' . $args['name'] . ' is not found'
-            )->withStatus(404);
-        }
-
-        if ($request->isGet()) {
-            $newResponse = $response->withJson($project);
-        }
-
-        if ($request->isPut()) {
-            $parsedBody = $request->getParsedBody();
-            
-            // If mandatories empty fields 400
-            foreach (array('label', 'description', 'link', 'manager', 'id_database') as $a) {
-                if ($this->isEmptyField($a, $parsedBody)) {
-                    return $this->dispatchHttpError(
-                        $response,
-                        'Invalid request',
-                        'Param ' . $a . ' needed to edit the project'
-                    );
-                }
-            }
-
-            // Search the correct database with primary key
-            $database = $this->memf->getMetaEntityManager()->find(
-                'App\Entity\Metamodel\Database',
-                $parsedBody['id_database']
-            );
-
-            // Database is mandatory. If is null 404
-            if (is_null($database)) {
-                return $this->dispatchHttpError(
-                    $response,
-                    'Invalid request',
-                    'Database with id ' . $parsedBody['id_database'] . ' is not found'
-                )->withStatus(404);
-            }
-
-            $this->editProject($project, $parsedBody, $database);
-            $newResponse = $response->withJson($project);
-        }
-        
-        if ($request->isDelete()) {
-            $name = $project->getName();
-            $this->memf->getMetaEntityManager()->remove($project);
-            $this->memf->getMetaEntityManager()->flush();
-            $newResponse = $response->withJson(array('message' => 'Project ' . $name . ' is removed!'));
-        }
-
-        return $newResponse;
-    }
-
-    /**
-     * Update project object with setters
-     *
-     * @param Project       $project    The project to update
-     * @param string[]      $parsedBody Contains the new values ​​of the project sent by the user
-     * @param Database      $database   Contains the database doctrine object to set to the project
-     */
-    private function editProject(Project $project, array $parsedBody, Database $database): void
-    {
-        $project->setLabel($parsedBody['label']);
-        $project->setDescription($parsedBody['description']);
-        $project->setLink($parsedBody['link']);
-        $project->setManager($parsedBody['manager']);
-        $project->setDatabase($database);
-        $this->memf->getMetaEntityManager()->flush();
-    }
-}
diff --git a/src/Action/Meta/ProjectListAction.php b/src/Action/Meta/ProjectListAction.php
deleted file mode 100644
index 5721870..0000000
--- a/src/Action/Meta/ProjectListAction.php
+++ /dev/null
@@ -1,98 +0,0 @@
-<?php declare(strict_types=1);
-/*
- * This file is part of ANIS SERVER API.
- *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-namespace App\Action\Meta;
-
-use Psr\Log\LoggerInterface;
-use Psr\Http\Message\ServerRequestInterface as Request;
-use Psr\Http\Message\ResponseInterface as Response;
-
-use App\Utils\ActionTrait;
-use App\Utils\MetaEntityManagerFactory;
-use App\Entity\Metamodel\Project;
-use App\Entity\Metamodel\Database;
-
-final class ProjectListAction
-{
-    use ActionTrait;
-
-    private $logger;
-    private $memf;
-    
-    public function __construct(LoggerInterface $logger, MetaEntityManagerFactory $memf)
-    {
-        $this->logger = $logger;
-        $this->memf = $memf;
-    }
-    
-    public function __invoke(Request $request, Response $response, array $args): Response
-    {
-        $this->logger->info('Project list action dispatched');
-
-        if ($request->isOptions()) {
-            return $response->withHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
-        }
-
-        $this->memf->createMetaEntityManager($args['instance']);
-
-        if ($request->isGet()) {
-            $projects = $this->memf->getMetaEntityManager()->getRepository('App\Entity\Metamodel\Project')->findAll();
-            $newResponse = $response->withJson($projects, 200, JSON_UNESCAPED_SLASHES);
-        }
-        
-        if ($request->isPost()) {
-            $parsedBody = $request->getParsedBody();
-
-            // Vérification des champs vides
-            foreach (array('name', 'label', 'description', 'link', 'manager', 'id_database') as $a) {
-                if ($this->isEmptyField($a, $parsedBody)) {
-                    return $this->dispatchHttpError(
-                        $response,
-                        'Invalid request',
-                        'Param ' . $a . ' needed to add a new project'
-                    );
-                }
-            }
-
-            // Vérification de l'existence de la database
-            $database = $this->memf->getMetaEntityManager()->find(
-                'App\Entity\Metamodel\Database',
-                $parsedBody['id_database']
-            );
-            if (is_null($database)) {
-                return $this->dispatchHttpError(
-                    $response,
-                    'Invalid request',
-                    'Database with id ' . $parsedBody['id_database'] . ' is not found'
-                )->withStatus(404);
-            }
-
-            $project = $this->postProject($parsedBody, $database);
-            $newResponse = $response->withJson($project, 201, JSON_UNESCAPED_SLASHES);
-        }
-
-        return $newResponse;
-    }
-
-    private function postProject(array $parsedBody, Database $database): Project
-    {
-        $project = new Project($parsedBody['name']);
-        $project->setLabel($parsedBody['label']);
-        $project->setDescription($parsedBody['description']);
-        $project->setLink($parsedBody['link']);
-        $project->setManager($parsedBody['manager']);
-        $project->setDatabase($database);
-
-        $this->memf->getMetaEntityManager()->persist($project);
-        $this->memf->getMetaEntityManager()->flush();
-
-        return $project;
-    }
-}
diff --git a/src/Action/Meta/TableListAction.php b/src/Action/Meta/TableListAction.php
deleted file mode 100644
index 7fdcb6b..0000000
--- a/src/Action/Meta/TableListAction.php
+++ /dev/null
@@ -1,90 +0,0 @@
-<?php declare(strict_types=1);
-/*
- * This file is part of ANIS SERVER API.
- *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-namespace App\Action\Meta;
-
-use Psr\Log\LoggerInterface;
-use Psr\Http\Message\ServerRequestInterface as Request;
-use Psr\Http\Message\ResponseInterface as Response;
-
-use App\Utils\ActionTrait;
-use App\Utils\MetaEntityManagerFactory;
-use App\Utils\DBALConnectionFactory;
-
-final class TableListAction
-{
-    use ActionTrait;
-
-    private $logger;
-    private $memf;
-    private $dcf;
-
-    /**
-     * The encryption key used by anis to encrypt and decrypt sensitive data like passwords
-     * This key is provided by configuration (see the config file)
-     *
-     * @var string
-     */
-    private $encryptionKey;
-    
-    public function __construct(
-        LoggerInterface $logger,
-        MetaEntityManagerFactory $memf,
-        DBALConnectionFactory $dcf,
-        string $encryptionKey
-    ) {
-        $this->logger = $logger;
-        $this->memf = $memf;
-        $this->dcf = $dcf;
-        $this->encryptionKey = $encryptionKey;
-    }
-    
-    public function __invoke(Request $request, Response $response, array $args): Response
-    {
-        $this->logger->info('Table list action dispatched');
-
-        if ($request->isOptions()) {
-            return $response->withHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
-        }
-
-        $this->memf->createMetaEntityManager($args['instance']);
-        $database = $this->memf->getMetaEntityManager()->find('App\Entity\Metamodel\Database', $args['id']);
-
-        if (is_null($database)) {
-            return $this->dispatchHttpError(
-                $response,
-                'Invalid request',
-                'Database with id ' . $args['id'] . ' is not found'
-            )->withStatus(404);
-        }
-
-        if ($request->isGet()) {
-            $decryptedPassword = $this->decryptData($database->getPassword());
-            $connection = $this->dcf->create($database, $decryptedPassword);
-            $sm = $connection->getSchemaManager();
-            $f = function ($o) {
-                return $o->getName();
-            };
-            $tables = array_merge(array_map($f, $sm->listTables()), $this->getViews($sm->listViews()));
-            $newReponse = $response->withJson($tables);
-        }
-
-        return $newReponse;
-    }
-
-    private function getViews($listViews)
-    {
-        $views = array();
-        foreach ($listViews as $v) {
-            $views[] = $v->getName();
-        }
-        return $views;
-    }
-}
diff --git a/src/Action/OutputCategoryAction.php b/src/Action/OutputCategoryAction.php
new file mode 100644
index 0000000..273c509
--- /dev/null
+++ b/src/Action/OutputCategoryAction.php
@@ -0,0 +1,112 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Action;
+
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Psr\Http\Message\ResponseInterface as Response;
+use Slim\Exception\HttpBadRequestException;
+use Slim\Exception\HttpNotFoundException;
+use App\Entity\OutputCategory;
+use App\Entity\OutputFamily;
+
+final class OutputCategoryAction extends AbstractAction
+{
+    /**
+     * `GET` Returns the output category found
+     * `PUT` Full update the output category and returns the new version
+     * `DELETE` Delete the output category found and return a confirmation message
+     *
+     * @param  ServerRequestInterface $request  PSR-7 This object represents the HTTP request
+     * @param  ResponseInterface      $response PSR-7 This object represents the HTTP response
+     * @param  string[]               $args     This table contains information transmitted in the URL (see routes.php)
+     *
+     * @return ResponseInterface
+     */
+    public function __invoke(Request $request, Response $response, array $args): Response
+    {
+        if ($request->getMethod() === OPTIONS) {
+            return $response->withHeader('Access-Control-Allow-Methods', 'GET, PUT, DELETE, OPTIONS');
+        }
+
+        // Search the correct output category with primary key (id)
+        $outputCategory = $this->em->find('App\Entity\OutputCategory', $args['id']);
+
+        // If output category is not found 404
+        if (is_null($outputCategory)) {
+            throw new HttpNotFoundException(
+                $request,
+                'Output category with id ' . $args['id'] . ' is not found'
+            );
+        }
+
+        if ($request->getMethod() === GET) {
+            $payload = json_encode($outputCategory);
+        }
+
+        if ($request->getMethod() === PUT) {
+            $parsedBody = $request->getParsedBody();
+
+            // If mandatories empty fields 400
+            foreach (array('label', 'display', 'id_output_family') as $a) {
+                if ($this->isEmptyField($a, $parsedBody)) {
+                    throw new HttpBadRequestException(
+                        $request,
+                        'Param ' . $a . ' needed to edit the output category'
+                    );
+                }
+            }
+
+            // Search the correct output family with primary key
+            $outputFamily = $this->em->find('App\Entity\OutputFamily', $parsedBody['id_output_family']);
+
+            // Output family is mandatory. If is null 400
+            if (is_null($outputFamily)) {
+                throw new HttpBadRequestException(
+                    $request,
+                    'Output family with id ' . $parsedBody['id_output_family'] . ' is not found'
+                );
+            }
+
+            $this->editOutputCategory($outputCategory, $parsedBody, $outputFamily);
+            $payload = json_encode($outputCategory);
+        }
+
+        if ($request->getMethod() === DELETE) {
+            $id = $outputCategory->getId();
+            $this->em->remove($outputCategory);
+            $this->em->flush();
+            $payload = json_encode(array('message' => 'Output category with id ' . $id . ' is removed!'));
+        }
+
+        $response->getBody()->write($payload);
+        return $response;
+    }
+
+    /**
+     * Update output category object with setters
+     *
+     * @param OutputCategory $outputCategory The output category to update
+     * @param array          $parsedBody     Contains the new values ​​of the output category sent by the user
+     * @param OutputFamily   $outputFamily   Contains the output family doctrine object to set to the output category
+     */
+    private function editOutputCategory(
+        OutputCategory $outputCategory,
+        array $parsedBody,
+        OutputFamily $outputFamily
+    ): void {
+        $outputCategory->setLabel($parsedBody['label']);
+        $outputCategory->setDisplay($parsedBody['display']);
+        $outputCategory->setOutputFamily($outputFamily);
+        $this->em->flush();
+    }
+}
diff --git a/src/Action/OutputCategoryListAction.php b/src/Action/OutputCategoryListAction.php
new file mode 100644
index 0000000..d818322
--- /dev/null
+++ b/src/Action/OutputCategoryListAction.php
@@ -0,0 +1,87 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Action;
+
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Psr\Http\Message\ResponseInterface as Response;
+use Slim\Exception\HttpBadRequestException;
+use App\Entity\OutputCategory;
+use App\Entity\OutputFamily;
+
+final class OutputCategoryListAction extends AbstractAction
+{
+    /**
+     * `GET`  Returns a list of all output categories listed in the metamodel database
+     * `POST` Add a new output category
+     *
+     * @param  ServerRequestInterface $request  PSR-7 This object represents the HTTP request
+     * @param  ResponseInterface      $response PSR-7 This object represents the HTTP response
+     * @param  string[]               $args     This table contains information transmitted in the URL (see routes.php)
+     *
+     * @return ResponseInterface
+     */
+    public function __invoke(Request $request, Response $response, array $args): Response
+    {
+        if ($request->getMethod() === OPTIONS) {
+            return $response->withHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
+        }
+
+        if ($request->getMethod() === GET) {
+            $outputCategories = $this->em->getRepository('App\Entity\OutputCategory')->findAll();
+            $payload = json_encode($outputCategories);
+        }
+
+        if ($request->getMethod() === POST) {
+            $parsedBody = $request->getParsedBody();
+
+            // Verif mandatories fields
+            foreach (array('label', 'display', 'id_output_family') as $a) {
+                if ($this->isEmptyField($a, $parsedBody)) {
+                    throw new HttpBadRequestException(
+                        $request,
+                        'Param ' . $a . ' needed to add a new output category'
+                    );
+                }
+            }
+
+            // Output family is mandatory
+            $outputFamily = $this->em->find('App\Entity\OutputFamily', $parsedBody['id_output_family']);
+            if (is_null($outputFamily)) {
+                throw new HttpBadRequestException(
+                    $request,
+                    'Output family with id ' . $parsedBody['id_output_family'] . ' is not found'
+                );
+            }
+
+            $outputCategory = $this->postOutputCategory($parsedBody, $outputFamily);
+            $payload = json_encode($outputCategory);
+            $response = $response->withStatus(201);
+        }
+
+        $response->getBody()->write($payload);
+        return $response;
+    }
+
+    private function postOutputCategory(array $parsedBody, OutputFamily $outputFamily): OutputCategory
+    {
+        $outputCategory = new OutputCategory();
+        $outputCategory->setLabel($parsedBody['label']);
+        $outputCategory->setDisplay($parsedBody['display']);
+        $outputCategory->setOutputFamily($outputFamily);
+
+        $this->em->persist($outputCategory);
+        $this->em->flush();
+
+        return $outputCategory;
+    }
+}
diff --git a/src/Action/ProjectAction.php b/src/Action/ProjectAction.php
new file mode 100644
index 0000000..138b1cd
--- /dev/null
+++ b/src/Action/ProjectAction.php
@@ -0,0 +1,109 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Action;
+
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Psr\Http\Message\ResponseInterface as Response;
+use Slim\Exception\HttpBadRequestException;
+use Slim\Exception\HttpNotFoundException;
+use App\Entity\Database;
+use App\Entity\Project;
+
+final class ProjectAction extends AbstractAction
+{
+    /**
+     * `GET` Returns the project found
+     * `PUT` Full update the project and returns the new version
+     * `DELETE` Delete the project found and return a confirmation message
+     *
+     * @param  ServerRequestInterface $request  PSR-7 This object represents the HTTP request
+     * @param  ResponseInterface      $response PSR-7 This object represents the HTTP response
+     * @param  string[]               $args     This table contains information transmitted in the URL (see routes.php)
+     *
+     * @return ResponseInterface
+     */
+    public function __invoke(Request $request, Response $response, array $args): Response
+    {
+        if ($request->getMethod() === OPTIONS) {
+            return $response->withHeader('Access-Control-Allow-Methods', 'GET, PUT, DELETE, OPTIONS');
+        }
+
+        // Search the correct project with primary key
+        $project = $this->em->find('App\Entity\Project', $args['name']);
+
+        // If project is not found 404
+        if (is_null($project)) {
+            throw new HttpNotFoundException(
+                $request,
+                'Project with name ' . $args['name'] . ' is not found'
+            );
+        }
+
+        if ($request->getMethod() === GET) {
+            $payload = json_encode($project, JSON_UNESCAPED_SLASHES);
+        }
+
+        if ($request->getMethod() === PUT) {
+            $parsedBody = $request->getParsedBody();
+            
+            // If mandatories empty fields 400
+            foreach (array('label', 'description', 'link', 'manager', 'id_database') as $a) {
+                if ($this->isEmptyField($a, $parsedBody)) {
+                    throw new HttpBadRequestException(
+                        $request,
+                        'Param ' . $a . ' needed to edit the project'
+                    );
+                }
+            }
+
+            // Database exists ?
+            $database = $this->em->find('App\Entity\Database', $parsedBody['id_database']);
+            if (is_null($database)) {
+                throw new HttpBadRequestException(
+                    $request,
+                    'Database with id ' . $parsedBody['id_database'] . ' is not found'
+                );
+            }
+
+            $this->editProject($project, $parsedBody, $database);
+            $payload = json_encode($project, JSON_UNESCAPED_SLASHES);
+        }
+
+        if ($request->getMethod() === DELETE) {
+            $name = $project->getName();
+            $this->em->remove($project);
+            $this->em->flush();
+            $payload = json_encode(array('message' => 'Project ' . $name . ' is removed!'));
+        }
+
+        $response->getBody()->write($payload);
+        return $response;
+    }
+
+    /**
+     * Update project object with setters
+     *
+     * @param Project  $project    The project to update
+     * @param string[] $parsedBody Contains the new values ​​of the project sent by the user
+     * @param Database $database   Contains the database doctrine object to set to the project
+     */
+    private function editProject(Project $project, array $parsedBody, Database $database): void
+    {
+        $project->setLabel($parsedBody['label']);
+        $project->setDescription($parsedBody['description']);
+        $project->setLink($parsedBody['link']);
+        $project->setManager($parsedBody['manager']);
+        $project->setDatabase($database);
+        $this->em->flush();
+    }
+}
diff --git a/src/Action/ProjectListAction.php b/src/Action/ProjectListAction.php
new file mode 100644
index 0000000..b35a8b8
--- /dev/null
+++ b/src/Action/ProjectListAction.php
@@ -0,0 +1,89 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Action;
+
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Psr\Http\Message\ResponseInterface as Response;
+use Slim\Exception\HttpBadRequestException;
+use App\Entity\Database;
+use App\Entity\Project;
+
+final class ProjectListAction extends AbstractAction
+{
+    /**
+     * `GET`  Returns a list of all projects listed in the metamodel database
+     * `POST` Add a new project
+     *
+     * @param  ServerRequestInterface $request  PSR-7 This object represents the HTTP request
+     * @param  ResponseInterface      $response PSR-7 This object represents the HTTP response
+     * @param  string[]               $args     This table contains information transmitted in the URL (see routes.php)
+     *
+     * @return ResponseInterface
+     */
+    public function __invoke(Request $request, Response $response, array $args): Response
+    {
+        if ($request->getMethod() === OPTIONS) {
+            return $response->withHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
+        }
+
+        if ($request->getMethod() === GET) {
+            $projects = $this->em->getRepository('App\Entity\Project')->findAll();
+            $payload = json_encode($projects, JSON_UNESCAPED_SLASHES);
+        }
+
+        if ($request->getMethod() === POST) {
+            $parsedBody = $request->getParsedBody();
+
+            // To work this action needs information to update
+            foreach (array('name', 'label', 'description', 'link', 'manager', 'id_database') as $a) {
+                if ($this->isEmptyField($a, $parsedBody)) {
+                    throw new HttpBadRequestException(
+                        $request,
+                        'Param ' . $a . ' needed to add a new project'
+                    );
+                }
+            }
+
+            // Database exists ?
+            $database = $this->em->find('App\Entity\Database', $parsedBody['id_database']);
+            if (is_null($database)) {
+                throw new HttpBadRequestException(
+                    $request,
+                    'Database with id ' . $parsedBody['id_database'] . ' is not found'
+                );
+            }
+
+            $project = $this->postProject($parsedBody, $database);
+            $payload = json_encode($project, JSON_UNESCAPED_SLASHES);
+            $response = $response->withStatus(201);
+        }
+
+        $response->getBody()->write($payload);
+        return $response;
+    }
+
+    private function postProject(array $parsedBody, Database $database): Project
+    {
+        $project = new Project($parsedBody['name']);
+        $project->setLabel($parsedBody['label']);
+        $project->setDescription($parsedBody['description']);
+        $project->setLink($parsedBody['link']);
+        $project->setManager($parsedBody['manager']);
+        $project->setDatabase($database);
+
+        $this->em->persist($project);
+        $this->em->flush();
+
+        return $project;
+    }
+}
diff --git a/src/Action/Root/OpenApiAction.php b/src/Action/Root/OpenApiAction.php
deleted file mode 100644
index 3130e39..0000000
--- a/src/Action/Root/OpenApiAction.php
+++ /dev/null
@@ -1,62 +0,0 @@
-<?php declare(strict_types=1);
-/*
- * This file is part of ANIS SERVER API.
- *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-namespace App\Action\Root;
-
-use Psr\Log\LoggerInterface;
-use Psr\Http\Message\ServerRequestInterface as Request;
-use Psr\Http\Message\ResponseInterface as Response;
-use file_get_contents;
-
-/**
- * Route: /open-api
- *
- * This action returns the open api description for the anis service. (GET)
- *
- * @author François Agneray <francois.agneray@lam.fr>
- * @package App\Action\Root
- */
-final class OpenApiAction
-{
-    /**
-     * The logger interface is the central access point to log anis information
-     *
-     * @var LoggerInterface
-     */
-    private $logger;
-
-    /**
-     * Create the classe before call __invoke to execute the action
-     *
-     * @param LoggerInterface        $logger
-     */
-    public function __construct(LoggerInterface $logger)
-    {
-        $this->logger = $logger;
-    }
-
-    /**
-     * This action returns the open api description for the anis service
-     * The open API description is available at the root of the project (anis-server.yaml)
-     *
-     * @param ServerRequestInterface $request   This object contains the HTTP request
-     * @param ResponseInterface      $response  This object represents the HTTP response
-     * @param string[]               $args      This table contains information transmitted in the URL (see routes.php)
-     *
-     * @return ResponseInterface
-     */
-    public function __invoke(Request $request, Response $response, array $args): Response
-    {
-        $this->logger->info('Open api action dispatched');
-        $filePath = __DIR__ . '/../../../anis-server.yaml';
-        $openApi = file_get_contents($filePath);
-        return $response->write($openApi)->withHeader('Content-Type', 'text/yaml');
-    }
-}
diff --git a/src/Action/Root/RootAction.php b/src/Action/Root/RootAction.php
deleted file mode 100644
index 369c911..0000000
--- a/src/Action/Root/RootAction.php
+++ /dev/null
@@ -1,59 +0,0 @@
-<?php declare(strict_types=1);
-/*
- * This file is part of ANIS SERVER API.
- *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-namespace App\Action\Root;
-
-use Psr\Log\LoggerInterface;
-use Psr\Http\Message\ServerRequestInterface as Request;
-use Psr\Http\Message\ResponseInterface as Response;
-
-/**
- * Route: /
- *
- * With this action user can make sure that
- * the service is responding. (GET)
- *
- * @author François Agneray <francois.agneray@lam.fr>
- * @package App\Action\Root
- */
-final class RootAction
-{
-    /**
-     * The logger interface is the central access point to log anis information
-     *
-     * @var LoggerInterface
-     */
-    private $logger;
-
-    /**
-     * Create the classe before call __invoke to execute the action
-     *
-     * @param LoggerInterface        $logger
-     */
-    public function __construct(LoggerInterface $logger)
-    {
-        $this->logger = $logger;
-    }
-
-    /**
-     * This action indicates that the service is responding
-     *
-     * @param ServerRequestInterface $request   This object contains the HTTP request
-     * @param ResponseInterface      $response  This object represents the HTTP response
-     * @param string[]               $args      This table contains information transmitted in the URL (see routes.php)
-     *
-     * @return ResponseInterface
-     */
-    public function __invoke(Request $request, Response $response, array $args): Response
-    {
-        $this->logger->info('Root action dispatched');
-        return $response->withJson(array('message' => 'it works!'));
-    }
-}
diff --git a/src/Action/RootAction.php b/src/Action/RootAction.php
new file mode 100644
index 0000000..f563a7c
--- /dev/null
+++ b/src/Action/RootAction.php
@@ -0,0 +1,41 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Action;
+
+use Psr\Http\Message\ResponseInterface as Response;
+use Psr\Http\Message\ServerRequestInterface as Request;
+
+/**
+ * Root action
+ *
+ * @author François Agneray <francois.agneray@lam.fr>
+ * @package App\Action
+ */
+final class RootAction
+{
+    /**
+     * This action indicates that the service is responding
+     *
+     * @param  ServerRequestInterface $request  PSR-7 This object represents the HTTP request
+     * @param  ResponseInterface      $response PSR-7 This object represents the HTTP response
+     * @param  string[]               $args     This table contains information transmitted in the URL (see routes.php)
+     *
+     * @return ResponseInterface
+     */
+    public function __invoke(Request $request, Response $response, array $args): Response
+    {
+        $payload = json_encode(array('message' => 'it works!'));
+        $response->getBody()->write($payload);
+        return $response;
+    }
+}
diff --git a/src/Action/Search/ServiceAction.php b/src/Action/Search/ServiceAction.php
deleted file mode 100644
index 5b7ba1f..0000000
--- a/src/Action/Search/ServiceAction.php
+++ /dev/null
@@ -1,86 +0,0 @@
-<?php
-/*
- * This file is part of ANIS SERVER API.
- *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-namespace App\Action\Search;
-
-use Psr\Log\LoggerInterface;
-use Psr\Http\Message\ServerRequestInterface as Request;
-use Psr\Http\Message\ResponseInterface as Response;
-use PhpAmqpLib\Connection\AMQPStreamConnection;
-use PhpAmqpLib\Message\AMQPMessage;
-
-use App\Utils\ActionTrait;
-use App\Utils\MetaEntityManagerFactory;
-
-final class ServiceAction
-{
-    use ActionTrait;
-
-    private $logger;
-    private $memf;
-    private $amqp;
-
-    public function __construct(LoggerInterface $logger, MetaEntityManagerFactory $memf, AMQPStreamConnection $amqp)
-    {
-        $this->logger = $logger;
-        $this->memf = $memf;
-        $this->amqp = $amqp;
-    }
-
-    public function __invoke(Request $request, Response $response, array $args): Response
-    {
-        $this->logger->info('Service action dispatched');
-
-        if ($request->isOptions()) {
-            return $response->withHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
-        }
-
-        // Create the doctrine Entity Manager object for the instance metamodel database
-        $this->memf->createMetaEntityManager($args['instance']);
-
-        $dataset = $this->memf->getMetaEntityManager()->find('App\Entity\Metamodel\Dataset', $args['dname']);
-
-        if (is_null($dataset)) {
-            return $this->dispatchHttpError(
-                $response,
-                'Invalid request',
-                'Dataset with id ' . $args['dname'] . ' is not found'
-            )->withStatus(404);
-        }
-
-        $queryParams = $request->getQueryParams();
-        if (!array_key_exists('a', $queryParams)) {
-            return $this->dispatchHttpError(
-                $response,
-                'Invalid request',
-                'Param a is required for this request'
-            )->withStatus(400);
-        }
-
-        $searchUri = '/search/' . $args['instance'] . '/data/' . $args['dname'];
-        $searchUri .= '?a=' . $queryParams['a'];
-
-        if (array_key_exists('c', $queryParams)) {
-            $searchUri .= '&c=' . $queryParams['c'];
-        }
-
-        $uniqId = uniqid();
-
-        $channel = $this->amqp->channel();
-        $channel->queue_declare('csv', false, false, false, false);
-        $msg = new AMQPMessage(json_encode(array('search' => $searchUri, 'uniqid' => $uniqId)));
-        $channel->basic_publish($msg, '', 'csv');
-
-        $channel->close();
-        $this->amqp->close();
-
-        return $response->withJson(array('message' => $uniqId));
-    }
-}
diff --git a/src/Action/Search/SearchAction.php b/src/Action/SearchAction.php
similarity index 53%
rename from src/Action/Search/SearchAction.php
rename to src/Action/SearchAction.php
index b9d5e00..3905ec3 100644
--- a/src/Action/Search/SearchAction.php
+++ b/src/Action/SearchAction.php
@@ -1,211 +1,132 @@
-<?php declare(strict_types=1);
+<?php
+
 /*
- * This file is part of ANIS SERVER API.
+ * This file is part of Anis Server.
  *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
  */
-namespace App\Action\Search;
+declare(strict_types=1);
+
+namespace App\Action;
 
-use Psr\Log\LoggerInterface;
-use Doctrine\DBAL\Platforms\PostgreSQL94Platform;
+use Psr\Http\Message\ResponseInterface as Response;
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Slim\Exception\HttpBadRequestException;
+use Slim\Exception\HttpNotFoundException;
+use Doctrine\ORM\EntityManagerInterface;
 use Doctrine\DBAL\Query\QueryBuilder;
 use Doctrine\DBAL\Query\Expression\CompositeExpression;
-use Psr\Http\Message\ServerRequestInterface as Request;
-use Psr\Http\Message\ResponseInterface as Response;
-use PDO;
-
-use App\Utils\ActionTrait;
-use App\Utils\MetaEntityManagerFactory;
 use App\Utils\DBALConnectionFactory;
+use App\Utils\Operator\OperatorFactory;
 use App\Utils\SearchException;
-use App\Utils\Operator\IOperatorFactory;
-use App\Entity\Metamodel\Dataset;
-use App\Entity\Metamodel\Attribute;
+use App\Entity\Dataset;
+use App\Entity\Attribute;
 
 /**
- * Get anis data or meta search (GET)
+ * Search action
  *
  * @author François Agneray <francois.agneray@lam.fr>
- * @package App\Action\Search
+ * @package App\Action
  */
-final class SearchAction
+final class SearchAction extends AbstractAction
 {
-    use ActionTrait;
-
-    /**
-     * The logger interface is the central access point to log anis information
-     *
-     * @var LoggerInterface
-     */
-    private $logger;
-
-    /**
-     * The MetaEntityManagerFactory create the doctrine entity manager for the metamodel database.
-     * The entity manager is the central access point to Doctrine ORM functionality (metadata database)
-     *
-     * @var MetaEntityManagerFactory
-     */
-    private $memf;
-
-    /**
-     * Factory method used to retrieve a PDO connection object to the business database
-     *
-     * @var DBALConnectionFactory
-     */
-    private $dcf;
-
-    /**
-     *
-     * @var IOperatorFactory
-     */
+    private $connectionFactory;
     private $operatorFactory;
 
     /**
-     * The encryption key used by anis to encrypt and decrypt sensitive data like passwords
-     * This key is provided by configuration (see the config file)
-     *
-     * @var string
-     */
-    private $encryptionKey;
-  
-    /**
-     * This class is creates before call __invoke to execute the action
+     * Create the classe before call __invoke to execute the action
      *
-     * @param LoggerInterface          $logger
-     * @param MetaEntityManagerFactory $memf
-     * @param DBALConnectionFactory    $dcf
-     * @param IOperatorFactory         $operatorFactory
-     * @param string                   $encryptionKey
+     * @param EntityManagerInterface $em Doctrine       Entity Manager Interface
+     * @param DBALConnectionFactory  $connectionFactory Factory used to construct connection to business database
      */
-    public function __construct(
-        LoggerInterface $logger,
-        MetaEntityManagerFactory $memf,
-        DBALConnectionFactory $dcf,
-        IOperatorFactory $operatorFactory,
-        string $encryptionKey
-    ) {
-        $this->logger = $logger;
-        $this->memf = $memf;
-        $this->dcf = $dcf;
+    public function __construct(EntityManagerInterface $em, DBALConnectionFactory $connectionFactory, OperatorFactory $operatorFactory)
+    {
+        parent::__construct($em);
+        $this->connectionFactory = $connectionFactory;
         $this->operatorFactory = $operatorFactory;
-        $this->encryptionKey = $encryptionKey;
     }
 
     /**
-     * This action returns data from an anis request
+     * This action indicates that the service is responding
      *
-     * @param ServerRequestInterface $request   This object contains the HTTP request
-     * @param ResponseInterface      $response  This object represents the HTTP response
-     * @param string[]               $args      This table contains information transmitted in the URL (see routes.php)
+     * @param  ServerRequestInterface $request  PSR-7 This object represents the HTTP request
+     * @param  ResponseInterface      $response PSR-7 This object represents the HTTP response
+     * @param  string[]               $args     This table contains information transmitted in the URL (see routes.php)
      *
      * @return ResponseInterface
      */
     public function __invoke(Request $request, Response $response, array $args): Response
     {
-        $this->logger->info('Search meta action dispatched');
-
-        if ($request->isOptions()) {
+        if ($request->getMethod() === OPTIONS) {
             return $response->withHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
         }
 
-        // Create the doctrine Entity Manager object for the instance metamodel database
-        $this->memf->createMetaEntityManager($args['instance']);
-
-        $dataset = $this->memf->getMetaEntityManager()->find('App\Entity\Metamodel\Dataset', $args['dname']);
+        // Search the correct dataset with primary key
+        $dataset = $this->em->find('App\Entity\Dataset', $args['dname']);
 
+        // If dataset is not found 404
         if (is_null($dataset)) {
-            return $this->dispatchHttpError(
-                $response,
-                'Invalid request',
-                'Dataset with id ' . $args['dname'] . ' is not found'
-            )->withStatus(404);
-        }
-
-        $queryParams = $request->getQueryParams();
-        if (!array_key_exists('a', $queryParams)) {
-            return $this->dispatchHttpError(
-                $response,
-                'Invalid request',
-                'Param a is required for this request'
-            )->withStatus(400);
+            throw new HttpNotFoundException(
+                $request,
+                'Dataset with name ' . $args['name'] . ' is not found'
+            );
         }
 
-        $database = $dataset->getProject()->getDatabase();
-        $decryptedPassword = $this->decryptData($database->getPassword());
-        $connection = $this->dcf->create($database, $decryptedPassword);
-        $this->operatorFactory->setDatabasePlatform($connection->getDatabasePlatform());
-        
+        // Create query builder with from clause using dataset information
+        $connection = $this->connectionFactory->create($dataset->getProject()->getDatabase());
         $queryBuilder = $connection->createQueryBuilder();
         $queryBuilder->from($dataset->getTableRef());
+        
+        if ($request->getMethod() === GET) {
+            $queryParams = $request->getQueryParams();
 
-        try {
-            $searchType = $this->getAndVerifySearchType($args['type']);
-
-            if (array_key_exists('c', $queryParams)) {
-                $this->where($queryBuilder, $dataset, explode(';', $queryParams['c']));
+            // The parameter "a" is mandatory
+            if (!array_key_exists('a', $queryParams)) {
+                throw new HttpBadRequestException(
+                    $request,
+                    'Param a is required for this request'
+                );
             }
-    
-            $listOfIds = explode(';', $queryParams['a']);
-    
-            if ($searchType === 'data') {
-                $attributes = $this->select($queryBuilder, $dataset, $listOfIds);
-    
+
+            try {
+                // The parameter a represents the SQL select clause
+                if ($queryParams['a'] === 'count') {
+                    $attributes = array();
+                    $queryBuilder->select('COUNT(*) as nb');
+                } else {
+                    $attributes = $this->select($queryBuilder, $dataset, explode(';', $queryParams['a']));
+                }
+
+                // The parameter c is not mandatory and represents the SQL where clause
+                if (array_key_exists('c', $queryParams)) {
+                    $this->where($queryBuilder, $dataset, explode(';', $queryParams['c']));
+                }
+
+                // The parameter o is not mandatory and represents the SQL order clause
                 if (array_key_exists('o', $queryParams)) {
                     $this->order($queryBuilder, $dataset, explode(';', $queryParams['o']));
                 }
-    
+
+                // The parameter p is not mandatory and represents the SQL limit clause
                 if (array_key_exists('p', $queryParams)) {
                     $this->limit($queryBuilder, $queryParams['p']);
                 }
-    
-                $result = $this->fetchAll($queryBuilder, $attributes);
-            } elseif ($searchType === 'meta') {
-                $queryBuilder->select('COUNT(*) as nb');
-                $stmt = $queryBuilder->execute();
-                $count = $stmt->fetchAll();
-                $result = array();
-                $result['dataset_selected'] = $dataset->getLabel();
-                $attributesSelected = array();
-                foreach ($listOfIds as $id) {
-                    $attribute = $this->getAttribute($dataset, (int) $id);
-                    $attributesSelected[] = array(
-                        'name' => $attribute->getName(),
-                        'label' => $attribute->getLabel()
-                    );
-                }
-                $result['attributes_selected'] = $attributesSelected;
-                $result['total_items'] = $count[0]['nb'];
+            } catch (SearchException $e) {
+                throw new HttpBadRequestException(
+                    $request,
+                    $e->getMessage()
+                );
             }
-        } catch (SearchException $e) {
-            return $this->dispatchHttpError(
-                $response,
-                'Invalid request',
-                $e->getMessage()
-            )->withStatus(400);
-        }
-
-        $this->logger->info('SQL: ' . $queryBuilder->getSQL());
-
-        return $response->withJson($result, 200, JSON_NUMERIC_CHECK | JSON_UNESCAPED_SLASHES);
-    }
 
-    /**
-     * Returns one of two accepted search types (meta or data)
-     * Or throw an Exception
-     *
-     * @throws SearchException Bad type of search
-     * @param string $type Type of search
-     */
-    private function getAndVerifySearchType(string $type): string
-    {
-        if ($type !== 'data' && $type !== 'meta') {
-            throw SearchException::typeOfSearchDoesNotExist($type);
+            $result = $this->fetchAll($queryBuilder, $attributes);
+            $payload = json_encode($result, JSON_UNESCAPED_SLASHES);
         }
-        return $type;
+
+        $response->getBody()->write($payload);
+        return $response;
     }
 
     /**
@@ -225,7 +146,6 @@ final class SearchAction
             $attributes[] = $attribute;
         }
         $queryBuilder->select($columns);
-
         return $attributes;
     }
 
@@ -306,6 +226,23 @@ final class SearchAction
             ->setMaxResults($limit);
     }
 
+    /**
+     * Returns the Attribute object of a dataset based on its ID (primary key)
+     *
+     * @throws SearchException Attribute with ID not found into the selected dataset
+     * @return Attribute       Returns the attribute found
+     */
+    private function getAttribute(Dataset $dataset, int $id): Attribute
+    {
+        $attributes = $dataset->getAttributes();
+        foreach ($attributes as $attribute) {
+            if ($attribute->getId() === $id) {
+                return $attribute;
+            }
+        }
+        throw SearchException::attributeNotFound($id, $dataset->getLabel());
+    }
+
     private function fetchAll(QueryBuilder $queryBuilder, array $attributes): array
     {
         $jsonAttributes = $this->getAttributesOfTypeJson($attributes);
@@ -331,21 +268,4 @@ final class SearchAction
             return $attribute->getType() === 'json';
         }));
     }
-
-    /**
-     * Returns the Attribute object of a dataset based on its ID (primary key)
-     *
-     * @throws SearchException Attribute with ID not found into the selected dataset
-     * @return Attribute
-     */
-    private function getAttribute(Dataset $dataset, int $id): Attribute
-    {
-        $attributes = $dataset->getAttributes();
-        foreach ($attributes as $attribute) {
-            if ($attribute->getId() === $id) {
-                return $attribute;
-            }
-        }
-        throw SearchException::attributeNotFound($id, $dataset->getLabel());
-    }
 }
diff --git a/src/Action/TableListAction.php b/src/Action/TableListAction.php
new file mode 100644
index 0000000..227647f
--- /dev/null
+++ b/src/Action/TableListAction.php
@@ -0,0 +1,86 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Action;
+
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Psr\Http\Message\ResponseInterface as Response;
+use Slim\Exception\HttpNotFoundException;
+use Doctrine\ORM\EntityManagerInterface;
+use App\Utils\DBALConnectionFactory;
+use App\Entity\Database;
+
+final class TableListAction extends AbstractAction
+{
+    private $connectionFactory;
+
+    /**
+     * Create the classe before call __invoke to execute the action
+     *
+     * @param EntityManagerInterface $em Doctrine       Entity Manager Interface
+     * @param DBALConnectionFactory  $connectionFactory Factory used to construct connection to business database
+     */
+    public function __construct(EntityManagerInterface $em, DBALConnectionFactory $connectionFactory)
+    {
+        parent::__construct($em);
+        $this->connectionFactory = $connectionFactory;
+    }
+
+    /**
+     * `GET`  Returns a list of all tables and views available in the database
+     *
+     * @param  ServerRequestInterface $request  PSR-7 This object represents the HTTP request
+     * @param  ResponseInterface      $response PSR-7 This object represents the HTTP response
+     * @param  string[]               $args     This table contains information transmitted in the URL (see routes.php)
+     *
+     * @return ResponseInterface
+     */
+    public function __invoke(Request $request, Response $response, array $args): Response
+    {
+        if ($request->getMethod() === OPTIONS) {
+            return $response->withHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
+        }
+
+        // Search the correct database with primary key
+        $database = $this->em->find('App\Entity\Database', $args['id']);
+
+        // If database is not found 404
+        if (is_null($database)) {
+            throw new HttpNotFoundException(
+                $request,
+                'Database with id ' . $args['id'] . ' is not found'
+            );
+        }
+
+        if ($request->getMethod() === GET) {
+            $connection = $this->connectionFactory->create($database);
+            $sm = $connection->getSchemaManager();
+            $f = function ($o) {
+                return $o->getName();
+            };
+            $tables = array_merge(array_map($f, $sm->listTables()), $this->getViews($sm->listViews()));
+            $payload = json_encode($tables);
+        }
+
+        $response->getBody()->write($payload);
+        return $response;
+    }
+
+    private function getViews($listViews)
+    {
+        $views = array();
+        foreach ($listViews as $v) {
+            $views[] = $v->getName();
+        }
+        return $views;
+    }
+}
diff --git a/src/Entity/Admin/Instance.php b/src/Entity/Admin/Instance.php
deleted file mode 100644
index 5ee052e..0000000
--- a/src/Entity/Admin/Instance.php
+++ /dev/null
@@ -1,336 +0,0 @@
-<?php declare(strict_types=1);
-/*
- * This file is part of ANIS SERVER API.
- *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-namespace App\Entity\Admin;
-
-/**
- * @Entity
- * @Table(name="instance")
- */
-class Instance implements \JsonSerializable
-{
-    /**
-     * @var string
-     *
-     * @Id
-     * @Column(type="string", nullable=false)
-     */
-    protected $name;
-
-    /**
-     * @var string
-     *
-     * @Column(type="string", nullable=false)
-     */
-    protected $label;
-
-    /**
-     * @var string
-     *
-     * @Column(type="string", name="path_proxy", nullable=false)
-     */
-    protected $pathProxy;
-
-    /**
-     * @var bool
-     *
-     * @Column(type="boolean", name="dev_mode", nullable=false)
-     */
-    protected $devMode;
-
-    /**
-     * @var string
-     *
-     * @Column(type="string", nullable=false)
-     */
-    protected $driver;
-
-    /**
-     * @var string
-     *
-     * @Column(type="string", nullable=false)
-     */
-    protected $path;
-
-    /**
-     * @var string
-     *
-     * @Column(type="string", nullable=false)
-     */
-    protected $host;
-
-    /**
-     * @var integer
-     *
-     * @Column(type="integer", nullable=true)
-     */
-    protected $port;
-
-    /**
-     * @var string
-     *
-     * @Column(type="string", name="dbname", nullable=false)
-     */
-    protected $dbname;
-
-    /**
-     * @var string
-     *
-     * @Column(type="string", nullable=false)
-     */
-    protected $login;
-
-    /**
-     * @var string
-     *
-     * @Column(type="string", nullable=false)
-     */
-    protected $password;
-
-    /**
-     * Create the classe with the property name (primary key)
-     */
-    public function __construct(string $name)
-    {
-        $this->name = $name;
-    }
-
-    /**
-     * Getter property name
-     *
-     * @return string
-     */
-    public function getName(): string
-    {
-        return $this->name;
-    }
-
-    /**
-     * Getter property label
-     *
-     * @return string
-     */
-    public function getLabel(): string
-    {
-        return $this->label;
-    }
-
-    /**
-     * Setter property label
-     *
-     * @param string $label
-     */
-    public function setLabel(string $label): void
-    {
-        $this->label = $label;
-    }
-
-    /**
-     * Getter property pathProxy
-     *
-     * @return string
-     */
-    public function getPathProxy(): string
-    {
-        return $this->pathProxy;
-    }
-
-    /**
-     * Setter property pathProxy
-     *
-     * @param string $pathProxy
-     */
-    public function setPathProxy(string $pathProxy): string
-    {
-        return $this->pathProxy = $pathProxy;
-    }
-
-    /**
-     * Getter property devMode
-     *
-     * @return bool
-     */
-    public function getDevMode(): bool
-    {
-        return $this->devMode;
-    }
-
-    /**
-     * @Setter property devMode
-     *
-     * @param bool $devMode
-     */
-    public function setDevMode(bool $devMode): void
-    {
-        $this->devMode = $devMode;
-    }
-
-    /**
-     * Getter property driver
-     *
-     * @return string
-     */
-    public function getDriver(): string
-    {
-        return $this->driver;
-    }
-
-    /**
-     * Setter property driver
-     *
-     * @param string $driver
-     */
-    public function setDriver(string $driver): void
-    {
-        $this->driver = $driver;
-    }
-
-    /**
-     * Getter property path
-     *
-     * @return string
-     */
-    public function getPath(): string
-    {
-        return $this->path;
-    }
-
-    /**
-     * Setter property path
-     *
-     * @param string $path
-     */
-    public function setPath(string $path): void
-    {
-        $this->path = $path;
-    }
-
-    /**
-     * Getter property host
-     *
-     * @return string
-     */
-    public function getHost(): string
-    {
-        return $this->host;
-    }
-
-    /**
-     * Setter property host
-     *
-     * @param string $host
-     */
-    public function setHost(string $host): void
-    {
-        $this->host = $host;
-    }
-
-    /**
-     * Getter property port
-     *
-     * @return int
-     */
-    public function getPort(): int
-    {
-        return $this->port;
-    }
-
-    /**
-     * Setter property port
-     *
-     * @param int $port
-     */
-    public function setPort(int $port): void
-    {
-        $this->port = $port;
-    }
-
-    /**
-     * Getter property dbname
-     *
-     * @return string
-     */
-    public function getDbName(): string
-    {
-        return $this->dbname;
-    }
-
-    /**
-     * Setter property dbname
-     *
-     * @param string $dbname
-     */
-    public function setDbName(string $dbname): void
-    {
-        $this->dbname = $dbname;
-    }
-
-    /**
-     * Getter property login
-     *
-     * @return string
-     */
-    public function getLogin(): string
-    {
-        return $this->login;
-    }
-
-    /**
-     * Setter property login
-     *
-     * @param string $login
-     */
-    public function setLogin(string $login): void
-    {
-        $this->login = $login;
-    }
-
-    /**
-     * Getter property password
-     *
-     * @return string
-     */
-    public function getPassword(): string
-    {
-        return $this->password;
-    }
-
-    /**
-     * Setter property password
-     *
-     * @param string $password
-     */
-    public function setPassword(string $password): void
-    {
-        $this->password = $password;
-    }
-
-    /**
-     * Return objet to array
-     *
-     * @return array
-     */
-    public function jsonSerialize(): array
-    {
-        return [
-            'name' => $this->getName(),
-            'label' => $this->getLabel(),
-            'path_proxy' => $this->getPathProxy(),
-            'dev_mode' => $this->getDevMode(),
-            'driver' => $this->getDriver(),
-            'path' => $this->getPath(),
-            'host' => $this->getHost(),
-            'port' => $this->getPort(),
-            'dbname' => $this->getDbName(),
-            'login' => $this->getLogin(),
-            'password' => $this->getPassword()
-        ];
-    }
-}
diff --git a/src/Entity/Admin/SettingsOption.php b/src/Entity/Admin/SettingsOption.php
deleted file mode 100644
index 9341515..0000000
--- a/src/Entity/Admin/SettingsOption.php
+++ /dev/null
@@ -1,112 +0,0 @@
-<?php declare(strict_types=1);
-/*
- * This file is part of ANIS SERVER API.
- *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-namespace App\Entity\Admin;
-
-/**
-* @Entity
-* @Table(name="settings_option")
-*/
-class SettingsOption implements \JsonSerializable
-{
-    /**
-     * @var integer
-     *
-     * @Id
-     * @Column(type="integer", nullable=false)
-     * @GeneratedValue
-     */
-    protected $id;
-
-    /**
-     * @var string
-     *
-     * @Column(type="string", nullable=false)
-     */
-    protected $label;
-
-    /**
-     * @var string
-     *
-     * @Column(type="string", nullable=false)
-     */
-    protected $value;
-
-    /**
-     * @var integer
-     *
-     * @Column(type="integer", nullable=false)
-     */
-    protected $display;
-
-    /**
-     * @var Anis\Entity\SettingsSelect
-     *
-     * @ManyToOne(targetEntity="SettingsSelect", inversedBy="settingsOptions")
-     * @JoinColumn(name="settings_select_id", referencedColumnName="id", nullable=false)
-     */
-    protected $settingsSelect;
-
-    public function __construct(SettingsSelect $settingsSelect)
-    {
-        $this->settingsSelect = $settingsSelect;
-    }
-
-    public function getId()
-    {
-        return $this->id;
-    }
-
-    public function getLabel()
-    {
-        return $this->label;
-    }
-
-    public function setLabel($label)
-    {
-        $this->label = $label;
-    }
-
-    public function getValue()
-    {
-        return $this->value;
-    }
-
-    public function setValue($value)
-    {
-        $this->value = $value;
-    }
-
-    public function getDisplay()
-    {
-        return $this->display;
-    }
-
-    public function setDisplay($display)
-    {
-        $this->display = (int) $display;
-    }
-
-    public function getSettingsSelect()
-    {
-        return $this->settingsSelect;
-    }
-
-    public function jsonSerialize()
-    {
-        return [
-            'id' => $this->getId(),
-            'label' => $this->getLabel(),
-            'value' => $this->getValue(),
-            'display' => $this->getDisplay(),
-            'id_settings_select' => $this->getSettingsSelect()->getId()
-        ];
-    }
-}
diff --git a/src/Entity/Admin/SettingsSelect.php b/src/Entity/Admin/SettingsSelect.php
deleted file mode 100644
index 93054fd..0000000
--- a/src/Entity/Admin/SettingsSelect.php
+++ /dev/null
@@ -1,82 +0,0 @@
-<?php declare(strict_types=1);
-/*
- * This file is part of ANIS SERVER API.
- *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-namespace App\Entity\Admin;
-
-/**
-* @Entity
-* @Table(name="settings_select")
-*/
-class SettingsSelect implements \JsonSerializable
-{
-    /**
-     * @var integer
-     *
-     * @Id
-     * @Column(type="integer", nullable=false)
-     * @GeneratedValue
-     */
-    protected $id;
-
-    /**
-     * @var string
-     *
-     * @Column(type="string", nullable=false)
-     */
-    protected $name;
-
-    /**
-     * @var string
-     *
-     * @Column(type="string", nullable=false)
-     */
-    protected $label;
-
-    /**
-     * @var Anis\Entity\SettingsOption[]
-     *
-     * @OneToMany(targetEntity="SettingsOption", mappedBy="settingsSelect", cascade={"remove"})
-     */
-    protected $settingsOptions;
-
-    public function getId()
-    {
-        return $this->id;
-    }
-
-    public function getName()
-    {
-        return $this->name;
-    }
-
-    public function setName($name)
-    {
-        $this->name = $name;
-    }
-
-    public function getLabel()
-    {
-        return $this->label;
-    }
-
-    public function setLabel($label)
-    {
-        $this->label = $label;
-    }
-    
-    public function jsonSerialize()
-    {
-        return [
-            'id' => $this->id,
-            'name' => $this->getName(),
-            'label' => $this->getLabel()
-        ];
-    }
-}
diff --git a/src/Entity/Admin/User.php b/src/Entity/Admin/User.php
deleted file mode 100644
index cd3fb54..0000000
--- a/src/Entity/Admin/User.php
+++ /dev/null
@@ -1,131 +0,0 @@
-<?php declare(strict_types=1);
-/*
- * This file is part of ANIS SERVER API.
- *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-namespace App\Entity\Admin;
-
-/**
-* @Entity
-* @Table(name="anis_user")
-*/
-class User implements \JsonSerializable
-{
-    /**
-     * @var string
-     *
-     * @Id
-     * @Column(type="string", nullable=false)
-     */
-    protected $email;
-
-    /**
-     * @var string
-     *
-     * @Column(type="string", nullable=false)
-     */
-    protected $password;
-
-    /**
-     * @var string
-     *
-     * @Column(type="string", name="activation_key", nullable=false)
-     */
-    protected $activationKey;
-
-    /**
-     * @var bool
-     *
-     * @Column(type="boolean", nullable=false)
-     */
-    protected $activated;
-
-    /**
-     * @var bool
-     *
-     * @Column(type="boolean", nullable=false)
-     */
-    protected $adminsi;
-
-    /**
-     * @var bool
-     *
-     * @Column(type="boolean", nullable=false)
-     */
-    protected $superuser;
-
-    public function __construct($email)
-    {
-        return $this->email = $email;
-    }
-
-    public function getEmail()
-    {
-        return $this->email;
-    }
-
-    public function getPassword()
-    {
-        return $this->password;
-    }
-
-    public function setPassword($password)
-    {
-        $this->password = $password;
-    }
-
-    public function getActivationKey()
-    {
-        return $this->activationKey;
-    }
-
-    public function setActivationKey($activationKey)
-    {
-        $this->activationKey = $activationKey;
-    }
-
-    public function getActivated()
-    {
-        return $this->activated;
-    }
-
-    public function setActivated($activated)
-    {
-        $this->activated = $activated;
-    }
-
-    public function getAdminsi()
-    {
-        return $this->adminsi;
-    }
-
-    public function setAdminsi($adminsi)
-    {
-        $this->adminsi = $adminsi;
-    }
-
-    public function getSuperuser()
-    {
-        return $this->superuser;
-    }
-
-    public function setSuperuser($superuser)
-    {
-        $this->superuser = $superuser;
-    }
-
-    public function jsonSerialize()
-    {
-        return [
-            'email' => $this->getEmail(),
-            'activated' => $this->getActivated(),
-            'adminsi' => $this->getAdminsi(),
-            'superuser' => $this->getSuperuser()
-        ];
-    }
-}
diff --git a/src/Entity/Metamodel/Attribute.php b/src/Entity/Attribute.php
similarity index 98%
rename from src/Entity/Metamodel/Attribute.php
rename to src/Entity/Attribute.php
index 0864960..30ecbea 100644
--- a/src/Entity/Metamodel/Attribute.php
+++ b/src/Entity/Attribute.php
@@ -1,14 +1,16 @@
-<?php declare(strict_types=1);
+<?php
+
 /*
- * This file is part of ANIS SERVER API.
+ * This file is part of Anis Server.
  *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
  */
-namespace App\Entity\Metamodel;
+declare(strict_types=1);
+
+namespace App\Entity;
 
 use Doctrine\Common\Collections\ArrayCollection;
 
diff --git a/src/Entity/Metamodel/CriteriaFamily.php b/src/Entity/CriteriaFamily.php
similarity index 87%
rename from src/Entity/Metamodel/CriteriaFamily.php
rename to src/Entity/CriteriaFamily.php
index de056b5..1646654 100644
--- a/src/Entity/Metamodel/CriteriaFamily.php
+++ b/src/Entity/CriteriaFamily.php
@@ -1,14 +1,16 @@
-<?php declare(strict_types=1);
+<?php
+
 /*
- * This file is part of ANIS SERVER API.
+ * This file is part of Anis Server.
  *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
  */
-namespace App\Entity\Metamodel;
+declare(strict_types=1);
+
+namespace App\Entity;
 
 /**
 * @Entity
diff --git a/src/Entity/Metamodel/Database.php b/src/Entity/Database.php
similarity index 93%
rename from src/Entity/Metamodel/Database.php
rename to src/Entity/Database.php
index b0c18db..1cf4c5d 100644
--- a/src/Entity/Metamodel/Database.php
+++ b/src/Entity/Database.php
@@ -1,14 +1,16 @@
-<?php declare(strict_types=1);
+<?php
+
 /*
- * This file is part of ANIS SERVER API.
+ * This file is part of Anis Server.
  *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
  */
-namespace App\Entity\Metamodel;
+declare(strict_types=1);
+
+namespace App\Entity;
 
 /**
  * @Entity
diff --git a/src/Entity/Metamodel/Dataset.php b/src/Entity/Dataset.php
similarity index 95%
rename from src/Entity/Metamodel/Dataset.php
rename to src/Entity/Dataset.php
index 2abe888..b2205f8 100644
--- a/src/Entity/Metamodel/Dataset.php
+++ b/src/Entity/Dataset.php
@@ -1,14 +1,16 @@
-<?php declare(strict_types=1);
+<?php
+
 /*
- * This file is part of ANIS SERVER API.
+ * This file is part of Anis Server.
  *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
  */
-namespace App\Entity\Metamodel;
+declare(strict_types=1);
+
+namespace App\Entity;
 
 use Doctrine\Common\Collections\ArrayCollection;
 
@@ -122,7 +124,7 @@ class Dataset implements \JsonSerializable
     public function __construct($name)
     {
         $this->name = $name;
-        $this->attributes = new ArrayCollection;
+        $this->attributes = new ArrayCollection();
     }
     
     public function getName()
diff --git a/src/Entity/Metamodel/DatasetFamily.php b/src/Entity/DatasetFamily.php
similarity index 87%
rename from src/Entity/Metamodel/DatasetFamily.php
rename to src/Entity/DatasetFamily.php
index 0f5a8cc..1137e75 100644
--- a/src/Entity/Metamodel/DatasetFamily.php
+++ b/src/Entity/DatasetFamily.php
@@ -1,14 +1,16 @@
-<?php declare(strict_types=1);
+<?php
+
 /*
- * This file is part of ANIS SERVER API.
+ * This file is part of Anis Server.
  *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
  */
-namespace App\Entity\Metamodel;
+declare(strict_types=1);
+
+namespace App\Entity;
 
 use Doctrine\Common\Collections\ArrayCollection;
 
@@ -50,7 +52,7 @@ class DatasetFamily implements \JsonSerializable
 
     public function __construct()
     {
-        $this->datasets = new ArrayCollection;
+        $this->datasets = new ArrayCollection();
     }
 
     public function getId()
diff --git a/src/Entity/Metamodel/DatasetPrivileges.php b/src/Entity/DatasetPrivileges.php
similarity index 86%
rename from src/Entity/Metamodel/DatasetPrivileges.php
rename to src/Entity/DatasetPrivileges.php
index 8570cf1..d7c4361 100644
--- a/src/Entity/Metamodel/DatasetPrivileges.php
+++ b/src/Entity/DatasetPrivileges.php
@@ -1,14 +1,16 @@
-<?php declare(strict_types=1);
+<?php
+
 /*
- * This file is part of ANIS SERVER API.
+ * This file is part of Anis Server.
  *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
  */
-namespace App\Entity\Metamodel;
+declare(strict_types=1);
+
+namespace App\Entity;
 
 /**
 * @Entity
diff --git a/src/Entity/Metamodel/File.php b/src/Entity/File.php
similarity index 92%
rename from src/Entity/Metamodel/File.php
rename to src/Entity/File.php
index ab92709..1065603 100644
--- a/src/Entity/Metamodel/File.php
+++ b/src/Entity/File.php
@@ -1,14 +1,16 @@
-<?php declare(strict_types=1);
+<?php
+
 /*
- * This file is part of ANIS SERVER API.
+ * This file is part of Anis Server.
  *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
  */
-namespace App\Entity\Metamodel;
+declare(strict_types=1);
+
+namespace App\Entity;
 
 /**
 * @Entity
diff --git a/src/Entity/Metamodel/Group.php b/src/Entity/Group.php
similarity index 86%
rename from src/Entity/Metamodel/Group.php
rename to src/Entity/Group.php
index 999cef7..f4fe223 100644
--- a/src/Entity/Metamodel/Group.php
+++ b/src/Entity/Group.php
@@ -1,14 +1,16 @@
-<?php declare(strict_types=1);
+<?php
+
 /*
- * This file is part of ANIS SERVER API.
+ * This file is part of Anis Server.
  *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
  */
-namespace App\Entity\Metamodel;
+declare(strict_types=1);
+
+namespace App\Entity;
 
 /**
 * @Entity
diff --git a/src/Entity/Metamodel/OutputCategory.php b/src/Entity/OutputCategory.php
similarity index 90%
rename from src/Entity/Metamodel/OutputCategory.php
rename to src/Entity/OutputCategory.php
index f139573..9fdff31 100644
--- a/src/Entity/Metamodel/OutputCategory.php
+++ b/src/Entity/OutputCategory.php
@@ -1,14 +1,16 @@
-<?php declare(strict_types=1);
+<?php
+
 /*
- * This file is part of ANIS SERVER API.
+ * This file is part of Anis Server.
  *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
  */
-namespace App\Entity\Metamodel;
+declare(strict_types=1);
+
+namespace App\Entity;
 
 /**
 * @Entity
diff --git a/src/Entity/Metamodel/OutputFamily.php b/src/Entity/OutputFamily.php
similarity index 88%
rename from src/Entity/Metamodel/OutputFamily.php
rename to src/Entity/OutputFamily.php
index be79b16..d768a47 100644
--- a/src/Entity/Metamodel/OutputFamily.php
+++ b/src/Entity/OutputFamily.php
@@ -1,14 +1,16 @@
-<?php declare(strict_types=1);
+<?php
+
 /*
- * This file is part of ANIS SERVER API.
+ * This file is part of Anis Server.
  *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
  */
-namespace App\Entity\Metamodel;
+declare(strict_types=1);
+
+namespace App\Entity;
 
 /**
 * @Entity
diff --git a/src/Entity/Metamodel/Project.php b/src/Entity/Project.php
similarity index 92%
rename from src/Entity/Metamodel/Project.php
rename to src/Entity/Project.php
index 4e27d37..e4f642f 100644
--- a/src/Entity/Metamodel/Project.php
+++ b/src/Entity/Project.php
@@ -1,14 +1,16 @@
-<?php declare(strict_types=1);
+<?php
+
 /*
- * This file is part of ANIS SERVER API.
+ * This file is part of Anis Server.
  *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
  */
-namespace App\Entity\Metamodel;
+declare(strict_types=1);
+
+namespace App\Entity;
 
 /**
 * @Entity
diff --git a/src/Entity/Metamodel/User.php b/src/Entity/User.php
similarity index 92%
rename from src/Entity/Metamodel/User.php
rename to src/Entity/User.php
index d0d9779..17a5294 100644
--- a/src/Entity/Metamodel/User.php
+++ b/src/Entity/User.php
@@ -1,14 +1,16 @@
-<?php declare(strict_types=1);
+<?php
+
 /*
- * This file is part of ANIS SERVER API.
+ * This file is part of Anis Server.
  *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
  */
-namespace App\Entity\Metamodel;
+declare(strict_types=1);
+
+namespace App\Entity;
 
 /**
 * @Entity
diff --git a/src/Handlers/LogErrorHandler.php b/src/Handlers/LogErrorHandler.php
new file mode 100644
index 0000000..61b0d76
--- /dev/null
+++ b/src/Handlers/LogErrorHandler.php
@@ -0,0 +1,52 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Handlers;
+
+use Slim\Handlers\ErrorHandler;
+use Psr\Log\LoggerInterface;
+
+/**
+ * Handler to log eror information with psr-3 logger system
+ *
+ * @author François Agneray <francois.agneray@lam.fr>
+ * @package App\Handlers
+ */
+class LogErrorHandler extends ErrorHandler
+{
+    /**
+     * The logger interface is the central access point to log information
+     *
+     * @var LoggerInterface
+     */
+    private $logger;
+
+    /**
+     * Set the concrete logger api based on psr-3 logger interface
+     *
+     * @param LoggerInterface $logger PSR-3 logger interface
+     */
+    public function setLogger(LoggerInterface $logger): void
+    {
+        $this->logger = $logger;
+    }
+
+    /**
+     * Log error information
+     *
+     * @param string $error Error information
+     */
+    protected function logError(string $error): void
+    {
+        $this->logger->info($error);
+    }
+}
diff --git a/src/Middleware/AuthorizationMiddleware.php b/src/Middleware/AuthorizationMiddleware.php
new file mode 100644
index 0000000..739dd60
--- /dev/null
+++ b/src/Middleware/AuthorizationMiddleware.php
@@ -0,0 +1,137 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Middleware;
+
+use Psr\Http\Message\ResponseInterface as Response;
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
+use Psr\Http\Server\MiddlewareInterface;
+use Slim\Exception\HttpBadRequestException;
+use Slim\Exception\HttpUnauthorizedException;
+use Doctrine\ORM\EntityManagerInterface;
+use Lcobucci\JWT\ValidationData;
+use Lcobucci\JWT\Parser;
+use Lcobucci\JWT\Signer\Hmac\Sha256;
+
+/**
+ * Middleware to handle authentication jwt
+ *
+ * @author François Agneray <francois.agneray@lam.fr>
+ * @package App\Middleware
+ */
+final class AuthorizationMiddleware implements MiddlewareInterface
+{
+    /**
+     * The EntityManager is the central access point to Doctrine ORM functionality
+     *
+     * @var EntityManagerInterface
+     */
+    protected $em;
+
+    /**
+     * This object contains token options from settings file
+     *
+     * @var array
+     */
+    private $tokenOptions;
+
+    /**
+     * Create the classe before call process to execute the middleware
+     *
+     * @param array                  $tokenOptions  Options to create the token
+     */
+    public function __construct(EntityManagerInterface $em, array $tokenOptions)
+    {
+        $this->em = $em;
+        $this->tokenOptions = $tokenOptions;
+    }
+
+    /**
+     * Handle jwt authentication
+     *
+     * @param  ServerRequest  $request PSR-7 request
+     * @param  RequestHandler $handler PSR-15 request handler
+     *
+     * @return Response
+     */
+    public function process(Request $request, RequestHandler $handler): Response
+    {
+        if (!$request->hasHeader('Authorization')) {
+            throw new HttpBadRequestException(
+                $request,
+                'Header Authorization needed for this route'
+            );
+        }
+
+        $authorization = $request->getHeaderLine('Authorization');
+
+        list($title, $jwt) = explode(' ', $authorization);
+        if ($title !== 'Bearer') {
+            throw new HttpBadRequestException(
+                $request,
+                'The format of the header Authorization must be Bearer jwt'
+            );
+        }
+
+        $token = (new Parser())->parse((string) $jwt);
+
+        if (!$token->validate($this->getValidationData())) {
+            throw new HttpUnauthorizedException(
+                $request,
+                'The jwt token is not validate'
+            );
+        }
+
+        $key = file_get_contents($this->tokenOptions['key']);
+        if (!$token->verify(new Sha256(), $key)) {
+            throw new HttpUnauthorizedException(
+                $request,
+                'The jwt token signature is not verify'
+            );
+        }
+
+        $jti = $token->getClaim('jti');
+        if ($this->getRevoked($jti)) {
+            throw new HttpUnauthorizedException(
+                $request,
+                'The jwt token is revoked'
+            );
+        }
+
+        return $handler->handle($request->withAttribute('superuser', $token->getClaim('superuser')));
+    }
+
+    /**
+     * Create and return the object used to validate a token
+     *
+     * @return ValidationData The object to validate a token
+     */
+    private function getValidationData(): ValidationData
+    {
+        $validationData = new ValidationData();
+        $validationData->setIssuer($this->tokenOptions['iss']);
+        $validationData->setAudience($this->tokenOptions['aud']);
+        return $validationData;
+    }
+
+    /**
+     * Return the state of the token (revoked or not)
+     *
+     * @return bool The state of the token
+     */
+    private function getRevoked($jti): bool
+    {
+        $entityToken = $this->em->find('App\Entity\Token', $jti);
+        return $entityToken->getRevoked();
+    }
+}
diff --git a/src/Middleware/ContentTypeJsonMiddleware.php b/src/Middleware/ContentTypeJsonMiddleware.php
new file mode 100644
index 0000000..1739227
--- /dev/null
+++ b/src/Middleware/ContentTypeJsonMiddleware.php
@@ -0,0 +1,41 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Middleware;
+
+use Psr\Http\Message\ResponseInterface as Response;
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
+use Psr\Http\Server\MiddlewareInterface;
+
+/**
+ * Middleware to force content type to application/json eror
+ *
+ * @author François Agneray <francois.agneray@lam.fr>
+ * @package App\Middleware
+ */
+final class ContentTypeJsonMiddleware implements MiddlewareInterface
+{
+    /**
+     * Force return response content type to application/json
+     *
+     * @param  ServerRequest  $request PSR-7 request
+     * @param  RequestHandler $handler PSR-15 request handler
+     *
+     * @return Response
+     */
+    public function process(Request $request, RequestHandler $handler): Response
+    {
+        $response = $handler->handle($request);
+        return $response->withHeader('Content-Type', 'application/json');
+    }
+}
diff --git a/src/Middleware/CorsMiddleware.php b/src/Middleware/CorsMiddleware.php
deleted file mode 100644
index e3b48ea..0000000
--- a/src/Middleware/CorsMiddleware.php
+++ /dev/null
@@ -1,34 +0,0 @@
-<?php declare(strict_types=1);
-/*
- * This file is part of ANIS SERVER API.
- *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-namespace App\Middleware;
-
-//use Psr\Log\LoggerInterface;
-use Psr\Http\Message\ServerRequestInterface as Request;
-use Psr\Http\Message\ResponseInterface as Response;
-
-class CorsMiddleware
-{
-    public function __invoke(Request $req, Response $res, $next)
-    {
-        if ($req->isOptions()) {
-            $acah = 'X-Requested-With, Content-Type, Accept, Origin, Authorization';
-            $response = $res
-                ->withHeader('Access-Control-Allow-Origin', '*')
-                ->withHeader('Access-Control-Allow-Headers', $acah);
-        } else {
-            $response = $res
-                ->withHeader('Access-Control-Allow-Origin', '*')
-                ->withHeader('Content-Type', 'application/json');
-        }
-
-        return $next($req, $response);
-    }
-}
diff --git a/src/Middleware/JsonBodyParserMiddleware.php b/src/Middleware/JsonBodyParserMiddleware.php
new file mode 100644
index 0000000..2628f7d
--- /dev/null
+++ b/src/Middleware/JsonBodyParserMiddleware.php
@@ -0,0 +1,50 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Middleware;
+
+use Psr\Http\Message\ResponseInterface as Response;
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
+use Psr\Http\Server\MiddlewareInterface;
+
+/**
+ * Middleware to handle request parsed body
+ *
+ * @author François Agneray <francois.agneray@lam.fr>
+ * @package App\Middleware
+ */
+final class JsonBodyParserMiddleware implements MiddlewareInterface
+{
+    /**
+     * Read body http request data if request content-type is application/json
+     * And set parsed body with json decoded information
+     *
+     * @param  ServerRequest  $request PSR-7 request
+     * @param  RequestHandler $handler PSR-15 request handler
+     *
+     * @return Response
+     */
+    public function process(Request $request, RequestHandler $handler): Response
+    {
+        $contentType = $request->getHeaderLine('Content-Type');
+
+        if (strstr($contentType, 'application/json')) {
+            $contents = json_decode(file_get_contents('php://input'), true);
+            if (json_last_error() === JSON_ERROR_NONE) {
+                $request = $request->withParsedBody($contents);
+            }
+        }
+
+        return $handler->handle($request);
+    }
+}
diff --git a/src/Middleware/TokenMiddleware.php b/src/Middleware/TokenMiddleware.php
deleted file mode 100644
index 42be44b..0000000
--- a/src/Middleware/TokenMiddleware.php
+++ /dev/null
@@ -1,70 +0,0 @@
-<?php declare(strict_types=1);
-/*
- * This file is part of ANIS SERVER API.
- *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-namespace App\Middleware;
-
-use Psr\Log\LoggerInterface;
-use Psr\Http\Message\ServerRequestInterface as Request;
-use Psr\Http\Message\ResponseInterface as Response;
-use Lcobucci\JWT\Parser;
-use Lcobucci\JWT\ValidationData;
-use Lcobucci\JWT\Signer\Hmac\Sha256;
-
-class TokenMiddleware
-{
-    private $logger;
-
-    public function __construct(LoggerInterface $logger)
-    {
-        $this->logger = $logger;
-    }
-
-    public function __invoke(Request $request, Response $response, $next)
-    {
-        $this->logger->info("Token middleware dispatched");
-
-        if ($request->isOptions()) {
-            $response = $next($request, $response);
-            return $response;
-        }
-
-        if (!$request->hasHeader('Authorization')) {
-            return $response->withStatus(401);
-        }
-
-        // Récupération du token string dans le header de la requete HTTP
-        $bearer = $request->getHeader('Authorization');
-        $data = explode(' ', $bearer[0]);
-        if ($data[0] !== 'Bearer') {
-            return $response->withStatus(401);
-        }
-
-        // Conversion string vers objet
-        $token = (new Parser())->parse((string) $data[1]);
-
-        // Validation du token
-        $data = new ValidationData();
-        $data->setIssuer('http://anis.lam.fr');
-        $data->setAudience('http://anis.lam.fr');
-        if (!$token->validate($data)) {
-            return $response->withStatus(401);
-        }
-
-        // Verification de la signature du token
-        $signer = new Sha256();
-        if (!$token->verify($signer, 'testing')) {
-            return $response->withStatus(401);
-        }
-
-        $response = $next($request->withAttribute('email', $token->getClaim('email')), $response);
-
-        return $response;
-    }
-}
diff --git a/src/Utils/ActionTrait.php b/src/Utils/ActionTrait.php
deleted file mode 100644
index 7b5a535..0000000
--- a/src/Utils/ActionTrait.php
+++ /dev/null
@@ -1,84 +0,0 @@
-<?php declare(strict_types=1);
-/*
- * This file is part of ANIS SERVER API.
- *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-namespace App\Utils;
-
-use Psr\Http\Message\ResponseInterface as Response;
-
-/**
- * Shared functions used by actions classes
- *
- * @author François Agneray <francois.agneray@lam.fr>
- * @package App\Utils
- */
-trait ActionTrait
-{
-    /**
-     * @param string $field
-     * @param array  $parsedBody
-     *
-     * @return string true if field is empty or false else
-     */
-    protected function isEmptyField(string $field, array $parsedBody): bool
-    {
-        // TODO: Vérifier le type, si le type est string verifier champ vide
-        if (!isset($parsedBody[$field])) {
-            return true;
-        } else {
-            return false;
-        }
-    }
-
-    /**
-     * Centralizes client error responses (400)
-     *
-     * @param ResponseInterface $response
-     * @param string            $error
-     * @param string            $errorDescription
-     *
-     * @return ResponseInterface The http client error response
-     */
-    protected function dispatchHttpError(Response $response, string $error, string $errorDescription): Response
-    {
-        $this->logger->info($errorDescription);
-        return $response->withStatus(400)->write(json_encode(array(
-            'error' => $error,
-            'error_description' => $errorDescription
-        )));
-    }
-
-    /**
-     * @param string $data The string to encrypt
-     *
-     * @return string The string encrypted
-     */
-    protected function encryptData(string $data): string
-    {
-        // Generate an initialization vector
-        $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('aes-256-cbc'));
-        // Encrypt the data using AES 256 encryption in CBC mode using our encryption key and initialization vector.
-        $encrypted = openssl_encrypt($data, 'aes-256-cbc', $this->encryptionKey, 0, $iv);
-        // The $iv is just as important as the key for decrypting
-        // So save it with our encrypted data using a unique separator (::)
-        return base64_encode($encrypted . '::' . $iv);
-    }
-
-    /**
-     * @param string $data The string to decrypt
-     *
-     * @return string The string decrypted
-     */
-    protected function decryptData(string $data): string
-    {
-        // To decrypt, split the encrypted data from our IV - our unique separator used was "::"
-        list($encryptedData, $iv) = explode('::', base64_decode($data), 2);
-        return openssl_decrypt($encryptedData, 'aes-256-cbc', $this->encryptionKey, 0, $iv);
-    }
-}
diff --git a/src/Utils/AnisErrorHandler.php b/src/Utils/AnisErrorHandler.php
deleted file mode 100644
index 31f1c00..0000000
--- a/src/Utils/AnisErrorHandler.php
+++ /dev/null
@@ -1,106 +0,0 @@
-<?php declare(strict_types=1);
-/*
- * This file is part of ANIS SERVER API.
- *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-namespace App\Utils;
-
-use Psr\Log\LoggerInterface;
-use Psr\Http\Message\ServerRequestInterface as Request;
-use Psr\Http\Message\ResponseInterface as Response;
-use Swift_Mailer;
-use Throwable;
-
-/**
- * Allows to manage anis server error and in particular returns the error by mail to the administrator
- *
- * @author François Agneray <francois.agneray@lam.fr>
- * @package App\Utils
- */
-class AnisErrorHandler extends \Slim\Handlers\PhpError
-{
-    /**
-     * The logger interface is the central access point to log anis information
-     *
-     * @var LoggerInterface
-     */
-    private $logger;
-
-    /**
-     * Swift Mailer class is used here to send error by email
-     *
-     * @var Swift_Mailer
-     */
-    private $mailer;
-
-    /**
-     * Create the classe before call __invoke to execute error handler
-     *
-     * @param LoggerInterface        $logger
-     * @param Swift_Mailer           $mailer
-     * @param bool                   $displayErrorDetails
-     */
-    public function __construct(LoggerInterface $logger, Swift_Mailer $mailer, bool $displayErrorDetails)
-    {
-        parent::__construct($displayErrorDetails);
-        $this->logger = $logger;
-        $this->mailer = $mailer;
-    }
-    
-    /**
-     * Sending an email to the administrator with details and invoke slim error handler
-     *
-     * @param ServerRequestInterface $request   The most recent Request object
-     * @param ResponseInterface      $response  The most recent Response object
-     * @param Throwable              $error     The caught Throwable object
-     *
-     * @return ResponseInterface
-     */
-    public function __invoke(Request $request, Response $response, Throwable $error): Response
-    {
-        $bool = $this->displayErrorDetails;
-        $this->displayErrorDetails = true;
-        $this->logger->error($this->getMessageAsText($error));
-        $this->sendErrorByMail($error);
-        $this->displayErrorDetails = $bool;
-        return parent::__invoke($request, $response, $error);
-    }
-
-    /**
-     * This method send the email to the administrator
-     *
-     * @param Throwable $error
-     */
-    private function sendErrorByMail(Throwable $error): void
-    {
-        $message = new \Swift_Message();
-        $message->setContentType('text/html');
-        $message->setSubject('ANIS error');
-        $message->setBody($this->renderHtmlErrorMessage($error));
-        $message->setFrom('anis-v3-server@lam.fr');
-        $message->setTo('cesamsi@lam.fr');
-        $this->mailer->send($message);
-    }
-
-    /**
-     * This method transform the error to the one line text message for the logger
-     *
-     * @param Throwable $error
-     */
-    private function getMessageAsText(Throwable $error): string
-    {
-        $message = 'Slim Application Error:' . PHP_EOL;
-        $message .= $this->renderThrowableAsText($error);
-        while ($error = $error->getPrevious()) {
-            $message .= PHP_EOL . 'Previous error:' . PHP_EOL;
-            $message .= $this->renderThrowableAsText($error);
-        }
-
-        return $message . PHP_EOL . 'View in rendered output by enabling the "displayErrorDetails" setting.' . PHP_EOL;
-    }
-}
diff --git a/src/Utils/DBALConnectionFactory.php b/src/Utils/DBALConnectionFactory.php
index 785079e..00a35d8 100644
--- a/src/Utils/DBALConnectionFactory.php
+++ b/src/Utils/DBALConnectionFactory.php
@@ -1,19 +1,20 @@
-<?php declare(strict_types=1);
+<?php
+
 /*
- * This file is part of ANIS SERVER API.
+ * This file is part of Anis Server.
  *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
  */
+declare(strict_types=1);
+
 namespace App\Utils;
 
 use Doctrine\DBAL\DriverManager;
 use Doctrine\DBAL\Connection;
-
-use App\Entity\Metamodel\Database;
+use App\Entity\Database;
 
 /**
  * Factory used to create a Doctrine DBAL connection to a business database
@@ -24,24 +25,23 @@ use App\Entity\Metamodel\Database;
 class DBALConnectionFactory
 {
     /**
-     * This static method create a connection to a business database.
+     * This method create a connection to a business database.
      * It retrieves connection information from the Database object.
      *
-     * Databae object stores information about a business database in
+     * Database object stores information about a business database in
      * the metamodel
      *
-     * @param Database $database          This object contains the database connection parameters
-     * @param string   $decryptedPassword This string contains the decrypted password for the database connection
+     * @param Database $database This object contains the database connection parameters
      *
      * @return Connection
      */
-    public static function create(Database $database, string $decryptedPassword): Connection
+    public function create(Database $database): Connection
     {
         $config = new \Doctrine\DBAL\Configuration();
         $connectionParams = array(
             'dbname' => $database->getDbName(),
             'user' => $database->getLogin(),
-            'password' => $decryptedPassword,
+            'password' => $database->getPassword(),
             'host' => $database->getHost(),
             'port' => $database->getPort(),
             'driver' => $database->getType(),
diff --git a/src/Utils/MetaEntityManagerFactory.php b/src/Utils/MetaEntityManagerFactory.php
deleted file mode 100644
index eefa78c..0000000
--- a/src/Utils/MetaEntityManagerFactory.php
+++ /dev/null
@@ -1,98 +0,0 @@
-<?php declare(strict_types=1);
-/*
- * This file is part of ANIS SERVER API.
- *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-namespace App\Utils;
-
-use Doctrine\ORM\EntityManagerInterface;
-use Doctrine\ORM\EntityManager;
-
-use App\Entity\Admin\Instance;
-
-/**
- * Factory used to create a Doctrine Entity Manager to the project metamodel database
- *
- * @author François Agneray <francois.agneray@lam.fr>
- * @package App\Utils
- */
-class MetaEntityManagerFactory
-{
-    /**
-     * Doctrine Entity Manager to the anis admin database
-     *
-     * @var EntityManagerInterface
-     */
-    private $aem;
-
-    /**
-     * Doctrine Entity Manager to the instance metamodel database
-     */
-    private $mem;
-
-    /**
-     * Create the classe with the Admin Entity Manager object
-     */
-    public function __construct(EntityManagerInterface $aem)
-    {
-        $this->aem = $aem;
-    }
-
-    /**
-     * Returns the instance metamodel doctrine entity manager
-     *
-     * @return EntityManagerInterface
-     */
-    public function getMetaEntityManager(): EntityManagerInterface
-    {
-        return $this->mem;
-    }
-
-    /**
-     * Create and return an Entity Manager to the correct metamodel database (databases, datasets, attributes...)
-     *
-     * @param string $instanceName Instance name necessary to find the correct metamodel database
-     */
-    public function createMetaEntityManager(string $instanceName): void
-    {
-        // Get instance information from anis admin database
-        $instance = $this->aem->find('App\Entity\Admin\Instance', $instanceName);
-
-        // Create the entity manager to drive the project metamodel database (databases, datasets, attributes...)
-        $c = \Doctrine\ORM\Tools\Setup::createAnnotationMetadataConfiguration(
-            array('../src/Entity/Metamodel'),
-            $instance->getDevMode()
-        );
-        $c->setProxyDir($instance->getPathProxy());
-        if ($instance->getDevMode()) {
-            $c->setAutoGenerateProxyClasses(true);
-        } else {
-            $c->setAutoGenerateProxyClasses(false);
-        }
-        $this->mem = EntityManager::create($this->getConnectionOptions($instance), $c);
-    }
-
-    /**
-     * Returns the doctrine connection options array created with the $instance row object
-     *
-     * @param Instance $instance The doctrine object instance find with the $instanceName
-     * @return array
-     */
-    private function getConnectionOptions(Instance $instance): array
-    {
-        return [
-            'driver' => $instance->getDriver(),
-            'path' => $instance->getPath(),
-            'host' => $instance->getHost(),
-            'port' => $instance->getPort(),
-            'dbname' => $instance->getDbName(),
-            'user' => $instance->getLogin(),
-            'password' => $instance->getPassword()
-        ];
-    }
-}
diff --git a/src/Utils/Operator/Between.php b/src/Utils/Operator/Between.php
index 9aad059..cc73891 100644
--- a/src/Utils/Operator/Between.php
+++ b/src/Utils/Operator/Between.php
@@ -1,13 +1,15 @@
-<?php declare(strict_types=1);
+<?php
+
 /*
- * This file is part of ANIS SERVER API.
+ * This file is part of Anis Server.
  *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
  */
+declare(strict_types=1);
+
 namespace App\Utils\Operator;
 
 use Doctrine\DBAL\Query\Expression\ExpressionBuilder;
diff --git a/src/Utils/Operator/Equal.php b/src/Utils/Operator/Equal.php
index 4a448b9..a076a67 100644
--- a/src/Utils/Operator/Equal.php
+++ b/src/Utils/Operator/Equal.php
@@ -1,13 +1,15 @@
-<?php declare(strict_types=1);
+<?php
+
 /*
- * This file is part of ANIS SERVER API.
+ * This file is part of Anis Server.
  *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
  */
+declare(strict_types=1);
+
 namespace App\Utils\Operator;
 
 use Doctrine\DBAL\Query\Expression\ExpressionBuilder;
diff --git a/src/Utils/Operator/GreaterThan.php b/src/Utils/Operator/GreaterThan.php
index 9f329ef..d4761d3 100644
--- a/src/Utils/Operator/GreaterThan.php
+++ b/src/Utils/Operator/GreaterThan.php
@@ -1,13 +1,15 @@
-<?php declare(strict_types=1);
+<?php
+
 /*
- * This file is part of ANIS SERVER API.
+ * This file is part of Anis Server.
  *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
  */
+declare(strict_types=1);
+
 namespace App\Utils\Operator;
 
 use Doctrine\DBAL\Query\Expression\ExpressionBuilder;
@@ -47,7 +49,7 @@ class GreaterThan extends Operator
      *
      * @return string
      */
-    public function getExpression() : string
+    public function getExpression(): string
     {
         return $this->expr->gt($this->column, $this->getSqlValue($this->value));
     }
diff --git a/src/Utils/Operator/GreaterThanEqual.php b/src/Utils/Operator/GreaterThanEqual.php
index bb604a4..3cca25e 100644
--- a/src/Utils/Operator/GreaterThanEqual.php
+++ b/src/Utils/Operator/GreaterThanEqual.php
@@ -1,13 +1,15 @@
-<?php declare(strict_types=1);
+<?php
+
 /*
- * This file is part of ANIS SERVER API.
+ * This file is part of Anis Server.
  *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
  */
+declare(strict_types=1);
+
 namespace App\Utils\Operator;
 
 use Doctrine\DBAL\Query\Expression\ExpressionBuilder;
@@ -47,7 +49,7 @@ class GreaterThanEqual extends Operator
      *
      * @return string
      */
-    public function getExpression() : string
+    public function getExpression(): string
     {
         return $this->expr->gte($this->column, $this->getSqlValue($this->value));
     }
diff --git a/src/Utils/Operator/IOperator.php b/src/Utils/Operator/IOperator.php
index 0de9c86..321ddc2 100644
--- a/src/Utils/Operator/IOperator.php
+++ b/src/Utils/Operator/IOperator.php
@@ -1,13 +1,15 @@
-<?php declare(strict_types=1);
+<?php
+
 /*
- * This file is part of ANIS SERVER API.
+ * This file is part of Anis Server.
  *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
  */
+declare(strict_types=1);
+
 namespace App\Utils\Operator;
 
 /**
diff --git a/src/Utils/Operator/IOperatorFactory.php b/src/Utils/Operator/IOperatorFactory.php
index b07819c..1a31370 100644
--- a/src/Utils/Operator/IOperatorFactory.php
+++ b/src/Utils/Operator/IOperatorFactory.php
@@ -1,13 +1,15 @@
-<?php declare(strict_types=1);
+<?php
+
 /*
- * This file is part of ANIS SERVER API.
+ * This file is part of Anis Server.
  *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
  */
+declare(strict_types=1);
+
 namespace App\Utils\Operator;
 
 use Doctrine\DBAL\Query\Expression\ExpressionBuilder;
diff --git a/src/Utils/Operator/In.php b/src/Utils/Operator/In.php
index 5bf3a58..1e60529 100644
--- a/src/Utils/Operator/In.php
+++ b/src/Utils/Operator/In.php
@@ -1,13 +1,15 @@
-<?php declare(strict_types=1);
+<?php
+
 /*
- * This file is part of ANIS SERVER API.
+ * This file is part of Anis Server.
  *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
  */
+declare(strict_types=1);
+
 namespace App\Utils\Operator;
 
 use Doctrine\DBAL\Query\Expression\ExpressionBuilder;
diff --git a/src/Utils/Operator/JsonPostgres.php b/src/Utils/Operator/JsonPostgres.php
index 4a1c13e..94adbb0 100644
--- a/src/Utils/Operator/JsonPostgres.php
+++ b/src/Utils/Operator/JsonPostgres.php
@@ -1,13 +1,15 @@
-<?php declare(strict_types=1);
+<?php
+
 /*
- * This file is part of ANIS SERVER API.
+ * This file is part of Anis Server.
  *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
  */
+declare(strict_types=1);
+
 namespace App\Utils\Operator;
 
 use Doctrine\DBAL\Query\Expression\ExpressionBuilder;
diff --git a/src/Utils/Operator/LessThan.php b/src/Utils/Operator/LessThan.php
index b17a803..ea62bf3 100644
--- a/src/Utils/Operator/LessThan.php
+++ b/src/Utils/Operator/LessThan.php
@@ -1,13 +1,15 @@
-<?php declare(strict_types=1);
+<?php
+
 /*
- * This file is part of ANIS SERVER API.
+ * This file is part of Anis Server.
  *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
  */
+declare(strict_types=1);
+
 namespace App\Utils\Operator;
 
 use Doctrine\DBAL\Query\Expression\ExpressionBuilder;
diff --git a/src/Utils/Operator/LessThanEqual.php b/src/Utils/Operator/LessThanEqual.php
index 4ed4085..ec0315d 100644
--- a/src/Utils/Operator/LessThanEqual.php
+++ b/src/Utils/Operator/LessThanEqual.php
@@ -1,13 +1,15 @@
-<?php declare(strict_types=1);
+<?php
+
 /*
- * This file is part of ANIS SERVER API.
+ * This file is part of Anis Server.
  *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
  */
+declare(strict_types=1);
+
 namespace App\Utils\Operator;
 
 use Doctrine\DBAL\Query\Expression\ExpressionBuilder;
diff --git a/src/Utils/Operator/Like.php b/src/Utils/Operator/Like.php
index fe5f26a..6909a53 100644
--- a/src/Utils/Operator/Like.php
+++ b/src/Utils/Operator/Like.php
@@ -1,13 +1,15 @@
-<?php declare(strict_types=1);
+<?php
+
 /*
- * This file is part of ANIS SERVER API.
+ * This file is part of Anis Server.
  *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
  */
+declare(strict_types=1);
+
 namespace App\Utils\Operator;
 
 use Doctrine\DBAL\Query\Expression\ExpressionBuilder;
diff --git a/src/Utils/Operator/NotEqual.php b/src/Utils/Operator/NotEqual.php
index 6e6bb6f..26aaa68 100644
--- a/src/Utils/Operator/NotEqual.php
+++ b/src/Utils/Operator/NotEqual.php
@@ -1,13 +1,15 @@
-<?php declare(strict_types=1);
+<?php
+
 /*
- * This file is part of ANIS SERVER API.
+ * This file is part of Anis Server.
  *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
  */
+declare(strict_types=1);
+
 namespace App\Utils\Operator;
 
 use Doctrine\DBAL\Query\Expression\ExpressionBuilder;
diff --git a/src/Utils/Operator/NotIn.php b/src/Utils/Operator/NotIn.php
index ad67ef6..10ad192 100644
--- a/src/Utils/Operator/NotIn.php
+++ b/src/Utils/Operator/NotIn.php
@@ -1,13 +1,15 @@
-<?php declare(strict_types=1);
+<?php
+
 /*
- * This file is part of ANIS SERVER API.
+ * This file is part of Anis Server.
  *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
  */
+declare(strict_types=1);
+
 namespace App\Utils\Operator;
 
 use Doctrine\DBAL\Query\Expression\ExpressionBuilder;
diff --git a/src/Utils/Operator/NotLike.php b/src/Utils/Operator/NotLike.php
index 02895ec..bae1ce4 100644
--- a/src/Utils/Operator/NotLike.php
+++ b/src/Utils/Operator/NotLike.php
@@ -1,13 +1,15 @@
-<?php declare(strict_types=1);
+<?php
+
 /*
- * This file is part of ANIS SERVER API.
+ * This file is part of Anis Server.
  *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
  */
+declare(strict_types=1);
+
 namespace App\Utils\Operator;
 
 use Doctrine\DBAL\Query\Expression\ExpressionBuilder;
diff --git a/src/Utils/Operator/Operator.php b/src/Utils/Operator/Operator.php
index 3e6cf57..7ebd1f2 100644
--- a/src/Utils/Operator/Operator.php
+++ b/src/Utils/Operator/Operator.php
@@ -1,13 +1,15 @@
-<?php declare(strict_types=1);
+<?php
+
 /*
- * This file is part of ANIS SERVER API.
+ * This file is part of Anis Server.
  *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
  */
+declare(strict_types=1);
+
 namespace App\Utils\Operator;
 
 use Doctrine\DBAL\Query\Expression\ExpressionBuilder;
diff --git a/src/Utils/Operator/OperatorException.php b/src/Utils/Operator/OperatorException.php
index f381c3d..6695b1a 100644
--- a/src/Utils/Operator/OperatorException.php
+++ b/src/Utils/Operator/OperatorException.php
@@ -1,13 +1,15 @@
-<?php declare(strict_types=1);
+<?php
+
 /*
- * This file is part of ANIS SERVER API.
+ * This file is part of Anis Server.
  *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
  */
+declare(strict_types=1);
+
 namespace App\Utils\Operator;
 
 use App\Utils\SearchException;
diff --git a/src/Utils/Operator/OperatorFactory.php b/src/Utils/Operator/OperatorFactory.php
index 1afe7f0..f99612a 100644
--- a/src/Utils/Operator/OperatorFactory.php
+++ b/src/Utils/Operator/OperatorFactory.php
@@ -1,13 +1,15 @@
-<?php declare(strict_types=1);
+<?php
+
 /*
- * This file is part of ANIS SERVER API.
+ * This file is part of Anis Server.
  *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
  */
+declare(strict_types=1);
+
 namespace App\Utils\Operator;
 
 use Doctrine\DBAL\Platforms\AbstractPlatform;
diff --git a/src/Utils/Operator/OperatorNotNull.php b/src/Utils/Operator/OperatorNotNull.php
index 5a60312..4d58171 100644
--- a/src/Utils/Operator/OperatorNotNull.php
+++ b/src/Utils/Operator/OperatorNotNull.php
@@ -1,13 +1,15 @@
-<?php declare(strict_types=1);
+<?php
+
 /*
- * This file is part of ANIS SERVER API.
+ * This file is part of Anis Server.
  *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
  */
+declare(strict_types=1);
+
 namespace App\Utils\Operator;
 
 use Doctrine\DBAL\Query\Expression\ExpressionBuilder;
diff --git a/src/Utils/Operator/OperatorNull.php b/src/Utils/Operator/OperatorNull.php
index 817d541..de07b1d 100644
--- a/src/Utils/Operator/OperatorNull.php
+++ b/src/Utils/Operator/OperatorNull.php
@@ -1,13 +1,15 @@
-<?php declare(strict_types=1);
+<?php
+
 /*
- * This file is part of ANIS SERVER API.
+ * This file is part of Anis Server.
  *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
  */
+declare(strict_types=1);
+
 namespace App\Utils\Operator;
 
 use Doctrine\DBAL\Query\Expression\ExpressionBuilder;
@@ -20,18 +22,6 @@ use Doctrine\DBAL\Query\Expression\ExpressionBuilder;
  */
 class OperatorNull extends Operator
 {
-    /**
-     * Create the class before call getExpression method to execute this operator
-     *
-     * @param ExpressionBuilder $expr
-     * @param string            $column
-     * @param string            $columnType
-     */
-    public function __construct(ExpressionBuilder $expr, string $column, string $columnType)
-    {
-        parent::__construct($expr, $column, $columnType);
-    }
-
     /**
      * This method returns the null expression for this criterion
      *
diff --git a/src/Utils/SearchException.php b/src/Utils/SearchException.php
index 74fcd62..a1380e8 100644
--- a/src/Utils/SearchException.php
+++ b/src/Utils/SearchException.php
@@ -1,13 +1,15 @@
-<?php declare(strict_types=1);
+<?php
+
 /*
- * This file is part of ANIS SERVER API.
+ * This file is part of Anis Server.
  *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
  */
+declare(strict_types=1);
+
 namespace App\Utils;
 
 use Exception;
diff --git a/src/dependencies.php b/src/dependencies.php
deleted file mode 100644
index 1cd43f7..0000000
--- a/src/dependencies.php
+++ /dev/null
@@ -1,269 +0,0 @@
-<?php declare(strict_types=1);
-/*
- * This file is part of ANIS SERVER API.
- *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-// DIC configuration
-$container = $app->getContainer();
-
-// Anis Error Handler
-$container['errorHandler'] = function ($c) {
-    return new App\Utils\AnisErrorHandler(
-        $c->get('logger'),
-        $c->get('mailer'),
-        (bool) $c->get('settings')['displayErrorDetails']
-    );
-};
-
-// Admin database Doctrine 2 Entity Manager
-$container['aem'] = function ($c) {
-    $settings = $c->get('settings');
-    $adminDb = $settings['admin_db'];
-    $c = \Doctrine\ORM\Tools\Setup::createAnnotationMetadataConfiguration(
-        array('src/Entity/Admin'),
-        $adminDb['dev_mode']
-    );
-    $c->setProxyDir($adminDb['path_proxy']);
-    if ($adminDb['dev_mode']) {
-        $c->setAutoGenerateProxyClasses(true);
-    } else {
-        $c->setAutoGenerateProxyClasses(false);
-    }
-    $em = \Doctrine\ORM\EntityManager::create($adminDb['connection_options'], $c);
-    return $em;
-};
-
-// Monolog
-$container['logger'] = function ($c) {
-    $settings = $c->get('settings');
-    $logger = new \Monolog\Logger($settings['logger']['name']);
-    $logger->pushProcessor(new \Monolog\Processor\UidProcessor());
-    $logger->pushHandler(new \Monolog\Handler\StreamHandler($settings['logger']['path'], $settings['logger']['level']));
-    return $logger;
-};
-
-// swiftmailer
-$container['mailer'] = function ($c) {
-    $settings = $c->get('settings');
-    $transport = new \Swift_SmtpTransport($settings['mailer']['host'], $settings['mailer']['port']);
-    $mailer = new \Swift_Mailer($transport);
-    return $mailer;
-};
-
-// RabbitMQ
-$container['amqp'] = function ($c) {
-    $settings = $c->get('settings');
-    $amqp = new \PhpAmqpLib\Connection\AMQPStreamConnection(
-        $settings['amqp']['host'],
-        $settings['amqp']['port'],
-        $settings['amqp']['user'],
-        $settings['amqp']['password']
-    );
-    return $amqp;
-};
-
-// MetaEntityManagerFactory
-$container['memf'] = function ($c) {
-    $adminEntityManager = $c->get('aem');
-    return new App\Utils\MetaEntityManagerFactory($adminEntityManager);
-};
-
-// OperatorFactory
-$container['of'] = function ($c) {
-    return new App\Utils\Operator\OperatorFactory();
-};
-
-// DBAL Connection Factory
-$container['dcf'] = function ($c) {
-    return new App\Utils\DBALConnectionFactory();
-};
-
-// Middleware
-$container['App\Middleware\CorsMiddleware'] = function ($c) {
-    return new App\Middleware\CorsMiddleware();
-};
-
-// Root actions
-$container['App\Action\Root\RootAction'] = function ($c) {
-    return new App\Action\Root\RootAction($c->get('logger'));
-};
-
-$container['App\Action\Root\OpenApiAction'] = function ($c) {
-    return new App\Action\Root\OpenApiAction($c->get('logger'));
-};
-
-// Login actions
-$container['App\Action\Login\RegisterAction'] = function ($c) {
-    return new App\Action\Login\RegisterAction($c->get('logger'), $c->get('aem'), $c->get('mailer'));
-};
-
-$container['App\Action\Login\ActivateAccountAction'] = function ($c) {
-    return new App\Action\Login\ActivateAccountAction($c->get('logger'), $c->get('aem'), $c->get('mailer'));
-};
-
-$container['App\Action\Login\TokenAction'] = function ($c) {
-    // TODO: Augmenter la force de la signature du jeton
-    // On instancie le jwtBuilder à envoyer dans l'action login
-    $jwtBuilder = (new \Lcobucci\JWT\Builder())
-        ->setIssuer('http://anis.lam.fr') // Emetteur du jeton
-        ->setAudience('http://anis.lam.fr') // Recepteur du jeton
-        ->setIssuedAt(time()) // Date à laquelle le jeton a été généré
-        ->setNotBefore(time()) // Date à laquelle le jeton pourra être utilisé
-        ->setExpiration(time() + 36000); // Date d'éxpiration du jeton
-    $jwtSigner = new \Lcobucci\JWT\Signer\Hmac\Sha256();
-    return new App\Action\Login\TokenAction($c->get('logger'), $c->get('aem'), $jwtBuilder, $jwtSigner);
-};
-
-$container['App\Action\Login\NewPasswordAction'] = function ($c) {
-    return new App\Action\Login\NewPasswordAction($c->get('logger'), $c->get('aem'), $c->get('mailer'));
-};
-
-$container['App\Action\Login\ChangePasswordAction'] = function ($c) {
-    return new App\Action\Login\ChangePasswordAction($c->get('logger'), $c->get('aem'), $c->get('mailer'));
-};
-
-// Admin actions
-$container['App\Action\Admin\UserListAction'] = function ($c) {
-    return new App\Action\Admin\UserListAction($c->get('logger'), $c->get('aem'));
-};
-
-$container['App\Action\Admin\UserAction'] = function ($c) {
-    return new App\Action\Admin\UserAction($c->get('logger'), $c->get('aem'));
-};
-
-$container['App\Action\Admin\SelectListAction'] = function ($c) {
-    return new App\Action\Admin\SelectListAction($c->get('logger'), $c->get('aem'));
-};
-
-$container['App\Action\Admin\SelectAction'] = function ($c) {
-    return new App\Action\Admin\SelectAction($c->get('logger'), $c->get('aem'));
-};
-
-$container['App\Action\Admin\OptionListAction'] = function ($c) {
-    return new App\Action\Admin\OptionListAction($c->get('logger'), $c->get('aem'));
-};
-
-$container['App\Action\Admin\OptionAction'] = function ($c) {
-    return new App\Action\Admin\OptionAction($c->get('logger'), $c->get('aem'));
-};
-
-$container['App\Action\Admin\InstanceListAction'] = function ($c) {
-    return new App\Action\Admin\InstanceListAction($c->get('logger'), $c->get('aem'));
-};
-
-$container['App\Action\Admin\InstanceAction'] = function ($c) {
-    return new App\Action\Admin\InstanceAction($c->get('logger'), $c->get('aem'));
-};
-
-$container['App\Action\Admin\MetamodelAction'] = function ($c) {
-    return new App\Action\Admin\MetamodelAction($c->get('logger'), $c->get('aem'), $c->get('memf'));
-};
-
-// Metamodel actions
-$container['App\Action\Meta\DatabaseListAction'] = function ($c) {
-    $settings = $c->get('settings');
-    return new App\Action\Meta\DatabaseListAction($c->get('logger'), $c->get('memf'), $settings['encryption_key']);
-};
-
-$container['App\Action\Meta\DatabaseAction'] = function ($c) {
-    $settings = $c->get('settings');
-    return new App\Action\Meta\DatabaseAction($c->get('logger'), $c->get('memf'), $settings['encryption_key']);
-};
-
-$container['App\Action\Meta\TableListAction'] = function ($c) {
-    $settings = $c->get('settings');
-    return new App\Action\Meta\TableListAction(
-        $c->get('logger'),
-        $c->get('memf'),
-        $c->get('dcf'),
-        $settings['encryption_key']
-    );
-};
-
-$container['App\Action\Meta\ProjectListAction'] = function ($c) {
-    return new App\Action\Meta\ProjectListAction($c->get('logger'), $c->get('memf'));
-};
-
-$container['App\Action\Meta\ProjectAction'] = function ($c) {
-    return new App\Action\Meta\ProjectAction($c->get('logger'), $c->get('memf'));
-};
-
-$container['App\Action\Meta\FamilyListAction'] = function ($c) {
-    return new App\Action\Meta\FamilyListAction($c->get('logger'), $c->get('memf'));
-};
-
-$container['App\Action\Meta\FamilyAction'] = function ($c) {
-    return new App\Action\Meta\FamilyAction($c->get('logger'), $c->get('memf'));
-};
-
-$container['App\Action\Meta\OutputCategoryListAction'] = function ($c) {
-    return new App\Action\Meta\OutputCategoryListAction($c->get('logger'), $c->get('memf'));
-};
-
-$container['App\Action\Meta\OutputCategoryAction'] = function ($c) {
-    return new App\Action\Meta\OutputCategoryAction($c->get('logger'), $c->get('memf'));
-};
-
-$container['App\Action\Meta\DatasetListAction'] = function ($c) {
-    $settings = $c->get('settings');
-    return new App\Action\Meta\DatasetListAction(
-        $c->get('logger'),
-        $c->get('memf'),
-        $c->get('dcf'),
-        $settings['encryption_key']
-    );
-};
-
-$container['App\Action\Meta\DatasetAction'] = function ($c) {
-    return new App\Action\Meta\DatasetAction($c->get('logger'), $c->get('memf'));
-};
-
-$container['App\Action\Meta\AttributeAction'] = function ($c) {
-    return new App\Action\Meta\AttributeAction($c->get('logger'), $c->get('memf'));
-};
-
-$container['App\Action\Meta\AttributeListAction'] = function ($c) {
-    return new App\Action\Meta\AttributeListAction($c->get('logger'), $c->get('memf'));
-};
-
-$container['App\Action\Meta\FileProxyAction'] = function ($c) {
-    return new App\Action\Meta\FileProxyAction($c->get('memf'));
-};
-
-$container['App\Action\Meta\FileListAction'] = function ($c) {
-    return new App\Action\Meta\FileListAction($c->get('logger'), $c->get('memf'));
-};
-
-$container['App\Action\Meta\FileAction'] = function ($c) {
-    return new App\Action\Meta\FileAction($c->get('logger'), $c->get('memf'));
-};
-
-$container['App\Action\Meta\GroupListAction'] = function ($c) {
-    return new App\Action\Meta\GroupListAction($c->get('logger'), $c->get('memf'));
-};
-
-$container['App\Action\Meta\GroupAction'] = function ($c) {
-    return new App\Action\Meta\GroupAction($c->get('logger'), $c->get('memf'));
-};
-
-// Search actions
-$container['App\Action\Search\SearchAction'] = function ($c) {
-    $settings = $c->get('settings');
-    return new App\Action\Search\SearchAction(
-        $c->get('logger'),
-        $c->get('memf'),
-        $c->get('dcf'),
-        $c->get('of'),
-        $settings['encryption_key']
-    );
-};
-
-$container['App\Action\Search\ServiceAction'] = function ($c) {
-    return new App\Action\Search\ServiceAction($c->get('logger'), $c->get('memf'), $c->get('amqp'));
-};
diff --git a/src/middleware.php b/src/middleware.php
deleted file mode 100644
index a5ee05b..0000000
--- a/src/middleware.php
+++ /dev/null
@@ -1,12 +0,0 @@
-<?php declare(strict_types=1);
-/*
- * This file is part of ANIS SERVER API.
- *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-$app->add(App\Middleware\CorsMiddleware::class);
diff --git a/src/routes.php b/src/routes.php
deleted file mode 100644
index 83d3ed8..0000000
--- a/src/routes.php
+++ /dev/null
@@ -1,65 +0,0 @@
-<?php declare(strict_types=1);
-/*
- * This file is part of ANIS SERVER API.
- *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-$app->get('/', App\Action\Root\RootAction::class);
-$app->get('/open-api', App\Action\Root\OpenApiAction::class);
-
-$app->group('/login', function (Slim\App $app) {
-    $app->map(['OPTIONS', 'POST'], '/register', App\Action\Login\RegisterAction::class);
-    $app->map(['OPTIONS', 'GET'], '/activate-account', App\Action\Login\ActivateAccountAction::class);
-    $app->map(['OPTIONS', 'POST'], '/token', App\Action\Login\TokenAction::class);
-    $app->map(['OPTIONS', 'POST'], '/new-password', App\Action\Login\NewPasswordAction::class);
-    $app->map(['OPTIONS', 'POST'], '/change-password', App\Action\Login\ChangePasswordAction::class);
-});
-
-$app->group('/admin', function (Slim\App $app) {
-    $app->map(['OPTIONS', 'GET'], '/user', App\Action\Admin\UserListAction::class);
-    $app->map(['OPTIONS', 'GET', 'PUT', 'DELETE'], '/user/{email}', App\Action\Admin\UserAction::class);
-    $app->group('/settings', function (Slim\App $app) {
-        $app->map(['OPTIONS', 'GET', 'POST'], '/select', App\Action\Admin\SelectListAction::class);
-        $app->map(['OPTIONS', 'GET', 'PUT', 'DELETE'], '/select/{id}', App\Action\Admin\SelectAction::class);
-        $app->map(['OPTIONS', 'GET', 'POST'], '/option', App\Action\Admin\OptionListAction::class);
-        $app->map(['OPTIONS', 'GET', 'PUT', 'DELETE'], '/option/{id}', App\Action\Admin\OptionAction::class);
-    });
-    $app->map(['OPTIONS', 'GET', 'POST'], '/instance', App\Action\Admin\InstanceListAction::class);
-    $app->map(['OPTIONS', 'GET', 'PUT', 'DELETE'], '/instance/{name}', App\Action\Admin\InstanceAction::class);
-    $app->map(['OPTIONS', 'GET'], '/instance/{name}/{action}', App\Action\Admin\MetamodelAction::class);
-});
-
-$app->group('/metadata', function (Slim\App $app) {
-    $app->group('/{instance}', function (Slim\App $app) {
-        $app->map(['OPTIONS', 'GET', 'POST'], '/database', App\Action\Meta\DatabaseListAction::class);
-        $app->map(['OPTIONS', 'GET', 'PUT', 'DELETE'], '/database/{id}', App\Action\Meta\DatabaseAction::class);
-        $app->map(['OPTIONS', 'GET'], '/database/{id}/table', App\Action\Meta\TableListAction::class);
-        $app->map(['OPTIONS', 'GET', 'POST'], '/project', App\Action\Meta\ProjectListAction::class);
-        $app->map(['OPTIONS', 'GET', 'PUT', 'DELETE'], '/project/{name}', App\Action\Meta\ProjectAction::class);
-        $app->map(['OPTIONS', 'GET', 'POST'], '/family/{type}', App\Action\Meta\FamilyListAction::class);
-        $app->map(['OPTIONS', 'GET', 'PUT', 'DELETE'], '/family/{type}/{id}', App\Action\Meta\FamilyAction::class);
-        $app->map(['OPTIONS', 'GET', 'POST'], '/output-category', App\Action\Meta\OutputCategoryListAction::class);
-        $app->map(
-            ['OPTIONS', 'GET', 'PUT', 'DELETE'],
-            '/output-category/{id}',
-            App\Action\Meta\OutputCategoryAction::class
-        );
-        $app->map(['OPTIONS', 'GET', 'POST'], '/dataset', App\Action\Meta\DatasetListAction::class);
-        $app->map(['OPTIONS', 'GET', 'PUT', 'DELETE'], '/dataset/{name}', App\Action\Meta\DatasetAction::class);
-        $app->map(['OPTIONS', 'GET'], '/dataset/{name}/attribute', App\Action\Meta\AttributeListAction::class);
-        $app->map(['OPTIONS', 'GET', 'PUT'], '/dataset/{name}/attribute/{id}', App\Action\Meta\AttributeAction::class);
-        $app->map(['OPTIONS', 'GET'], '/dataset/{name}/get-file/{path}', App\Action\Meta\FileProxyAction::class);
-        $app->map(['OPTIONS', 'GET', 'POST'], '/dataset/{name}/file', App\Action\Meta\FileListAction::class);
-        $app->map(['OPTIONS', 'GET', 'PUT', 'DELETE'], '/file/{id}', App\Action\Meta\FileAction::class);
-        $app->map(['OPTIONS', 'GET', 'POST'], '/group', App\Action\Meta\GroupListAction::class);
-        $app->map(['OPTIONS', 'GET', 'PUT', 'DELETE'], '/group/{id}', App\Action\Meta\GroupAction::class);
-    });
-});
-
-$app->map(['OPTIONS', 'GET'], '/search/{instance}/{type}/{dname}', App\Action\Search\SearchAction::class);
-$app->map(['OPTIONS', 'GET'], '/service/{instance}/{dname}', App\Action\Search\ServiceAction::class);
diff --git a/src/settings.php b/src/settings.php
deleted file mode 100644
index 4d4e029..0000000
--- a/src/settings.php
+++ /dev/null
@@ -1,53 +0,0 @@
-<?php declare(strict_types=1);
-/*
- * This file is part of ANIS SERVER API.
- *
- * (c) François Agneray <francois.agneray@lam.fr>
- * (c) Chrystel Moreau <chrystel.moreau@lam.fr>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-return [
-    'settings' => [
-        // app config
-        'encryption_key' => 'r3Q8C7LgIrRcTtI8I6EPzFwrDXJ4adgnGQ9V/pWVI8M=',
-        // slim framework settings
-        'displayErrorDetails' => (bool) getenv('SLIM_DISPLAY_ERROR_DETAILS'),
-        'determineRouteBeforeAppMiddleware' => false,
-        'addContentLengthHeader' => false, // Allow the web server to send the content-length header
-        // metadata settings (doctrine 2)
-        'admin_db' => [
-            'path_proxy' => getenv('METADATA_DOCTRINE_PATH_PROXY'),
-            'dev_mode' => getenv('METADATA_DOCTRINE_DEV_MODE'),
-            'connection_options' => [
-                'driver' => getenv('METADATA_DB_DRIVER'),
-                'path' => getenv('METADATA_DB_PATH'),
-                'host' => getenv('METADATA_DB_HOST'),
-                'port' => getenv('METADATA_DB_PORT'),
-                'dbname' => getenv('METADATA_DB_DBNAME'),
-                'user' => getenv('METADATA_DB_USER'),
-                'password' => getenv('METADATA_DB_PASSWORD')
-            ]
-        ],
-        // monolog settings
-        'logger' => [
-            'name' => getenv('LOGGER_NAME'),
-            'path' => getenv('LOGGER_PATH'),
-            'level' => getenv('LOGGER_LEVEL')
-        ],
-        // Swiftmailer settings
-        'mailer' => [
-            'host' => getenv('MAILER_HOST'),
-            'port' => getenv('MAILER_PORT')
-        ],
-        // RabbitMQ settings
-        'amqp' => [
-            'host' => getenv('AMQP_HOST'),
-            'port' => getenv('AMQP_PORT'),
-            'user' => getenv('AMQP_USER'),
-            'password' => getenv('AMQP_PASSWORD')
-        ]
-    ]
-];
diff --git a/tests/AbstractActionAdminTestCase.php b/tests/AbstractActionAdminTestCase.php
deleted file mode 100644
index 3bc0967..0000000
--- a/tests/AbstractActionAdminTestCase.php
+++ /dev/null
@@ -1,47 +0,0 @@
-<?php
-declare(strict_types=1);
-
-namespace App\Tests;
-
-use PHPUnit\DbUnit\TestCase;
-use PHPUnit\DbUnit\DataSet\YamlDataSet;
-use Doctrine\ORM\Tools\Setup;
-use Doctrine\ORM\EntityManager;
-use Doctrine\ORM\Tools\SchemaTool;
-
-abstract class AbstractActionAdminTestCase extends TestCase
-{
-    protected $entityManager;
-    protected $mailer;
-    protected $action;
-
-    protected function setUp(): void
-    {
-        parent::setUp();
-        $transport = new \Swift_Transport_SpoolTransport(
-            new \Swift_Events_SimpleEventDispatcher(),
-            new \Swift_MemorySpool
-        );
-        $this->mailer = new \Swift_Mailer($transport);
-    }
-
-    protected function getConnection()
-    {
-        $isDevMode = true;
-        $config = Setup::createAnnotationMetadataConfiguration(array(__DIR__ . "/../src/Entity/Admin"), $isDevMode);
-        $conn = array(
-            'driver' => 'pdo_sqlite',
-            'memory' => true
-        );
-        $this->entityManager = EntityManager::create($conn, $config);
-        $schemaTool = new SchemaTool($this->entityManager);
-        $schemaTool->createSchema($this->entityManager->getMetadataFactory()->getAllMetadata());
-        $pdo = $this->entityManager->getConnection()->getWrappedConnection();
-        return $this->createDefaultDBConnection($pdo, ':memory');
-    }
-
-    protected function getDataSet()
-    {
-        return new YamlDataset(__DIR__ . "/admin.yaml");
-    }
-}
diff --git a/tests/AbstractActionMetaTestCase.php b/tests/AbstractActionMetaTestCase.php
deleted file mode 100644
index 52cd5d2..0000000
--- a/tests/AbstractActionMetaTestCase.php
+++ /dev/null
@@ -1,56 +0,0 @@
-<?php
-declare(strict_types=1);
-
-namespace App\Tests;
-
-use PHPUnit\DbUnit\TestCase;
-use PHPUnit\DbUnit\DataSet\YamlDataSet;
-use Doctrine\ORM\Tools\Setup;
-use Doctrine\ORM\EntityManager;
-use Doctrine\ORM\Tools\SchemaTool;
-
-use App\Utils\MetaEntityManagerFactory;
-
-abstract class AbstractActionMetaTestCase extends TestCase
-{
-    protected $metaEntityManagerFactory;
-    protected $entityManager;
-    protected $mailer;
-    protected $action;
-
-    protected function setUp(): void
-    {
-        parent::setUp();
-        $transport = new \Swift_Transport_SpoolTransport(
-            new \Swift_Events_SimpleEventDispatcher(),
-            new \Swift_MemorySpool
-        );
-        $this->mailer = new \Swift_Mailer($transport);
-        $this->metaEntityManagerFactory = $this->getMockBuilder(MetaEntityManagerFactory::class)
-            ->disableOriginalConstructor()
-            ->setMethods(['getMetaEntityManager', 'createMetaEntityManager'])
-            ->getMock();
-        
-        $this->metaEntityManagerFactory->method('getMetaEntityManager')->willReturn($this->entityManager);
-    }
-
-    protected function getConnection()
-    {
-        $isDevMode = true;
-        $config = Setup::createAnnotationMetadataConfiguration(array(__DIR__ . "/../src/Entity/Metamodel"), $isDevMode);
-        $conn = array(
-            'driver' => 'pdo_sqlite',
-            'memory' => true
-        );
-        $this->entityManager = EntityManager::create($conn, $config);
-        $schemaTool = new SchemaTool($this->entityManager);
-        $schemaTool->createSchema($this->entityManager->getMetadataFactory()->getAllMetadata());
-        $pdo = $this->entityManager->getConnection()->getWrappedConnection();
-        return $this->createDefaultDBConnection($pdo, ':memory');
-    }
-
-    protected function getDataSet()
-    {
-        return new YamlDataset(__DIR__ . "/database.yaml");
-    }
-}
diff --git a/tests/Action/AttributeActionTest.php b/tests/Action/AttributeActionTest.php
new file mode 100644
index 0000000..4402516
--- /dev/null
+++ b/tests/Action/AttributeActionTest.php
@@ -0,0 +1,250 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Tests\Action;
+
+use PHPUnit\Framework\TestCase;
+use Nyholm\Psr7\ServerRequest;
+use Nyholm\Psr7\Response;
+use Slim\Exception\HttpNotFoundException;
+use Slim\Exception\HttpBadRequestException;
+use App\tests\EntityManagerBuilder;
+use App\Entity\Database;
+use App\Entity\Project;
+use App\Entity\DatasetFamily;
+use App\Entity\CriteriaFamily;
+use App\Entity\OutputFamily;
+use App\Entity\OutputCategory;
+use App\Entity\Dataset;
+use App\Entity\Attribute;
+
+final class AttributeActionTest extends TestCase
+{
+    private $action;
+    private $entityManager;
+
+    protected function setUp(): void
+    {
+        $this->entityManager = EntityManagerBuilder::getInstance();
+        $this->action = new \App\Action\AttributeAction($this->entityManager);
+    }
+
+    public function testOptionsHttpMethod(): void
+    {
+        $request = $this->getRequest('OPTIONS');
+        $response = ($this->action)($request, new Response(), array());
+        $this->assertSame($response->getHeaderLine('Access-Control-Allow-Methods'), 'GET, PUT, OPTIONS');
+    }
+
+    protected function tearDown(): void
+    {
+        $this->entityManager->getConnection()->close();
+    }
+
+    public function testAttributeIsNotFound(): void
+    {
+        $this->expectException(HttpNotFoundException::class);
+        $this->expectExceptionMessage('Attribute with dataset name obs_cat and attribute id 1 is not found');
+        $request = $this->getRequest('GET');
+        $response = ($this->action)($request, new Response(), array('name' => 'obs_cat', 'id' => 1));
+        $this->assertEquals(404, (int) $response->getStatusCode());
+    }
+
+    public function testGetAnAttributeById(): void
+    {
+        $attribute = $this->addAnAttribute();
+        $request = $this->getRequest('GET');
+        $response = ($this->action)($request, new Response(), array('name' => 'obs_cat', 'id' => 1));
+        $this->assertSame(json_encode($attribute), (string) $response->getBody());
+    }
+
+    public function testEditADatabase(): void
+    {
+        $this->addAnAttribute();
+        $this->addCriteriaFamily();
+        $this->addOutputCategory();
+        $fields = $this->getEditAttributeFields();
+        $request = $this->getRequest('PUT')->withParsedBody($fields);
+        $response = ($this->action)($request, new Response(), array('name' => 'obs_cat', 'id' => 1));
+        $this->assertSame(
+            json_encode(array_merge(['id' => 1, 'name' => 'id', 'table_name' => 'v_obs_cat'], $fields)),
+            (string) $response->getBody()
+        );
+    }
+
+    public function testEditADatabaseWithCriteriaFamilyAndOutputCategoryNull(): void
+    {
+        $this->addAnAttribute();
+        $fields = $this->getEditAttributeFields();
+        $fields['id_criteria_family'] = null;
+        $fields['id_output_category'] = null;
+        $request = $this->getRequest('PUT')->withParsedBody($fields);
+        $response = ($this->action)($request, new Response(), array('name' => 'obs_cat', 'id' => 1));
+        $this->assertSame(
+            json_encode(array_merge(['id' => 1, 'name' => 'id', 'table_name' => 'v_obs_cat'], $fields)),
+            (string) $response->getBody()
+        );
+    }
+
+    private function getRequest(string $method): ServerRequest
+    {
+        return new ServerRequest($method, '/dataset/obs_cat/attribute/1', array(
+            'Content-Type' => 'application/json'
+        ));
+    }
+
+    private function getEditAttributeFields(): array
+    {
+        return array(
+            'label' => 'ID',
+            'form_label' => 'ID',
+            'description' => 'ID description',
+            'output_display' => 20,
+            'criteria_display' => 10,
+            'search_flag' => 'ID',
+            'search_type' => null,
+            'type' => 'integer',
+            'operator' => null,
+            'min' => null,
+            'max' => null,
+            'placeholder_min' => null,
+            'placeholder_max' => null,
+            'uri_action' => null,
+            'renderer' => null,
+            'display_detail' => 10,
+            'selected' => false,
+            'order_by' => false,
+            'order_display' => null,
+            'detail' => false,
+            'renderer_detail' => null,
+            'options' => null,
+            'vo_utype' => null,
+            'vo_ucd' => null,
+            'vo_unit' => null,
+            'vo_description' => null,
+            'vo_datatype' => null,
+            'vo_size' => 0,
+            'id_criteria_family' => 1,
+            'id_output_category' => 1
+        );
+    }
+
+    private function addProject(): Project
+    {
+        $database = new Database();
+        $database->setLabel('Test1');
+        $database->setDbName('test1');
+        $database->setType('pgsql');
+        $database->setHost('db');
+        $database->setPort(5432);
+        $database->setLogin('test');
+        $database->setPassword('test');
+        $this->entityManager->persist($database);
+
+        $project = new Project('anis_project');
+        $project->setLabel('Test project');
+        $project->setDescription('Test description');
+        $project->setLink('http://test.com');
+        $project->setManager('User1');
+        $project->setDatabase($database);
+        $this->entityManager->persist($project);
+
+        return $project;
+    }
+
+    private function addDatasetFamily(): DatasetFamily
+    {
+        $family = new DatasetFamily();
+        $family->setLabel('Default dataset');
+        $family->setDisplay(10);
+        $this->entityManager->persist($family);
+
+        return $family;
+    }
+
+    private function addCriteriaFamily(): CriteriaFamily
+    {
+        $family = new CriteriaFamily();
+        $family->setLabel('Default criteria');
+        $family->setDisplay(10);
+        $this->entityManager->persist($family);
+        $this->entityManager->flush();
+
+        return $family;
+    }
+
+    private function addOutputCategory(): OutputCategory
+    {
+        $outputFamily = $this->addOutputFamily();
+
+        $outputCategory = new OutputCategory();
+        $outputCategory->setLabel('Default output category');
+        $outputCategory->setDisplay(10);
+        $outputCategory->setOutputFamily($outputFamily);
+        $this->entityManager->persist($outputCategory);
+        $this->entityManager->flush();
+        return $outputCategory;
+    }
+
+    private function addOutputFamily(): OutputFamily
+    {
+        $family = new OutputFamily();
+        $family->setLabel('Default output');
+        $family->setDisplay(10);
+        $this->entityManager->persist($family);
+
+        return $family;
+    }
+
+    private function addADataset(): Dataset
+    {
+        $project = $this->addProject();
+        $family = $this->addDatasetFamily();
+
+        $dataset = new Dataset('obs_cat');
+        $dataset->setTableRef('v_obs_cat');
+        $dataset->setLabel('Obscat label');
+        $dataset->setDescription('Obscat description');
+        $dataset->setDisplay(10);
+        $dataset->setCount(10000);
+        $dataset->setVo(false);
+        $dataset->setDataPath('/mnt/obs_cat');
+        $dataset->setSelectableRow(false);
+        $dataset->setProject($project);
+        $dataset->setDatasetFamily($family);
+        $this->entityManager->persist($dataset);
+        $this->entityManager->flush();
+
+        return $dataset;
+    }
+
+    private function addAnAttribute(): Attribute
+    {
+        $dataset = $this->addADataset();
+
+        $attribute = new Attribute(1, $dataset);
+        $attribute->setName('id');
+        $attribute->setTableName($dataset->getTableRef());
+        $attribute->setLabel('ID');
+        $attribute->setFormLabel('ID');
+        $attribute->setType('integer');
+        $attribute->setCriteriaDisplay(10);
+        $attribute->setOutputDisplay(10);
+        $attribute->setDisplayDetail(10);
+        $attribute->setOrderDisplay(10);
+        $attribute->setSelected(true);
+        $this->entityManager->persist($attribute);
+        $this->entityManager->flush();
+        
+        return $attribute;
+    }
+}
diff --git a/tests/Action/AttributeListActionTest.php b/tests/Action/AttributeListActionTest.php
new file mode 100644
index 0000000..f49816c
--- /dev/null
+++ b/tests/Action/AttributeListActionTest.php
@@ -0,0 +1,165 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Tests\Action;
+
+use PHPUnit\Framework\TestCase;
+use Nyholm\Psr7\ServerRequest;
+use Nyholm\Psr7\Response;
+use Slim\Exception\HttpBadRequestException;
+use Slim\Exception\HttpNotFoundException;
+use App\tests\EntityManagerBuilder;
+use App\Entity\Database;
+use App\Entity\Project;
+use App\Entity\DatasetFamily;
+use App\Entity\Dataset;
+use App\Entity\Attribute;
+
+final class AttributeListActionTest extends TestCase
+{
+    private $action;
+    private $entityManager;
+
+    protected function setUp(): void
+    {
+        $this->entityManager = EntityManagerBuilder::getInstance();
+        $this->action = new \App\Action\AttributeListAction($this->entityManager);
+    }
+
+    public function testOptionsHttpMethod(): void
+    {
+        $request = $this->getRequest('OPTIONS');
+        $response = ($this->action)($request, new Response(), array());
+        $this->assertSame($response->getHeaderLine('Access-Control-Allow-Methods'), 'GET, OPTIONS');
+    }
+
+    public function testDatasetIsNotFound(): void
+    {
+        $this->expectException(HttpNotFoundException::class);
+        $this->expectExceptionMessage('Dataset with name obs_cat is not found');
+        $request = $this->getRequest('GET');
+        $response = ($this->action)($request, new Response(), array('name' => 'obs_cat'));
+        $this->assertEquals(404, (int) $response->getStatusCode());
+    }
+
+    public function testGetAllAttributesOfADataset(): void
+    {
+        $attributes = $this->addAttributes();
+        $request = $this->getRequest('GET');
+        $response = ($this->action)($request, new Response(), array('name' => 'obs_cat'));
+        $this->assertSame(
+            json_encode($attributes),
+            (string) $response->getBody()
+        );
+    }
+
+    protected function tearDown(): void
+    {
+        $this->entityManager->getConnection()->close();
+    }
+
+    private function getRequest(string $method): ServerRequest
+    {
+        return new ServerRequest($method, '/dataset/obs_cat/attribute', array(
+            'Content-Type' => 'application/json'
+        ));
+    }
+
+    private function addProject(): Project
+    {
+        $database = new Database();
+        $database->setLabel('Test1');
+        $database->setDbName('test1');
+        $database->setType('pgsql');
+        $database->setHost('db');
+        $database->setPort(5432);
+        $database->setLogin('test');
+        $database->setPassword('test');
+        $this->entityManager->persist($database);
+
+        $project = new Project('anis_project');
+        $project->setLabel('Test project');
+        $project->setDescription('Test description');
+        $project->setLink('http://test.com');
+        $project->setManager('User1');
+        $project->setDatabase($database);
+        $this->entityManager->persist($project);
+
+        return $project;
+    }
+
+    private function addDatasetFamily(): DatasetFamily
+    {
+        $family = new DatasetFamily();
+        $family->setLabel('Default dataset');
+        $family->setDisplay(10);
+        $this->entityManager->persist($family);
+
+        return $family;
+    }
+
+    private function addADataset(): Dataset
+    {
+        $project = $this->addProject();
+        $family = $this->addDatasetFamily();
+
+        $dataset = new Dataset('obs_cat');
+        $dataset->setTableRef('v_obs_cat');
+        $dataset->setLabel('Obscat label');
+        $dataset->setDescription('Obscat description');
+        $dataset->setDisplay(10);
+        $dataset->setCount(10000);
+        $dataset->setVo(false);
+        $dataset->setDataPath('/mnt/obs_cat');
+        $dataset->setSelectableRow(false);
+        $dataset->setProject($project);
+        $dataset->setDatasetFamily($family);
+        $this->entityManager->persist($dataset);
+        $this->entityManager->flush();
+
+        return $dataset;
+    }
+
+    private function addAttributes(): array
+    {
+        $dataset = $this->addADataset();
+
+        $attribute1 = new Attribute(1, $dataset);
+        $attribute1->setName('id');
+        $attribute1->setTableName($dataset->getTableRef());
+        $attribute1->setLabel('ID');
+        $attribute1->setFormLabel('ID');
+        $attribute1->setType('integer');
+        $attribute1->setCriteriaDisplay(10);
+        $attribute1->setOutputDisplay(10);
+        $attribute1->setDisplayDetail(10);
+        $attribute1->setOrderDisplay(10);
+        $attribute1->setSelected(true);
+        $this->entityManager->persist($attribute1);
+
+        $attribute2 = new Attribute(2, $dataset);
+        $attribute2->setName('ra');
+        $attribute2->setTableName($dataset->getTableRef());
+        $attribute2->setLabel('RA');
+        $attribute2->setFormLabel('RA');
+        $attribute2->setType('float');
+        $attribute2->setCriteriaDisplay(20);
+        $attribute2->setOutputDisplay(20);
+        $attribute2->setDisplayDetail(20);
+        $attribute2->setOrderDisplay(20);
+        $attribute2->setSelected(true);
+        $this->entityManager->persist($attribute2);
+
+        $this->entityManager->flush();
+        return array($attribute1, $attribute2);
+    }
+}
diff --git a/tests/Action/DatabaseActionTest.php b/tests/Action/DatabaseActionTest.php
new file mode 100644
index 0000000..ffb140d
--- /dev/null
+++ b/tests/Action/DatabaseActionTest.php
@@ -0,0 +1,122 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Tests\Action;
+
+use PHPUnit\Framework\TestCase;
+use Nyholm\Psr7\ServerRequest;
+use Nyholm\Psr7\Response;
+use Slim\Exception\HttpNotFoundException;
+use Slim\Exception\HttpBadRequestException;
+use App\tests\EntityManagerBuilder;
+use App\Entity\Database;
+
+final class DatabaseActionTest extends TestCase
+{
+    private $action;
+    private $entityManager;
+
+    protected function setUp(): void
+    {
+        $this->entityManager = EntityManagerBuilder::getInstance();
+        $this->action = new \App\Action\DatabaseAction($this->entityManager);
+    }
+
+    public function testOptionsHttpMethod(): void
+    {
+        $request = $this->getRequest('OPTIONS');
+        $response = ($this->action)($request, new Response(), array());
+        $this->assertSame($response->getHeaderLine('Access-Control-Allow-Methods'), 'GET, PUT, DELETE, OPTIONS');
+    }
+
+    public function testDatabaseIsNotFound(): void
+    {
+        $this->expectException(HttpNotFoundException::class);
+        $this->expectExceptionMessage('Database with id 1 is not found');
+        $request = $this->getRequest('GET');
+        $response = ($this->action)($request, new Response(), array('id' => 1));
+        $this->assertEquals(404, (int) $response->getStatusCode());
+    }
+
+    public function testGetADatabaseById(): void
+    {
+        $database = $this->addADatabase();
+        $request = $this->getRequest('GET');
+        $response = ($this->action)($request, new Response(), array('id' => 1));
+        $this->assertSame(json_encode($database), (string) $response->getBody());
+    }
+
+    public function testEditADatabaseEmptyLabelField(): void
+    {
+        $this->addADatabase();
+        $this->expectException(HttpBadRequestException::class);
+        $this->expectExceptionMessage('Param label needed to edit the database');
+        $request = $this->getRequest('PUT')->withParsedBody(array());
+        $response = ($this->action)($request, new Response(), array('id' => 1));
+        $this->assertEquals(400, (int) $response->getStatusCode());
+    }
+
+    public function testEditADatabase(): void
+    {
+        $fields = array(
+            'label' => 'New_label',
+            'dbname' => 'test2',
+            'dbtype' => 'pgsql',
+            'dbhost' => 'db',
+            'dbport' => 5432,
+            'dblogin' => 'test',
+            'dbpassword' => 'test'
+        );
+        $this->addADatabase();
+        $request = $this->getRequest('PUT')->withParsedBody($fields);
+        $response = ($this->action)($request, new Response(), array('id' => 1));
+        $this->assertSame(json_encode(array_merge(['id' => 1], $fields)), (string) $response->getBody());
+    }
+
+    public function testDeleteADatabase(): void
+    {
+        $this->addADatabase();
+        $request = $this->getRequest('DELETE');
+        $response = ($this->action)($request, new Response(), array('id' => 1));
+        $this->assertSame(
+            json_encode(array('message' => 'Database with id 1 is removed!')),
+            (string) $response->getBody()
+        );
+    }
+
+    protected function tearDown(): void
+    {
+        $this->entityManager->getConnection()->close();
+    }
+
+    private function getRequest(string $method): ServerRequest
+    {
+        return new ServerRequest($method, '/database/1', array(
+            'Content-Type' => 'application/json'
+        ));
+    }
+
+    private function addADatabase(): Database
+    {
+        $database = new Database();
+        $database->setLabel('Test1');
+        $database->setDbName('test1');
+        $database->setType('pgsql');
+        $database->setHost('db');
+        $database->setPort(5432);
+        $database->setLogin('test');
+        $database->setPassword('test');
+        $this->entityManager->persist($database);
+        $this->entityManager->flush();
+        return $database;
+    }
+}
diff --git a/tests/Action/DatabaseListActionTest.php b/tests/Action/DatabaseListActionTest.php
new file mode 100644
index 0000000..9344073
--- /dev/null
+++ b/tests/Action/DatabaseListActionTest.php
@@ -0,0 +1,117 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Tests\Action;
+
+use PHPUnit\Framework\TestCase;
+use Nyholm\Psr7\ServerRequest;
+use Nyholm\Psr7\Response;
+use Slim\Exception\HttpBadRequestException;
+use App\tests\EntityManagerBuilder;
+use App\Entity\Database;
+
+final class DatabaseListActionTest extends TestCase
+{
+    private $action;
+    private $entityManager;
+
+    protected function setUp(): void
+    {
+        $this->entityManager = EntityManagerBuilder::getInstance();
+        $this->action = new \App\Action\DatabaseListAction($this->entityManager);
+    }
+
+    public function testOptionsHttpMethod(): void
+    {
+        $request = $this->getRequest('OPTIONS');
+        $response = ($this->action)($request, new Response(), array());
+        $this->assertSame($response->getHeaderLine('Access-Control-Allow-Methods'), 'GET, POST, OPTIONS');
+    }
+
+    public function testGetAllDatabases(): void
+    {
+        $databases = $this->addDatabases();
+        $request = $this->getRequest('GET');
+        $response = ($this->action)($request, new Response(), array());
+        $this->assertSame(
+            json_encode($databases),
+            (string) $response->getBody()
+        );
+    }
+
+    public function testAddANewDatabaseEmptyLabelField(): void
+    {
+        $this->expectException(HttpBadRequestException::class);
+        $this->expectExceptionMessage('Param label needed to add a new database');
+        $request = $this->getRequest('POST')->withParsedBody(array());
+        $response = ($this->action)($request, new Response(), array());
+        $this->assertEquals(400, (int) $response->getStatusCode());
+    }
+
+    public function testAddANewDatabase(): void
+    {
+        $fields = array(
+            'label' => 'Test1',
+            'dbname' => 'test1',
+            'dbtype' => 'pgsql',
+            'dbhost' => 'db',
+            'dbport' => 5432,
+            'dblogin' => 'test',
+            'dbpassword' => 'test'
+        );
+        $request = $this->getRequest('POST')->withParsedBody($fields);
+        $response = ($this->action)($request, new Response(), array());
+        $this->assertSame(
+            json_encode(array_merge(['id' => 1], $fields)),
+            (string) $response->getBody()
+        );
+        $this->assertEquals(201, (int) $response->getStatusCode());
+    }
+
+    protected function tearDown(): void
+    {
+        $this->entityManager->getConnection()->close();
+    }
+
+    private function getRequest(string $method): ServerRequest
+    {
+        return new ServerRequest($method, '/database', array(
+            'Content-Type' => 'application/json'
+        ));
+    }
+
+    private function addDatabases(): array
+    {
+        $database = new Database();
+        $database->setLabel('Test1');
+        $database->setDbName('test1');
+        $database->setType('pgsql');
+        $database->setHost('db');
+        $database->setPort(5432);
+        $database->setLogin('test');
+        $database->setPassword('test');
+        $this->entityManager->persist($database);
+
+        $database2 = new Database();
+        $database2->setLabel('Test2');
+        $database2->setDbName('test2');
+        $database2->setType('pgsql');
+        $database2->setHost('db');
+        $database2->setPort(5432);
+        $database2->setLogin('test');
+        $database2->setPassword('test');
+        $this->entityManager->persist($database2);
+
+        $this->entityManager->flush();
+        return array($database, $database2);
+    }
+}
diff --git a/tests/Action/DatasetActionTest.php b/tests/Action/DatasetActionTest.php
new file mode 100644
index 0000000..d3f4f54
--- /dev/null
+++ b/tests/Action/DatasetActionTest.php
@@ -0,0 +1,196 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Tests\Action;
+
+use PHPUnit\Framework\TestCase;
+use Nyholm\Psr7\ServerRequest;
+use Nyholm\Psr7\Response;
+use Slim\Exception\HttpNotFoundException;
+use Slim\Exception\HttpBadRequestException;
+use App\tests\EntityManagerBuilder;
+use App\Entity\Database;
+use App\Entity\Project;
+use App\Entity\DatasetFamily;
+use App\Entity\Dataset;
+
+final class DatasetActionTest extends TestCase
+{
+    private $action;
+    private $entityManager;
+
+    protected function setUp(): void
+    {
+        $this->entityManager = EntityManagerBuilder::getInstance();
+        $this->action = new \App\Action\DatasetAction($this->entityManager);
+    }
+
+    public function testOptionsHttpMethod(): void
+    {
+        $request = $this->getRequest('OPTIONS');
+        $response = ($this->action)($request, new Response(), array());
+        $this->assertSame($response->getHeaderLine('Access-Control-Allow-Methods'), 'GET, PUT, DELETE, OPTIONS');
+    }
+
+    public function testDatasetIsNotFound(): void
+    {
+        $this->expectException(HttpNotFoundException::class);
+        $this->expectExceptionMessage('Dataset with name obs_cat is not found');
+        $request = $this->getRequest('GET');
+        $response = ($this->action)($request, new Response(), array('name' => 'obs_cat'));
+        $this->assertEquals(404, (int) $response->getStatusCode());
+    }
+
+    public function testGetADatasetByName(): void
+    {
+        $dataset = $this->addADataset();
+        $request = $this->getRequest('GET');
+        $response = ($this->action)($request, new Response(), array('name' => 'obs_cat'));
+        $this->assertSame(json_encode($dataset, JSON_UNESCAPED_SLASHES), (string) $response->getBody());
+    }
+
+    public function testEditADatasetEmptyLabelField(): void
+    {
+        $dataset = $this->addADataset();
+        $this->expectException(HttpBadRequestException::class);
+        $this->expectExceptionMessage('Param label needed to edit the dataset');
+        $request = $this->getRequest('PUT')->withParsedBody(array());
+        $response = ($this->action)($request, new Response(), array('name' => 'obs_cat'));
+        $this->assertEquals(400, (int) $response->getStatusCode());
+    }
+
+    public function testEditADatasetFamilyNotFound(): void
+    {
+        $dataset = $this->addADataset();
+        $this->expectException(HttpBadRequestException::class);
+        $this->expectExceptionMessage('Dataset family with id 2 is not found');
+        $fields = $this->getEditDatasetFields();
+        $fields['id_dataset_family'] = 2;
+        $request = $this->getRequest('PUT')->withParsedBody($fields);
+        $response = ($this->action)($request, new Response(), array('name' => 'obs_cat'));
+        $this->assertEquals(400, (int) $response->getStatusCode());
+    }
+
+    public function testEditADataset(): void
+    {
+        $this->addADataset();
+        $fields = $this->getEditDatasetFields();
+        $request = $this->getRequest('PUT')->withParsedBody($fields);
+        $response = ($this->action)($request, new Response(), array('name' => 'obs_cat'));
+        unset($fields['id_dataset_family']);
+        $this->assertSame(
+            json_encode(
+                array_merge(
+                    ['name' => 'obs_cat', 'table_ref' => 'v_obs_cat'],
+                    $fields,
+                    ['project_name' => 'anis_project', 'id_dataset_family' => 1]
+                ),
+                JSON_UNESCAPED_SLASHES
+            ),
+            (string) $response->getBody()
+        );
+        $this->assertEquals(200, (int) $response->getStatusCode());
+    }
+
+    public function testDeleteADatabase(): void
+    {
+        $this->addADataset();
+        $request = $this->getRequest('DELETE');
+        $response = ($this->action)($request, new Response(), array('name' => 'obs_cat'));
+        $this->assertSame(
+            json_encode(array('message' => 'Dataset with name obs_cat is removed!')),
+            (string) $response->getBody()
+        );
+    }
+
+    protected function tearDown(): void
+    {
+        $this->entityManager->getConnection()->close();
+    }
+
+    private function getRequest(string $method): ServerRequest
+    {
+        return new ServerRequest($method, '/dataset/obs_cat', array(
+            'Content-Type' => 'application/json'
+        ));
+    }
+
+    private function getEditDatasetFields(): array
+    {
+        return array(
+            'label' => 'Dataset1 label modified',
+            'description' => 'Dataset1 description',
+            'display' => 20,
+            'count' => 5000,
+            'vo' => false,
+            'data_path' => '/mnt/dataset1',
+            'selectable_row' => false,
+            'id_dataset_family' => 1
+        );
+    }
+
+    private function addProject(): Project
+    {
+        $database = new Database();
+        $database->setLabel('Test1');
+        $database->setDbName('test1');
+        $database->setType('pgsql');
+        $database->setHost('db');
+        $database->setPort(5432);
+        $database->setLogin('test');
+        $database->setPassword('test');
+        $this->entityManager->persist($database);
+
+        $project = new Project('anis_project');
+        $project->setLabel('Test project');
+        $project->setDescription('Test description');
+        $project->setLink('http://test.com');
+        $project->setManager('User1');
+        $project->setDatabase($database);
+        $this->entityManager->persist($project);
+
+        $this->entityManager->flush();
+        return $project;
+    }
+
+    private function addDatasetFamily(): DatasetFamily
+    {
+        $family = new DatasetFamily();
+        $family->setLabel('Default dataset');
+        $family->setDisplay(10);
+        $this->entityManager->persist($family);
+
+        $this->entityManager->flush();
+        return $family;
+    }
+
+    private function addADataset(): Dataset
+    {
+        $project = $this->addProject();
+        $family = $this->addDatasetFamily();
+
+        $dataset = new Dataset('obs_cat');
+        $dataset->setTableRef('v_obs_cat');
+        $dataset->setLabel('Obscat label');
+        $dataset->setDescription('Obscat description');
+        $dataset->setDisplay(10);
+        $dataset->setCount(10000);
+        $dataset->setVo(false);
+        $dataset->setDataPath('/mnt/obs_cat');
+        $dataset->setSelectableRow(false);
+        $dataset->setProject($project);
+        $dataset->setDatasetFamily($family);
+        $this->entityManager->persist($dataset);
+        $this->entityManager->flush();
+        return $dataset;
+    }
+}
diff --git a/tests/Action/DatasetListActionTest.php b/tests/Action/DatasetListActionTest.php
new file mode 100644
index 0000000..dfcea82
--- /dev/null
+++ b/tests/Action/DatasetListActionTest.php
@@ -0,0 +1,234 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Tests\Action;
+
+use PHPUnit\Framework\TestCase;
+use Nyholm\Psr7\ServerRequest;
+use Nyholm\Psr7\Response;
+use Slim\Exception\HttpBadRequestException;
+use Doctrine\DBAL\Connection;
+use Doctrine\DBAL\Schema\SqliteSchemaManager;
+use Doctrine\DBAL\Schema\Column;
+use App\Utils\DBALConnectionFactory;
+use Doctrine\DBAL\Types\Type;
+use App\tests\EntityManagerBuilder;
+use App\Entity\Database;
+use App\Entity\Project;
+use App\Entity\DatasetFamily;
+use App\Entity\Dataset;
+
+final class DatasetListActionTest extends TestCase
+{
+    private $action;
+    private $entityManager;
+
+    protected function setUp(): void
+    {
+        $this->entityManager = EntityManagerBuilder::getInstance();
+        $this->action = new \App\Action\DatasetListAction($this->entityManager, $this->getConnectionFactory());
+    }
+
+    public function testOptionsHttpMethod(): void
+    {
+        $request = $this->getRequest('OPTIONS');
+        $response = ($this->action)($request, new Response(), array());
+        $this->assertSame($response->getHeaderLine('Access-Control-Allow-Methods'), 'GET, POST, OPTIONS');
+    }
+
+    public function testGetAllDatasets(): void
+    {
+        $datasets = $this->addDatasets();
+        $request = $this->getRequest('GET');
+        $response = ($this->action)($request, new Response(), array());
+        $this->assertSame(
+            json_encode($datasets),
+            (string) $response->getBody()
+        );
+    }
+
+    public function testAddANewDatasetEmptyNameField(): void
+    {
+        $this->expectException(HttpBadRequestException::class);
+        $this->expectExceptionMessage('Param name needed to add a new dataset');
+        $request = $this->getRequest('POST')->withParsedBody(array());
+        $response = ($this->action)($request, new Response(), array());
+        $this->assertEquals(400, (int) $response->getStatusCode());
+    }
+
+    public function testAddANewDatasetProjectNotFound(): void
+    {
+        $this->expectException(HttpBadRequestException::class);
+        $this->expectExceptionMessage('Project with name anis_project is not found');
+        $fields = $this->getNewDatasetFields();
+        $request = $this->getRequest('POST')->withParsedBody($fields);
+        $response = ($this->action)($request, new Response(), array());
+        $this->assertEquals(400, (int) $response->getStatusCode());
+    }
+
+    public function testAddANewDatasetFamilyNotFound(): void
+    {
+        $this->addProject();
+        $this->expectException(HttpBadRequestException::class);
+        $this->expectExceptionMessage('Dataset family with id 1 is not found');
+        $fields = $this->getNewDatasetFields();
+        $request = $this->getRequest('POST')->withParsedBody($fields);
+        $response = ($this->action)($request, new Response(), array());
+        $this->assertEquals(400, (int) $response->getStatusCode());
+    }
+
+    public function testAddANewDataset(): void
+    {
+        $this->addProject();
+        $this->addDatasetFamily();
+        $fields = $this->getNewDatasetFields();
+        $request = $this->getRequest('POST')->withParsedBody($fields);
+        $response = ($this->action)($request, new Response(), array());
+        $this->assertSame(
+            json_encode($fields),
+            (string) $response->getBody()
+        );
+        $this->assertEquals(201, (int) $response->getStatusCode());
+    }
+
+    protected function tearDown(): void
+    {
+        $this->entityManager->getConnection()->close();
+    }
+
+    private function getRequest(string $method): ServerRequest
+    {
+        return new ServerRequest($method, '/dataset', array(
+            'Content-Type' => 'application/json'
+        ));
+    }
+
+    private function getNewDatasetFields(): array
+    {
+        return array(
+            'name' => 'dataset1',
+            'table_ref' => 'table1',
+            'label' => 'Dataset1 label',
+            'description' => 'Dataset1 description',
+            'display' => 10,
+            'count' => 10000,
+            'vo' => false,
+            'data_path' => '/mnt/dataset1',
+            'selectable_row' => false,
+            'project_name' => 'anis_project',
+            'id_dataset_family' => 1
+        );
+    }
+
+    private function addProject(): Project
+    {
+        $database = new Database();
+        $database->setLabel('Test1');
+        $database->setDbName('test1');
+        $database->setType('pgsql');
+        $database->setHost('db');
+        $database->setPort(5432);
+        $database->setLogin('test');
+        $database->setPassword('test');
+        $this->entityManager->persist($database);
+
+        $project = new Project('anis_project');
+        $project->setLabel('Test project');
+        $project->setDescription('Test description');
+        $project->setLink('http://test.com');
+        $project->setManager('User1');
+        $project->setDatabase($database);
+        $this->entityManager->persist($project);
+
+        $this->entityManager->flush();
+        return $project;
+    }
+
+    private function addDatasetFamily(): DatasetFamily
+    {
+        $family = new DatasetFamily();
+        $family->setLabel('Default dataset');
+        $family->setDisplay(10);
+        $this->entityManager->persist($family);
+
+        $this->entityManager->flush();
+        return $family;
+    }
+
+    private function addDatasets(): array
+    {
+        $project = $this->addProject();
+        $family = $this->addDatasetFamily();
+
+        $dataset1 = new Dataset('dataset1');
+        $dataset1->setTableRef('table1');
+        $dataset1->setLabel('Dataset1 label');
+        $dataset1->setDescription('Dataset1 description');
+        $dataset1->setDisplay(10);
+        $dataset1->setCount(10000);
+        $dataset1->setVo(false);
+        $dataset1->setDataPath('/mnt/dataset1');
+        $dataset1->setSelectableRow(false);
+        $dataset1->setProject($project);
+        $dataset1->setDatasetFamily($family);
+        $this->entityManager->persist($dataset1);
+
+        $dataset2 = new Dataset('dataset2');
+        $dataset2->setTableRef('table2');
+        $dataset2->setLabel('Dataset2 label');
+        $dataset2->setDescription('Dataset2 description');
+        $dataset2->setDisplay(20);
+        $dataset2->setCount(5000);
+        $dataset2->setVo(false);
+        $dataset2->setDataPath('/mnt/dataset2');
+        $dataset2->setSelectableRow(false);
+        $dataset2->setProject($project);
+        $dataset2->setDatasetFamily($family);
+        $this->entityManager->persist($dataset2);
+
+        $this->entityManager->flush();
+        return array($dataset1, $dataset2);
+    }
+
+    private function getConnectionFactory(): DBALConnectionFactory
+    {
+        $schemaManager = $this->getMockBuilder(SqliteSchemaManager::class)
+            ->disableOriginalConstructor()
+            ->setMethods(['listTableColumns'])
+            ->getMock();
+
+        $schemaManager->method('listTableColumns')
+            ->will($this->returnValue(array(
+                new Column('id', Type::getType(Type::INTEGER)),
+                new Column('ra', Type::getType(Type::FLOAT)),
+                new Column('dec', Type::getType(TYPE::FLOAT))
+            )));
+
+        $connection = $this->getMockBuilder(Connection::class)
+            ->disableOriginalConstructor()
+            ->setMethods(['getSchemaManager'])
+            ->getMock();
+
+        $connection->method('getSchemaManager')
+            ->will($this->returnValue($schemaManager));
+
+        $connectionFactory = $this->getMockBuilder(DBALConnectionFactory::class)
+            ->disableOriginalConstructor()
+            ->setMethods(['create'])
+            ->getMock();
+
+        $connectionFactory->method('create')
+            ->will($this->returnValue($connection));
+
+        return $connectionFactory;
+    }
+}
diff --git a/tests/Action/FamilyActionTest.php b/tests/Action/FamilyActionTest.php
new file mode 100644
index 0000000..313d9a2
--- /dev/null
+++ b/tests/Action/FamilyActionTest.php
@@ -0,0 +1,124 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Tests\Action;
+
+use PHPUnit\Framework\TestCase;
+use Nyholm\Psr7\ServerRequest;
+use Nyholm\Psr7\Response;
+use Slim\Exception\HttpNotFoundException;
+use Slim\Exception\HttpBadRequestException;
+use App\tests\EntityManagerBuilder;
+use App\Entity\DatasetFamily;
+
+final class FamilyActionTest extends TestCase
+{
+    private $action;
+    private $entityManager;
+
+    protected function setUp(): void
+    {
+        $this->entityManager = EntityManagerBuilder::getInstance();
+        $this->action = new \App\Action\FamilyAction($this->entityManager);
+    }
+
+    public function testOptionsHttpMethod(): void
+    {
+        $request = $this->getRequest('OPTIONS');
+        $response = ($this->action)($request, new Response(), array());
+        $this->assertSame($response->getHeaderLine('Access-Control-Allow-Methods'), 'GET, PUT, DELETE, OPTIONS');
+    }
+
+    public function testTypeIsNotDefined(): void
+    {
+        $this->expectException(HttpBadRequestException::class);
+        $this->expectExceptionMessage('Type undifined is not defined');
+        $request = $this->getRequest('GET');
+        $response = ($this->action)($request, new Response(), array('type' => 'undifined'));
+        $this->assertEquals(400, (int) $response->getStatusCode());
+    }
+
+    public function testDatasetFamilyIsNotFound(): void
+    {
+        $this->expectException(HttpNotFoundException::class);
+        $this->expectExceptionMessage('Dataset family with id 1 is not found');
+        $request = $this->getRequest('GET');
+        $response = ($this->action)($request, new Response(), array('type' => 'dataset', 'id' => 1));
+        $this->assertEquals(404, (int) $response->getStatusCode());
+    }
+
+    public function testGetADatasetFamilyById(): void
+    {
+        $family = $this->addADatasetFamily();
+        $request = $this->getRequest('GET');
+        $response = ($this->action)($request, new Response(), array('type' => 'dataset', 'id' => 1));
+        $this->assertSame(json_encode($family), (string) $response->getBody());
+    }
+
+    public function testEditADatasetFamilyEmptyLabelField(): void
+    {
+        $this->addADatasetFamily();
+        $this->expectException(HttpBadRequestException::class);
+        $this->expectExceptionMessage('Param label needed to edit the dataset family');
+        $request = $this->getRequest('PUT')->withParsedBody(array());
+        $response = ($this->action)($request, new Response(), array('type' => 'dataset', 'id' => 1));
+        $this->assertEquals(400, (int) $response->getStatusCode());
+    }
+
+    public function testEditADatasetFamily(): void
+    {
+        $fields = array(
+            'label' => 'New_label',
+            'display' => 20
+        );
+        $this->addADatasetFamily();
+        $request = $this->getRequest('PUT')->withParsedBody($fields);
+        $response = ($this->action)($request, new Response(), array('type' => 'dataset', 'id' => 1));
+        $this->assertSame(
+            json_encode(array_merge(['id' => 1], $fields, ['type' => 'dataset', 'datasets' => array()])),
+            (string) $response->getBody()
+        );
+    }
+
+    public function testDeleteADatasetFamily(): void
+    {
+        $this->addADatasetFamily();
+        $request = $this->getRequest('DELETE');
+        $response = ($this->action)($request, new Response(), array('type' => 'dataset', 'id' => 1));
+        $this->assertSame(
+            json_encode(array('message' => 'Dataset family with id 1 is removed!')),
+            (string) $response->getBody()
+        );
+    }
+
+    protected function tearDown(): void
+    {
+        $this->entityManager->getConnection()->close();
+    }
+
+    private function getRequest(string $method): ServerRequest
+    {
+        return new ServerRequest($method, '/family/dataset/1', array(
+            'Content-Type' => 'application/json'
+        ));
+    }
+
+    private function addADatasetFamily(): DatasetFamily
+    {
+        $family = new DatasetFamily();
+        $family->setLabel('Default dataset');
+        $family->setDisplay(10);
+        $this->entityManager->persist($family);
+        $this->entityManager->flush();
+        return $family;
+    }
+}
diff --git a/tests/Action/FamilyListActionTest.php b/tests/Action/FamilyListActionTest.php
new file mode 100644
index 0000000..2c7ab2b
--- /dev/null
+++ b/tests/Action/FamilyListActionTest.php
@@ -0,0 +1,111 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Tests\Action;
+
+use PHPUnit\Framework\TestCase;
+use Nyholm\Psr7\ServerRequest;
+use Nyholm\Psr7\Response;
+use Slim\Exception\HttpBadRequestException;
+use App\tests\EntityManagerBuilder;
+use App\Entity\DatasetFamily;
+
+final class FamilyListActionTest extends TestCase
+{
+    private $action;
+    private $entityManager;
+
+    protected function setUp(): void
+    {
+        $this->entityManager = EntityManagerBuilder::getInstance();
+        $this->action = new \App\Action\FamilyListAction($this->entityManager);
+    }
+
+    public function testOptionsHttpMethod(): void
+    {
+        $request = $this->getRequest('OPTIONS');
+        $response = ($this->action)($request, new Response(), array());
+        $this->assertSame($response->getHeaderLine('Access-Control-Allow-Methods'), 'GET, POST, OPTIONS');
+    }
+
+    public function testTypeIsNotDefined(): void
+    {
+        $this->expectException(HttpBadRequestException::class);
+        $this->expectExceptionMessage('Type undifined is not defined');
+        $request = $this->getRequest('GET');
+        $response = ($this->action)($request, new Response(), array('type' => 'undifined'));
+        $this->assertEquals(400, (int) $response->getStatusCode());
+    }
+
+    public function testGetAllDatasetFamilies(): void
+    {
+        $families = $this->addDatasetFamilies();
+        $request = $this->getRequest('GET');
+        $response = ($this->action)($request, new Response(), array('type' => 'dataset'));
+        $this->assertSame(
+            json_encode($families),
+            (string) $response->getBody()
+        );
+    }
+
+    public function testAddANewFamilyEmptyLabelField(): void
+    {
+        $this->expectException(HttpBadRequestException::class);
+        $this->expectExceptionMessage('Param label needed to add a new family');
+        $request = $this->getRequest('POST')->withParsedBody(array());
+        $response = ($this->action)($request, new Response(), array('type' => 'dataset'));
+        $this->assertEquals(400, (int) $response->getStatusCode());
+    }
+
+    public function testAddANewDatabase(): void
+    {
+        $fields = array(
+            'label' => 'Default family',
+            'display' => 10
+        );
+        $request = $this->getRequest('POST')->withParsedBody($fields);
+        $response = ($this->action)($request, new Response(), array('type' => 'dataset'));
+        $this->assertSame(
+            json_encode(array_merge(['id' => 1], $fields, ['type' => 'dataset', 'datasets' => array()])),
+            (string) $response->getBody()
+        );
+        $this->assertEquals(201, (int) $response->getStatusCode());
+    }
+
+    protected function tearDown(): void
+    {
+        $this->entityManager->getConnection()->close();
+    }
+
+    private function getRequest(string $method): ServerRequest
+    {
+        return new ServerRequest($method, '/family/dataset', array(
+            'Content-Type' => 'application/json'
+        ));
+    }
+
+    private function addDatasetFamilies(): array
+    {
+        $family1 = new DatasetFamily();
+        $family1->setLabel('Default dataset');
+        $family1->setDisplay(10);
+        $this->entityManager->persist($family1);
+
+        $family2 = new DatasetFamily();
+        $family2->setLabel('My family dataset');
+        $family2->setDisplay(20);
+        $this->entityManager->persist($family2);
+
+        $this->entityManager->flush();
+        return array($family1, $family2);
+    }
+}
diff --git a/tests/Action/Login/ActivateAccountActionTest.php b/tests/Action/Login/ActivateAccountActionTest.php
deleted file mode 100644
index 31640b4..0000000
--- a/tests/Action/Login/ActivateAccountActionTest.php
+++ /dev/null
@@ -1,86 +0,0 @@
-<?php
-declare(strict_types=1);
-
-namespace App\Tests\Action\Login;
-
-use Psr\Http\Message\ServerRequestInterface as Request;
-
-use App\Tests\AbstractActionAdminTestCase;
-
-final class ActivateAccountActionTest extends AbstractActionAdminTestCase
-{
-    protected function setUp(): void
-    {
-        parent::setUp();
-        $this->action = new \App\Action\Login\ActivateAccountAction(
-            new \Psr\Log\NullLogger(),
-            $this->entityManager,
-            $this->mailer
-        );
-    }
-
-    protected function getRequest(string $queryString): Request
-    {
-        $environment = \Slim\Http\Environment::mock([
-            'REQUEST_METHOD' => 'GET',
-            'REQUEST_URI' => '/activate-account',
-            'QUERY_STRING' => $queryString
-        ]);
-        return \Slim\Http\Request::createFromEnvironment($environment);
-    }
-
-    public function testEmailIsEmpty(): void
-    {
-        $request = $this->getRequest('activation_key=noioioi');
-        $response = ($this->action)($request, new \Slim\Http\Response(), array());
-        $this->assertEquals(400, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array(
-            'error' => 'Invalid request',
-            'error_description' => 'Param email needed to activate a new user account'
-        )));
-    }
-
-    public function testActivationKeyIsEmpty(): void
-    {
-        $request = $this->getRequest('email=user1@anis.fr');
-        $response = ($this->action)($request, new \Slim\Http\Response(), array());
-        $this->assertEquals(400, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array(
-            'error' => 'Invalid request',
-            'error_description' => 'Param activation_key needed to activate a new user account'
-        )));
-    }
-
-    public function testUserDoNotExist(): void
-    {
-        $request = $this->getRequest('email=user99@anis.fr&activation_key=noioioi');
-        $response = ($this->action)($request, new \Slim\Http\Response(), array());
-        $this->assertEquals(400, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array(
-            'error' => 'Invalid user',
-            'error_description' => 'No account is identified with this email address'
-        )));
-    }
-
-    public function testBadActivationKey(): void
-    {
-        $request = $this->getRequest('email=user1@anis.fr&activation_key=badkey');
-        $response = ($this->action)($request, new \Slim\Http\Response(), array());
-        $this->assertEquals(400, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array(
-            'error' => 'Invalid activation key',
-            'error_description' => 'Bad activation key; Unable to activate account'
-        )));
-    }
-
-    public function testActivateAccount(): void
-    {
-        $request = $this->getRequest('email=user1@anis.fr&activation_key=noioioi');
-        $response = ($this->action)($request, new \Slim\Http\Response(), array());
-        $this->assertEquals(200, (int) $response->getStatusCode());
-        $this->assertSame(
-            (string) $response->getBody(),
-            '{"email":"user1@anis.fr","activated":true,"adminsi":false,"superuser":false}'
-        );
-    }
-}
diff --git a/tests/Action/Login/ChangePasswordActionTest.php b/tests/Action/Login/ChangePasswordActionTest.php
deleted file mode 100644
index 37b73c8..0000000
--- a/tests/Action/Login/ChangePasswordActionTest.php
+++ /dev/null
@@ -1,121 +0,0 @@
-<?php
-declare(strict_types=1);
-
-namespace App\Tests\Action\Login;
-
-use Psr\Http\Message\ServerRequestInterface as Request;
-
-use App\Tests\AbstractActionAdminTestCase;
-
-final class ChangePasswordActionTest extends AbstractActionAdminTestCase
-{
-    protected function setUp(): void
-    {
-        parent::setUp();
-        $this->action = new \App\Action\Login\ChangePasswordAction(
-            new \Psr\Log\NullLogger(),
-            $this->entityManager,
-            $this->mailer
-        );
-    }
-
-    protected function getRequest(array $parsedBody): Request
-    {
-        $environment = \Slim\Http\Environment::mock([
-            'REQUEST_METHOD' => 'POST',
-            'REQUEST_URI' => '/change-password',
-            'CONTENT_TYPE'   => 'application/json',
-        ]);
-        return \Slim\Http\Request::createFromEnvironment($environment)->withParsedBody($parsedBody);
-    }
-
-    public function testEmailIsEmpty(): void
-    {
-        $request = $this->getRequest(['password' => 'password', 'new_password' => 'new_password']);
-        $response = ($this->action)($request, new \Slim\Http\Response(), array());
-        $this->assertEquals(400, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array(
-            'error' => 'Invalid request',
-            'error_description' => 'Param email needed to change your password'
-        )));
-    }
-
-    public function testPasswordIsEmpty(): void
-    {
-        $request = $this->getRequest(['email' => 'user2@anis.fr', 'new_password' => 'new_password']);
-        $response = ($this->action)($request, new \Slim\Http\Response(), array());
-        $this->assertEquals(400, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array(
-            'error' => 'Invalid request',
-            'error_description' => 'Param password needed to change your password'
-        )));
-    }
-
-    public function testNewPasswordIsEmpty(): void
-    {
-        $request = $this->getRequest(['email' => 'user2@anis.fr', 'password' => 'password']);
-        $response = ($this->action)($request, new \Slim\Http\Response(), array());
-        $this->assertEquals(400, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array(
-            'error' => 'Invalid request',
-            'error_description' => 'Param new_password needed to change your password'
-        )));
-    }
-
-    public function testUserDoNotExist(): void
-    {
-        $request = $this->getRequest(array(
-            'email' => 'nobody@anis.fr',
-            'password' => 'password',
-            'new_password' => 'new_password'
-        ));
-        $response = ($this->action)($request, new \Slim\Http\Response(), array());
-        $this->assertEquals(400, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array(
-            'error' => 'Invalid user',
-            'error_description' => 'No account is identified with this email address'
-        )));
-    }
-
-    public function testAccountNotActivated(): void
-    {
-        $request = $this->getRequest(array(
-            'email' => 'user1@anis.fr',
-            'password' => 'password',
-            'new_password' => 'new_password'
-        ));
-        $response = ($this->action)($request, new \Slim\Http\Response(), array());
-        $this->assertEquals(400, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array(
-            'error' => 'Invalid user',
-            'error_description' => 'Account not yet activated'
-        )));
-    }
-
-    public function testBadPassword(): void
-    {
-        $request = $this->getRequest(array(
-            'email' => 'user2@anis.fr',
-            'password' => 'badpassword',
-            'new_password' => 'new_password'
-        ));
-        $response = ($this->action)($request, new \Slim\Http\Response(), array());
-        $this->assertEquals(400, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array(
-            'error' => 'Invalid password',
-            'error_description' => 'Bad password; unable to change the password'
-        )));
-    }
-
-    public function testChangePassword(): void
-    {
-        $request = $this->getRequest(array(
-            'email' => 'user2@anis.fr',
-            'password' => 'password',
-            'new_password' => 'new_password'
-        ));
-        $response = ($this->action)($request, new \Slim\Http\Response(), array());
-        $this->assertEquals(200, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array('message' => 'Password changed!')));
-    }
-}
diff --git a/tests/Action/Login/NewPasswordActionTest.php b/tests/Action/Login/NewPasswordActionTest.php
deleted file mode 100644
index e0ef478..0000000
--- a/tests/Action/Login/NewPasswordActionTest.php
+++ /dev/null
@@ -1,72 +0,0 @@
-<?php
-declare(strict_types=1);
-
-namespace App\Tests\Action\Login;
-
-use Psr\Http\Message\ServerRequestInterface as Request;
-
-use App\Tests\AbstractActionAdminTestCase;
-
-final class NewPasswordActionTest extends AbstractActionAdminTestCase
-{
-    protected function setUp(): void
-    {
-        parent::setUp();
-        $this->action = new \App\Action\Login\NewPasswordAction(
-            new \Psr\Log\NullLogger(),
-            $this->entityManager,
-            $this->mailer
-        );
-    }
-
-    protected function getRequest(array $parsedBody): Request
-    {
-        $environment = \Slim\Http\Environment::mock([
-            'REQUEST_METHOD' => 'POST',
-            'REQUEST_URI' => '/new-password',
-            'CONTENT_TYPE'   => 'application/json',
-        ]);
-        return \Slim\Http\Request::createFromEnvironment($environment)->withParsedBody($parsedBody);
-    }
-
-    public function testEmailIsEmpty(): void
-    {
-        $request = $this->getRequest([]);
-        $response = ($this->action)($request, new \Slim\Http\Response(), array());
-        $this->assertEquals(400, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array(
-            'error' => 'Invalid request',
-            'error_description' => 'Param email needed to generate a new password'
-        )));
-    }
-
-    public function testUserDoNotExist(): void
-    {
-        $request = $this->getRequest(['email' => 'nobody@anis.fr']);
-        $response = ($this->action)($request, new \Slim\Http\Response(), array());
-        $this->assertEquals(400, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array(
-            'error' => 'Invalid user',
-            'error_description' => 'No account is identified with this email address'
-        )));
-    }
-
-    public function testAccountNotActivated(): void
-    {
-        $request = $this->getRequest(['email' => 'user1@anis.fr']);
-        $response = ($this->action)($request, new \Slim\Http\Response(), array());
-        $this->assertEquals(400, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array(
-            'error' => 'Invalid user',
-            'error_description' => 'Account not yet activated'
-        )));
-    }
-
-    public function testNewPassword(): void
-    {
-        $request = $this->getRequest(['email' => 'user2@anis.fr']);
-        $response = ($this->action)($request, new \Slim\Http\Response(), array());
-        $this->assertEquals(200, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array('message' => 'Password re-generated!')));
-    }
-}
diff --git a/tests/Action/Login/RegisterActionTest.php b/tests/Action/Login/RegisterActionTest.php
deleted file mode 100644
index 4ff7cad..0000000
--- a/tests/Action/Login/RegisterActionTest.php
+++ /dev/null
@@ -1,98 +0,0 @@
-<?php
-declare(strict_types=1);
-
-namespace App\Tests\Action\Login;
-
-use Psr\Http\Message\ServerRequestInterface as Request;
-
-use App\Tests\AbstractActionAdminTestCase;
-
-final class RegisterActionTest extends AbstractActionAdminTestCase
-{
-    protected function setUp(): void
-    {
-        parent::setUp();
-        $this->action = new \App\Action\Login\RegisterAction(
-            new \Psr\Log\NullLogger(),
-            $this->entityManager,
-            $this->mailer
-        );
-    }
-
-    protected function getRequest(array $parsedBody): Request
-    {
-        $environment = \Slim\Http\Environment::mock([
-            'REQUEST_METHOD' => 'POST',
-            'REQUEST_URI' => '/register',
-            'CONTENT_TYPE'   => 'application/json',
-        ]);
-        return \Slim\Http\Request::createFromEnvironment($environment)->withParsedBody($parsedBody);
-    }
-
-    public function testEmailIsEmpty(): void
-    {
-        $request = $this->getRequest(['password' => 'test']);
-        $response = ($this->action)($request, new \Slim\Http\Response(), array());
-        $this->assertEquals(400, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array(
-            'error' => 'Invalid request',
-            'error_description' => 'Param email needed to register a new user'
-        )));
-    }
-
-    public function testPasswordIsEmpty(): void
-    {
-        $request = $this->getRequest(['email' => 'test@anis.fr']);
-        $response = ($this->action)($request, new \Slim\Http\Response(), array());
-        $this->assertEquals(400, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array(
-            'error' => 'Invalid request',
-            'error_description' => 'Param password needed to register a new user'
-        )));
-    }
-
-    public function testExistUser(): void
-    {
-        $request = $this->getRequest([
-            'email' => 'user1@anis.fr',
-            'password' => 'test'
-        ]);
-        $response = ($this->action)($request, new \Slim\Http\Response(), array());
-        $this->assertEquals(400, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array(
-            'error' => 'Invalid user',
-            'error_description' => 'A user with the email address user1@anis.fr already exists'
-        )));
-    }
-
-    public function testInvalidEmail(): void
-    {
-        $request = $this->getRequest([
-            'email' => 'user1@anis',
-            'password' => 'test'
-        ]);
-        $response = ($this->action)($request, new \Slim\Http\Response(), array());
-        $this->assertEquals(400, (int) $response->getStatusCode());
-        $error_description = 'Bad email adress; a well-defined email address is needed to finalize the registration';
-        $this->assertSame((string) $response->getBody(), json_encode(array(
-            'error' => 'Invalid email',
-            'error_description' => $error_description
-        )));
-    }
-
-    public function testRegisterPost(): void
-    {
-        $this->assertSame(2, $this->getDatabaseTester()->getConnection()->getRowCount('anis_user'));
-        $request = $this->getRequest([
-            'email' => 'test@anis.fr',
-            'password' => 'test'
-        ]);
-        $response = ($this->action)($request, new \Slim\Http\Response(), array());
-        $this->assertEquals(201, (int) $response->getStatusCode());
-        $this->assertSame(
-            (string) $response->getBody(),
-            '{"email":"test@anis.fr","activated":false,"adminsi":false,"superuser":false}'
-        );
-        $this->assertSame(3, $this->getDatabaseTester()->getConnection()->getRowCount('anis_user'));
-    }
-}
diff --git a/tests/Action/Login/TokenActionTest.php b/tests/Action/Login/TokenActionTest.php
deleted file mode 100644
index fc0a71c..0000000
--- a/tests/Action/Login/TokenActionTest.php
+++ /dev/null
@@ -1,120 +0,0 @@
-<?php
-declare(strict_types=1);
-
-namespace App\Tests\Action\Login;
-
-use Psr\Http\Message\ServerRequestInterface as Request;
-use Lcobucci\JWT\Builder as JwtBuilder;
-use Lcobucci\JWT\Signer as JwtSigner;
-use Lcobucci\JWT\Token;
-
-use App\Tests\AbstractActionAdminTestCase;
-
-final class TokenActionTest extends AbstractActionAdminTestCase
-{
-    protected function setUp(): void
-    {
-        parent::setUp();
-        $this->action = new \App\Action\Login\TokenAction(
-            new \Psr\Log\NullLogger(),
-            $this->entityManager,
-            $this->getMockJwtBuilder(),
-            $this->createMock(JwtSigner::class)
-        );
-    }
-
-    protected function getMockJwtBuilder(): JwtBuilder
-    {
-        $mockJwtBuilder = $this->createMock(JwtBuilder::class);
-        $mockToken = $this->createMock(Token::class);
-        $mockToken
-            ->method('__toString')
-            ->willReturn('supertoken');
-        $mockJwtBuilder
-            ->method('set')
-            ->willReturn($mockJwtBuilder);
-        $mockJwtBuilder
-            ->method('sign')
-            ->willReturn($mockJwtBuilder);
-        $mockJwtBuilder
-            ->method('getToken')
-            ->willReturn($mockToken);
-        return $mockJwtBuilder;
-    }
-
-    protected function getRequest(array $parsedBody): Request
-    {
-        $environment = \Slim\Http\Environment::mock([
-            'REQUEST_METHOD' => 'POST',
-            'REQUEST_URI' => '/login',
-            'CONTENT_TYPE'   => 'application/json',
-        ]);
-        return \Slim\Http\Request::createFromEnvironment($environment)->withParsedBody($parsedBody);
-    }
-
-    public function testEmailIsEmpty(): void
-    {
-        $request = $this->getRequest(['password' => 'password']);
-        $response = ($this->action)($request, new \Slim\Http\Response(), array());
-        $this->assertEquals(400, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array(
-            'error' => 'Invalid request',
-            'error_description' => 'Param email needed to login'
-        )));
-    }
-
-    public function testPasswordIsEmpty(): void
-    {
-        $request = $this->getRequest(['email' => 'user2@anis.fr']);
-        $response = ($this->action)($request, new \Slim\Http\Response(), array());
-        $this->assertEquals(400, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array(
-            'error' => 'Invalid request',
-            'error_description' => 'Param password needed to login'
-        )));
-    }
-
-    public function testUserDoNotExist(): void
-    {
-        $request = $this->getRequest(['email' => 'nobody@anis.fr', 'password' => 'pass']);
-        $response = ($this->action)($request, new \Slim\Http\Response(), array());
-        $this->assertEquals(400, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array(
-            'error' => 'Invalid user',
-            'error_description' => 'No account is identified with this email address'
-        )));
-    }
-
-    public function testAccountNotActivated(): void
-    {
-        $request = $this->getRequest(['email' => 'user1@anis.fr', 'password' => 'sfpsdfpsdmsdlsdfdf']);
-        $response = ($this->action)($request, new \Slim\Http\Response(), array());
-        $this->assertEquals(400, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array(
-            'error' => 'Invalid user',
-            'error_description' => 'Account not yet activated'
-        )));
-    }
-
-    public function testBadPassword(): void
-    {
-        $request = $this->getRequest(['email' => 'user2@anis.fr', 'password' => 'badpassword']);
-        $response = ($this->action)($request, new \Slim\Http\Response(), array());
-        $this->assertEquals(400, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array(
-            'error' => 'Invalid password',
-            'error_description' => 'Bad password; unable to login'
-        )));
-    }
-
-    public function testLogin(): void
-    {
-        $request = $this->getRequest([
-            'email' => 'user2@anis.fr',
-            'password' => 'password'
-        ]);
-        $response = ($this->action)($request, new \Slim\Http\Response(), array());
-        $this->assertEquals(200, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), '{"email":"user2@anis.fr","token":"supertoken"}');
-    }
-}
diff --git a/tests/Action/Meta/DatabaseActionTest.php b/tests/Action/Meta/DatabaseActionTest.php
deleted file mode 100644
index 3abe5e9..0000000
--- a/tests/Action/Meta/DatabaseActionTest.php
+++ /dev/null
@@ -1,158 +0,0 @@
-<?php
-declare(strict_types=1);
-
-namespace App\Tests\Action\Meta;
-
-use Psr\Http\Message\ServerRequestInterface as Request;
-
-use App\Tests\AbstractActionMetaTestCase;
-
-final class DatabaseActionTest extends AbstractActionMetaTestCase
-{
-    protected function setUp(): void
-    {
-        parent::setUp();
-        $this->action = new \App\Action\Meta\DatabaseAction(
-            new \Psr\Log\NullLogger(),
-            $this->metaEntityManagerFactory,
-            base64_decode('r3Q8C7LgIrRcTtI8I6EPzFwrDXJ4adgnGQ9V/pWVI8M=')
-        );
-    }
-
-    protected function getRequestForPut(array $parsedBody): Request
-    {
-        $environment = \Slim\Http\Environment::mock([
-            'REQUEST_METHOD' => 'PUT',
-            'REQUEST_URI' => '/metadata/database/1',
-            'CONTENT_TYPE'   => 'application/json',
-        ]);
-        return \Slim\Http\Request::createFromEnvironment($environment)->withParsedBody($parsedBody);
-    }
-
-    public function testDatabaseIsNotFound(): void
-    {
-        $environment = \Slim\Http\Environment::mock([
-            'REQUEST_METHOD' => 'GET',
-            'REQUEST_URI' => '/metadata/database/15'
-        ]);
-        $request = \Slim\Http\Request::createFromEnvironment($environment);
-        $response = ($this->action)($request, new \Slim\Http\Response(), array('id' => 15, 'instance' => 'default'));
-        $this->assertEquals(404, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array(
-            'error' => 'Invalid request',
-            'error_description' => 'Database with id 15 is not found'
-        )));
-    }
-
-    public function testGetDatabase(): void
-    {
-        $database = array(
-            'id' => 1,
-            'label' => 'Cosmology',
-            'dbname' => 'cosmologydb',
-            'dbtype' => 'pgsql',
-            'dbhost' => 'pgserver',
-            'dbport' => 5432,
-            'dblogin' =>  'consult',
-            'dbpassword' => 'password'
-        );
-        $environment = \Slim\Http\Environment::mock([
-            'REQUEST_METHOD' => 'GET',
-            'REQUEST_URI' => '/metadata/database/1'
-        ]);
-        $request = \Slim\Http\Request::createFromEnvironment($environment);
-        $response = ($this->action)($request, new \Slim\Http\Response(), array('id' => 1, 'instance' => 'default'));
-        $this->assertEquals(200, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode($database));
-    }
-
-    public function testLabelIsEmpty(): void
-    {
-        $this->fieldIsEmpty('label');
-    }
-
-    public function testDbNameIsEmpty(): void
-    {
-        $this->fieldIsEmpty('dbname');
-    }
-
-    public function testDbTypeIsEmpty(): void
-    {
-        $this->fieldIsEmpty('dbtype');
-    }
-
-    public function testDbHostIsEmpty(): void
-    {
-        $this->fieldIsEmpty('dbhost');
-    }
-
-    public function testDbPortIsEmpty(): void
-    {
-        $this->fieldIsEmpty('dbport');
-    }
-
-    public function testDbLoginIsEmpty(): void
-    {
-        $this->fieldIsEmpty('dblogin');
-    }
-
-    public function testDbPasswordIsEmpty(): void
-    {
-        $this->fieldIsEmpty('dbpassword');
-    }
-
-    public function testEditDatabase(): void
-    {
-        $editedDatabase = $this->getEditedDatabase();
-        $request = $this->getRequestForPut($editedDatabase);
-        $response = ($this->action)($request, new \Slim\Http\Response(), array('id' => 1, 'instance' => 'default'));
-        $this->assertEquals(200, (int) $response->getStatusCode());
-        $arrayResponse = json_decode((string) $response->getBody(), true);
-        $arrayResponse['dbpassword'] = 'password';
-        $this->assertSame($arrayResponse, $editedDatabase);
-        $this->assertSame(2, $this->getDatabaseTester()->getConnection()->getRowCount('database'));
-    }
-
-    public function testRemoveDatabase(): void
-    {
-        $environment = \Slim\Http\Environment::mock([
-            'REQUEST_METHOD' => 'DELETE',
-            'REQUEST_URI' => '/metadata/database/1'
-        ]);
-        $request = \Slim\Http\Request::createFromEnvironment($environment);
-        $response = ($this->action)($request, new \Slim\Http\Response(), array('id' => 1, 'instance' => 'default'));
-        $this->assertEquals(200, (int) $response->getStatusCode());
-        $this->assertSame(
-            (string) $response->getBody(),
-            json_encode(array('message' => 'Database with id 1 is removed!'))
-        );
-        $this->assertSame(1, $this->getDatabaseTester()->getConnection()->getRowCount('database'));
-    }
-
-    private function fieldIsEmpty($field): void
-    {
-        $editedDatabase = $this->getEditedDatabase();
-        unset($editedDatabase[$field]);
-        $request = $this->getRequestForPut($editedDatabase);
-        $response = ($this->action)($request, new \Slim\Http\Response(), array('id' => 1, 'instance' => 'default'));
-        $this->assertEquals(400, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array(
-            'error' => 'Invalid request',
-            'error_description' => 'Param ' . $field . ' needed to edit the database'
-        )));
-    }
-
-    private function getEditedDatabase(): array
-    {
-        return array(
-            'id' => 1,
-            'label' => 'Edited Database',
-            'dbname' => 'edited_database',
-            'dbtype' => 'pgsql',
-            'dbhost' => 'pgserver2',
-            'dbport' => 5432,
-            'dblogin' => 'login',
-            'dbpassword' => 'password'
-        );
-    }
-}
diff --git a/tests/Action/Meta/DatabaseListActionTest.php b/tests/Action/Meta/DatabaseListActionTest.php
deleted file mode 100644
index 13159c9..0000000
--- a/tests/Action/Meta/DatabaseListActionTest.php
+++ /dev/null
@@ -1,138 +0,0 @@
-<?php
-declare(strict_types=1);
-
-namespace App\Tests\Action\Meta;
-
-use Psr\Http\Message\ServerRequestInterface as Request;
-
-use App\Tests\AbstractActionMetaTestCase;
-
-final class DatabaseListActionTest extends AbstractActionMetaTestCase
-{
-    protected function setUp(): void
-    {
-        parent::setUp();
-        $this->action = new \App\Action\Meta\DatabaseListAction(
-            new \Psr\Log\NullLogger(),
-            $this->metaEntityManagerFactory,
-            base64_decode('r3Q8C7LgIrRcTtI8I6EPzFwrDXJ4adgnGQ9V/pWVI8M=')
-        );
-    }
-
-    protected function getRequestForPost(array $parsedBody): Request
-    {
-        $environment = \Slim\Http\Environment::mock([
-            'REQUEST_METHOD' => 'POST',
-            'REQUEST_URI' => '/metadata/database',
-            'CONTENT_TYPE'   => 'application/json',
-        ]);
-        return \Slim\Http\Request::createFromEnvironment($environment)->withParsedBody($parsedBody);
-    }
-
-    public function testDatabaseList(): void
-    {
-        $environment = \Slim\Http\Environment::mock([
-            'REQUEST_METHOD' => 'GET',
-            'REQUEST_URI' => '/metadata/database'
-        ]);
-        $request = \Slim\Http\Request::createFromEnvironment($environment);
-        $response = ($this->action)($request, new \Slim\Http\Response(), array('instance' => 'default'));
-        $this->assertEquals(200, (int) $response->getStatusCode());
-        $json = array(
-            [
-                "id" => 1,
-                "label" => "Cosmology",
-                "dbname" => "cosmologydb",
-                "dbtype" => "pgsql",
-                "dbhost" => "pgserver",
-                "dbport" => 5432,
-                "dblogin" => "consult",
-                "dbpassword" => "password"
-            ],
-            [
-                "id" => 2,
-                "label" => "ExoDat",
-                "dbname" => "exodat_new",
-                "dbtype" => "pgsql",
-                "dbhost" => "pgserver",
-                "dbport" => 5432,
-                "dblogin" => "consult",
-                "dbpassword" => "exopassword"
-            ]
-        );
-        $this->assertSame((string) $response->getBody(), json_encode($json));
-    }
-
-    public function testLabelIsEmpty(): void
-    {
-        $this->fieldIsEmpty('label');
-    }
-
-    public function testDbNameIsEmpty(): void
-    {
-        $this->fieldIsEmpty('dbname');
-    }
-
-    public function testDbTypeIsEmpty(): void
-    {
-        $this->fieldIsEmpty('dbtype');
-    }
-
-    public function testDbHostIsEmpty(): void
-    {
-        $this->fieldIsEmpty('dbhost');
-    }
-
-    public function testDbPortIsEmpty(): void
-    {
-        $this->fieldIsEmpty('dbport');
-    }
-
-    public function testDbLoginIsEmpty(): void
-    {
-        $this->fieldIsEmpty('dblogin');
-    }
-
-    public function testDbPasswordIsEmpty(): void
-    {
-        $this->fieldIsEmpty('dbpassword');
-    }
-
-    public function testAddDatabase(): void
-    {
-        $newDatabase = $this->getNewDatabase();
-        $request = $this->getRequestForPost($newDatabase);
-        $response = ($this->action)($request, new \Slim\Http\Response(), array('instance' => 'default'));
-        $this->assertEquals(201, (int) $response->getStatusCode());
-        $arrayResponse = json_decode((string) $response->getBody(), true);
-        $arrayResponse['dbpassword'] = 'password';
-        $this->assertSame($arrayResponse, array_merge(array('id' => 3), $newDatabase));
-        $this->assertSame(3, $this->getDatabaseTester()->getConnection()->getRowCount('database'));
-    }
-
-    private function fieldIsEmpty($field): void
-    {
-        $newDatabase = $this->getNewDatabase();
-        unset($newDatabase[$field]);
-        $request = $this->getRequestForPost($newDatabase);
-        $response = ($this->action)($request, new \Slim\Http\Response(), array('instance' => 'default'));
-        $this->assertEquals(400, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array(
-            'error' => 'Invalid request',
-            'error_description' => 'Param ' . $field . ' needed to add a new database'
-        )));
-    }
-
-    private function getNewDatabase(): array
-    {
-        return array(
-            'label' => 'New Database',
-            'dbname' => 'new_database',
-            'dbtype' => 'pgsql',
-            'dbhost' => 'pgserver2',
-            'dbport' => 5432,
-            'dblogin' => 'login',
-            'dbpassword' => 'password'
-        );
-    }
-}
diff --git a/tests/Action/Meta/DatasetActionTest.php b/tests/Action/Meta/DatasetActionTest.php
deleted file mode 100644
index 4ba2a5f..0000000
--- a/tests/Action/Meta/DatasetActionTest.php
+++ /dev/null
@@ -1,180 +0,0 @@
-<?php
-declare(strict_types=1);
-
-namespace App\Tests\Action\Meta;
-
-use Psr\Http\Message\ServerRequestInterface as Request;
-
-use App\Tests\AbstractActionMetaTestCase;
-
-final class DatasetActionTest extends AbstractActionMetaTestCase
-{
-    protected function setUp(): void
-    {
-        parent::setUp();
-        $this->action = new \App\Action\Meta\DatasetAction(
-            new \Psr\Log\NullLogger(),
-            $this->metaEntityManagerFactory
-        );
-    }
-
-    protected function getRequestForPut(array $parsedBody): Request
-    {
-        $environment = \Slim\Http\Environment::mock([
-            'REQUEST_METHOD' => 'PUT',
-            'REQUEST_URI' => '/metadata/dataset/corot_targets',
-            'CONTENT_TYPE'   => 'application/json',
-        ]);
-        return \Slim\Http\Request::createFromEnvironment($environment)->withParsedBody($parsedBody);
-    }
-
-    public function testDatasetIsNotFound(): void
-    {
-        $environment = \Slim\Http\Environment::mock([
-            'REQUEST_METHOD' => 'GET',
-            'REQUEST_URI' => '/metadata/dataset/undifined'
-        ]);
-        $request = \Slim\Http\Request::createFromEnvironment($environment);
-        $response = ($this->action)(
-            $request,
-            new \Slim\Http\Response(),
-            array('name' => 'undifined', 'instance' => 'default')
-        );
-        $this->assertEquals(404, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array(
-            'error' => 'Invalid request',
-            'error_description' => 'Dataset with name undifined is not found'
-        )));
-    }
-
-    public function testGetDataset(): void
-    {
-        $environment = \Slim\Http\Environment::mock([
-            'REQUEST_METHOD' => 'GET',
-            'REQUEST_URI' => '/metadata/dataset/corot_targets'
-        ]);
-        $request = \Slim\Http\Request::createFromEnvironment($environment);
-        $response = ($this->action)(
-            $request,
-            new \Slim\Http\Response(),
-            array('name' => 'corot_targets', 'instance' => 'default')
-        );
-        $this->assertEquals(200, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array(
-            'name' => 'corot_targets',
-            'table_ref' => 'v_corot_targets',
-            'label' => 'CoRoT Targets',
-            'description' => 'CoRoT Targets',
-            'display' => 10,
-            'count' => 100582,
-            'vo' => true,
-            'data_path' => '/tmp/data',
-            'selectable_row' => false,
-            'project_name' => 'corot',
-            'id_dataset_family' => 1
-        )));
-    }
-
-    public function testLabelIsEmpty(): void
-    {
-        $this->fieldIsEmpty('label');
-    }
-
-    public function testDescriptionIsEmpty(): void
-    {
-        $this->fieldIsEmpty('description');
-    }
-
-    public function testIdDatasetFamilyIsEmpty(): void
-    {
-        $this->fieldIsEmpty('id_dataset_family');
-    }
-
-    public function testDisplayIsEmpty(): void
-    {
-        $this->fieldIsEmpty('display');
-    }
-
-    public function testCountIsEmpty(): void
-    {
-        $this->fieldIsEmpty('count');
-    }
-
-    public function testVoIsEmpty(): void
-    {
-        $this->fieldIsEmpty('vo');
-    }
-
-    public function testDataPathIsEmpty(): void
-    {
-        $this->fieldIsEmpty('data_path');
-    }
-
-    public function testEditDataset(): void
-    {
-        $editedDataset = $this->getEditedDataset();
-        $request = $this->getRequestForPut($editedDataset);
-        $response = ($this->action)(
-            $request,
-            new \Slim\Http\Response(),
-            array('name' => 'corot_targets', 'instance' => 'default')
-        );
-        $this->assertEquals(200, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode($editedDataset));
-        $this->assertSame(2, $this->getDatabaseTester()->getConnection()->getRowCount('dataset'));
-    }
-
-    public function testRemoveDataset(): void
-    {
-        $environment = \Slim\Http\Environment::mock([
-            'REQUEST_METHOD' => 'DELETE',
-            'REQUEST_URI' => '/metadata/dataset/corot_targets'
-        ]);
-        $request = \Slim\Http\Request::createFromEnvironment($environment);
-        $response = ($this->action)(
-            $request,
-            new \Slim\Http\Response(),
-            array('name' => 'corot_targets', 'instance' => 'default')
-        );
-        $this->assertEquals(200, (int) $response->getStatusCode());
-        $this->assertSame(
-            (string) $response->getBody(),
-            json_encode(array('message' => 'Dataset with name corot_targets is removed!'))
-        );
-        $this->assertSame(1, $this->getDatabaseTester()->getConnection()->getRowCount('dataset'));
-    }
-
-    private function fieldIsEmpty($field): void
-    {
-        $editedDataset = $this->getEditedDataset();
-        unset($editedDataset[$field]);
-        $request = $this->getRequestForPut($editedDataset);
-        $response = ($this->action)(
-            $request,
-            new \Slim\Http\Response(),
-            array('name' => 'corot_targets', 'instance' => 'default')
-        );
-        $this->assertEquals(400, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array(
-            'error' => 'Invalid request',
-            'error_description' => 'Param ' . $field . ' needed to edit the dataset'
-        )));
-    }
-
-    private function getEditedDataset(): array
-    {
-        return array(
-            'name' => 'corot_targets',
-            'table_ref' => 'v_corot_targets',
-            'label' => 'Edited dataset',
-            'description' => 'Edited dataset description',
-            'display' => 50,
-            'count' => 100583,
-            'vo' => true,
-            'data_path' => '/tmp/data',
-            'selectable_row' => false,
-            'project_name' => 'corot',
-            'id_dataset_family' => 1
-        );
-    }
-}
diff --git a/tests/Action/Meta/DatasetListActionTest.php b/tests/Action/Meta/DatasetListActionTest.php
deleted file mode 100644
index dbfa034..0000000
--- a/tests/Action/Meta/DatasetListActionTest.php
+++ /dev/null
@@ -1,189 +0,0 @@
-<?php
-declare(strict_types=1);
-
-namespace App\Tests\Action\Meta;
-
-use Psr\Http\Message\ServerRequestInterface as Request;
-use Doctrine\DBAL\Connection as DBALConnection;
-
-use App\Utils\DBALConnectionFactory;
-use App\Tests\AbstractActionMetaTestCase;
-
-final class DatasetListActionTest extends AbstractActionMetaTestCase
-{
-    protected function setUp(): void
-    {
-        parent::setUp();
-        $this->action = new \App\Action\Meta\DatasetListAction(
-            new \Psr\Log\NullLogger(),
-            $this->metaEntityManagerFactory,
-            $this->getMockDBALConnectionFactory(),
-            base64_decode('r3Q8C7LgIrRcTtI8I6EPzFwrDXJ4adgnGQ9V/pWVI8M=')
-        );
-    }
-
-    protected function getMockDBALConnectionFactory(): DBALConnectionFactory
-    {
-        $mockDBALConnection = $this->createMock(DBALConnection::class);
-        $mockConnectionFactory = $this->createMock(DBALConnectionFactory::class);
-        $mockConnectionFactory
-            ->method('create')
-            ->willReturn($mockDBALConnection);
-        return $mockConnectionFactory;
-    }
-
-    protected function getRequestForPost(array $parsedBody): Request
-    {
-        $environment = \Slim\Http\Environment::mock([
-            'REQUEST_METHOD' => 'POST',
-            'REQUEST_URI' => '/metadata/dataset',
-            'CONTENT_TYPE'   => 'application/json',
-        ]);
-        return \Slim\Http\Request::createFromEnvironment($environment)->withParsedBody($parsedBody);
-    }
-
-    public function testGetDatasetList(): void
-    {
-        $environment = \Slim\Http\Environment::mock([
-            'REQUEST_METHOD' => 'GET',
-            'REQUEST_URI' => '/metadata/dataset'
-        ]);
-        $request = \Slim\Http\Request::createFromEnvironment($environment);
-        $response = ($this->action)($request, new \Slim\Http\Response(), array('instance' => 'default'));
-        $this->assertEquals(200, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array(
-            array(
-                'name' => 'corot_targets',
-                'table_ref' => 'v_corot_targets',
-                'label' => 'CoRoT Targets',
-                'description' => 'CoRoT Targets',
-                'display' => 10,
-                'count' => 100582,
-                'vo' => true,
-                'data_path' => '/tmp/data',
-                'selectable_row' => false,
-                'project_name' => 'corot',
-                'id_dataset_family' => 1
-            ),
-            array(
-                'name' => 'vuds_targets',
-                'table_ref' => 'v_vuds_targets',
-                'label' => 'VUDS Targets',
-                'description' => 'VUDS Targets display',
-                'display' => 20,
-                'count' => 784512,
-                'vo' => false,
-                'data_path' => '/tmp/data',
-                'selectable_row' => false,
-                'project_name' => 'vuds',
-                'id_dataset_family' => 2
-            )
-        )));
-    }
-
-    public function testNameIsEmpty(): void
-    {
-        $this->fieldIsEmpty('name');
-    }
-
-    public function testTableRefIsEmpty(): void
-    {
-        $this->fieldIsEmpty('table_ref');
-    }
-
-    public function testLabelIsEmpty(): void
-    {
-        $this->fieldIsEmpty('label');
-    }
-
-    public function testDescriptionIsEmpty(): void
-    {
-        $this->fieldIsEmpty('description');
-    }
-
-    public function testDisplayIsEmpty(): void
-    {
-        $this->fieldIsEmpty('display');
-    }
-
-    public function testCountIsEmpty(): void
-    {
-        $this->fieldIsEmpty('count');
-    }
-
-    public function testVoIsEmpty(): void
-    {
-        $this->fieldIsEmpty('vo');
-    }
-
-    public function testDataPathIsEmpty(): void
-    {
-        $this->fieldIsEmpty('data_path');
-    }
-
-    public function testProjectNameIsEmpty(): void
-    {
-        $this->fieldIsEmpty('project_name');
-    }
-
-    public function testIdDatasetFamilyIsEmpty(): void
-    {
-        $this->fieldIsEmpty('id_dataset_family');
-    }
-
-    public function testProjectIsNotFound(): void
-    {
-        $newDataset = $this->getNewDataset();
-        $newDataset['project_name'] = 'undifined';
-        $request = $this->getRequestForPost($newDataset);
-        $response = ($this->action)($request, new \Slim\Http\Response(), array('instance' => 'default'));
-        $this->assertEquals(404, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array(
-            'error' => 'Invalid request',
-            'error_description' => 'Project with name undifined is not found'
-        )));
-    }
-
-    public function testDatasetFamilyIsNotFound(): void
-    {
-        $newDataset = $this->getNewDataset();
-        $newDataset['id_dataset_family'] = 15;
-        $request = $this->getRequestForPost($newDataset);
-        $response = ($this->action)($request, new \Slim\Http\Response(), array('instance' => 'default'));
-        $this->assertEquals(404, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array(
-            'error' => 'Invalid request',
-            'error_description' => 'Dataset family with id 15 is not found'
-        )));
-    }
-
-    private function fieldIsEmpty($field): void
-    {
-        $newDataset = $this->getNewDataset();
-        unset($newDataset[$field]);
-        $request = $this->getRequestForPost($newDataset);
-        $response = ($this->action)($request, new \Slim\Http\Response(), array('instance' => 'default'));
-        $this->assertEquals(400, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array(
-            'error' => 'Invalid request',
-            'error_description' => 'Param ' . $field . ' needed to add a new dataset'
-        )));
-    }
-
-    private function getNewDataset(): array
-    {
-        return array(
-            'name' => 'new_dataset',
-            'table_ref' => 'v_new_dataset',
-            'label' => 'New dataset',
-            'description' => 'New Dataset description',
-            'display' => 30,
-            'count' => 987456,
-            'vo' => true,
-            'data_path' => '/tmp/data',
-            'selectable_row' => true,
-            'project_name' => 'corot',
-            'id_dataset_family' => 1
-        );
-    }
-}
diff --git a/tests/Action/Meta/FamilyActionTest.php b/tests/Action/Meta/FamilyActionTest.php
deleted file mode 100644
index 581875d..0000000
--- a/tests/Action/Meta/FamilyActionTest.php
+++ /dev/null
@@ -1,169 +0,0 @@
-<?php
-declare(strict_types=1);
-
-namespace App\Tests\Action\Meta;
-
-use Psr\Http\Message\ServerRequestInterface as Request;
-
-use App\Tests\AbstractActionMetaTestCase;
-
-final class FamilyActionTest extends AbstractActionMetaTestCase
-{
-    protected function setUp(): void
-    {
-        parent::setUp();
-        $this->action = new \App\Action\Meta\FamilyAction(
-            new \Psr\Log\NullLogger(),
-            $this->metaEntityManagerFactory
-        );
-    }
-
-    protected function getRequestForPut(array $parsedBody): Request
-    {
-        $environment = \Slim\Http\Environment::mock([
-            'REQUEST_METHOD' => 'PUT',
-            'REQUEST_URI' => '/metadata/family/dataset/1',
-            'CONTENT_TYPE'   => 'application/json',
-        ]);
-        return \Slim\Http\Request::createFromEnvironment($environment)->withParsedBody($parsedBody);
-    }
-
-    public function testTypeIsNotDefined(): void
-    {
-        $environment = \Slim\Http\Environment::mock([
-            'REQUEST_METHOD' => 'GET',
-            'REQUEST_URI' => '/metadata/family/undifined/1'
-        ]);
-        $request = \Slim\Http\Request::createFromEnvironment($environment);
-        $response = ($this->action)(
-            $request,
-            new \Slim\Http\Response(),
-            array('type' => 'undifined', 'id' => 1, 'instance' => 'default')
-        );
-        $this->assertEquals(400, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array(
-            'error' => 'Invalid request',
-            'error_description' => 'Type undifined is not defined'
-        )));
-    }
-
-    public function testFamilyIsNotFound(): void
-    {
-        $environment = \Slim\Http\Environment::mock([
-            'REQUEST_METHOD' => 'GET',
-            'REQUEST_URI' => '/metadata/family/dataset/15'
-        ]);
-        $request = \Slim\Http\Request::createFromEnvironment($environment);
-        $response = ($this->action)(
-            $request,
-            new \Slim\Http\Response(),
-            array('type' => 'dataset', 'id' => 15, 'instance' => 'default')
-        );
-        $this->assertEquals(404, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array(
-            'error' => 'Invalid request',
-            'error_description' => 'Dataset family with id 15 is not found'
-        )));
-    }
-
-    public function testGetDatasetFamily(): void
-    {
-        $environment = \Slim\Http\Environment::mock([
-            'REQUEST_METHOD' => 'GET',
-            'REQUEST_URI' => '/metadata/family/dataset/1'
-        ]);
-        $request = \Slim\Http\Request::createFromEnvironment($environment);
-        $response = ($this->action)(
-            $request,
-            new \Slim\Http\Response(),
-            array('type' => 'dataset', 'id' => 1, 'instance' => 'default')
-        );
-        $this->assertEquals(200, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array(
-            'id' => 1,
-            'label' => 'VUDS dataset collection',
-            'display' => 10,
-            'type' => 'dataset',
-            'datasets' => array(
-                'corot_targets'
-            )
-        )));
-    }
-
-    public function testLabelIsEmpty(): void
-    {
-        $this->fieldIsEmpty('label');
-    }
-
-    public function testDisplayIsEmpty(): void
-    {
-        $this->fieldIsEmpty('display');
-    }
-
-    public function testEditDatasetFamily(): void
-    {
-        $editedDatasetFamily = $this->getEditedDatasetFamily();
-        $request = $this->getRequestForPut($editedDatasetFamily);
-        $response = ($this->action)(
-            $request,
-            new \Slim\Http\Response(),
-            array('type' => 'dataset', 'id' => 1, 'instance' => 'default')
-        );
-        $this->assertEquals(200, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array(
-            'id' => 1,
-            'label' => 'Edited Family',
-            'display' => 40,
-            'type' => 'dataset',
-            'datasets' => array(
-                'corot_targets'
-            )
-        )));
-    }
-
-    public function testRemoveDatasetFamily(): void
-    {
-        $environment = \Slim\Http\Environment::mock([
-            'REQUEST_METHOD' => 'DELETE',
-            'REQUEST_URI' => '/metadata/family/dataset/1'
-        ]);
-        $request = \Slim\Http\Request::createFromEnvironment($environment);
-        $response = ($this->action)(
-            $request,
-            new \Slim\Http\Response(),
-            array('type' => 'dataset', 'id' => 1, 'instance' => 'default')
-        );
-        $this->assertEquals(200, (int) $response->getStatusCode());
-        $this->assertSame(
-            (string) $response->getBody(),
-            json_encode(array('message' => 'Dataset family with id 1 is removed!'))
-        );
-        $this->assertSame(1, $this->getDatabaseTester()->getConnection()->getRowCount('dataset_family'));
-    }
-
-    private function fieldIsEmpty($field): void
-    {
-        $editedDatasetFamily = $this->getEditedDatasetFamily();
-        unset($editedDatasetFamily[$field]);
-        $request = $this->getRequestForPut($editedDatasetFamily);
-        $response = ($this->action)(
-            $request,
-            new \Slim\Http\Response(),
-            array('type' => 'dataset', 'id' => 1, 'instance' => 'default')
-        );
-        $this->assertEquals(400, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array(
-            'error' => 'Invalid request',
-            'error_description' => 'Param ' . $field . ' needed to edit the dataset family'
-        )));
-    }
-
-    private function getEditedDatasetFamily(): array
-    {
-        return array(
-            'id' => 1,
-            'label' => 'Edited Family',
-            'display' => 40
-        );
-    }
-}
diff --git a/tests/Action/Meta/FamilyListActionTest.php b/tests/Action/Meta/FamilyListActionTest.php
deleted file mode 100644
index a1ccdf3..0000000
--- a/tests/Action/Meta/FamilyListActionTest.php
+++ /dev/null
@@ -1,236 +0,0 @@
-<?php
-declare(strict_types=1);
-
-namespace App\Tests\Action\Meta;
-
-use Psr\Http\Message\ServerRequestInterface as Request;
-
-use App\Tests\AbstractActionMetaTestCase;
-
-final class FamilyListActionTest extends AbstractActionMetaTestCase
-{
-    protected function setUp(): void
-    {
-        parent::setUp();
-        $this->action = new \App\Action\Meta\FamilyListAction(
-            new \Psr\Log\NullLogger(),
-            $this->metaEntityManagerFactory
-        );
-    }
-
-    protected function getRequestForPost(array $parsedBody): Request
-    {
-        $environment = \Slim\Http\Environment::mock([
-            'REQUEST_METHOD' => 'POST',
-            'REQUEST_URI' => '/metadata/family',
-            'CONTENT_TYPE'   => 'application/json',
-        ]);
-        return \Slim\Http\Request::createFromEnvironment($environment)->withParsedBody($parsedBody);
-    }
-
-    public function testDatasetFamilyList(): void
-    {
-        $environment = \Slim\Http\Environment::mock([
-            'REQUEST_METHOD' => 'GET',
-            'REQUEST_URI' => '/metadata/family/dataset'
-        ]);
-        $request = \Slim\Http\Request::createFromEnvironment($environment);
-        $response = ($this->action)(
-            $request,
-            new \Slim\Http\Response(),
-            array('type' => 'dataset', 'instance' => 'default')
-        );
-        $this->assertEquals(200, (int) $response->getStatusCode());
-        $json = array(
-            [
-                "id" => 1,
-                "label" => "VUDS dataset collection",
-                "display" => 10,
-                "type" => "dataset",
-                "datasets" => ["corot_targets"]
-            ],
-            [
-                "id" => 2,
-                "label" => "VVDS dataset collection",
-                "display" => 20,
-                "type" => "dataset",
-                "datasets" => ["vuds_targets"]
-            ]
-        );
-        $this->assertSame((string) $response->getBody(), json_encode($json));
-    }
-
-    public function testCriteriaFamilyList(): void
-    {
-        $environment = \Slim\Http\Environment::mock([
-            'REQUEST_METHOD' => 'GET',
-            'REQUEST_URI' => '/metadata/family/criteria'
-        ]);
-        $request = \Slim\Http\Request::createFromEnvironment($environment);
-        $response = ($this->action)(
-            $request,
-            new \Slim\Http\Response(),
-            array('type' => 'criteria', 'instance' => 'default')
-        );
-        $this->assertEquals(200, (int) $response->getStatusCode());
-        $json = array(
-            [
-                "id" => 1,
-                "label" => "Criteria by default",
-                "display" => 10,
-                "type" => "criteria"
-            ],
-            [
-                "id" => 2,
-                "label" => "Photometry",
-                "display" => 20,
-                "type" => "criteria"
-            ]
-        );
-        $this->assertSame((string) $response->getBody(), json_encode($json));
-    }
-
-    public function testOutputFamilyList(): void
-    {
-        $environment = \Slim\Http\Environment::mock([
-            'REQUEST_METHOD' => 'GET',
-            'REQUEST_URI' => '/metadata/family/family'
-        ]);
-        $request = \Slim\Http\Request::createFromEnvironment($environment);
-        $response = ($this->action)(
-            $request,
-            new \Slim\Http\Response(),
-            array('type' => 'output', 'instance' => 'default')
-        );
-        $this->assertEquals(200, (int) $response->getStatusCode());
-        $json = array(
-            [
-                "id" => 1,
-                "label" => "Parameters by default",
-                "display" => 10,
-                "type" => "output"
-            ],
-            [
-                "id" => 2,
-                "label" => "Others catalog ID",
-                "display" => 20,
-                "type" => "output"
-            ]
-        );
-        $this->assertSame((string) $response->getBody(), json_encode($json));
-    }
-
-    public function testFamilyTypeIsNotDefined(): void
-    {
-        $environment = \Slim\Http\Environment::mock([
-            'REQUEST_METHOD' => 'GET',
-            'REQUEST_URI' => '/metadata/family/undifined'
-        ]);
-        $request = \Slim\Http\Request::createFromEnvironment($environment);
-        $response = ($this->action)(
-            $request,
-            new \Slim\Http\Response(),
-            array('type' => 'undifined', 'instance' => 'default')
-        );
-        $this->assertEquals(400, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array(
-            'error' => 'Invalid request',
-            'error_description' => 'Type undifined is not defined'
-        )));
-    }
-
-    public function testLabelIsEmpty(): void
-    {
-        $this->fieldIsEmpty('label');
-    }
-
-    public function testDisplayIsEmpty(): void
-    {
-        $this->fieldIsEmpty('display');
-    }
-
-    public function testAddDatasetFamily(): void
-    {
-        $newDatasetFamily = $this->getNewDatasetFamily();
-        $request = $this->getRequestForPost($newDatasetFamily);
-        $response = ($this->action)(
-            $request,
-            new \Slim\Http\Response(),
-            array('type' => 'dataset', 'instance' => 'default')
-        );
-        $this->assertEquals(201, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(
-            array_merge(
-                array('id' => 3),
-                $newDatasetFamily,
-                array('type' => 'dataset', 'datasets' => array())
-            )
-        ));
-        $this->assertSame(3, $this->getDatabaseTester()->getConnection()->getRowCount('dataset_family'));
-    }
-
-    public function testAddCriteriaFamily(): void
-    {
-        $newCriteriaFamily = array('label' => 'New criteria family', 'display' => 30);
-        $request = $this->getRequestForPost($newCriteriaFamily);
-        $response = ($this->action)(
-            $request,
-            new \Slim\Http\Response(),
-            array('type' => 'criteria', 'instance' => 'default')
-        );
-        $this->assertEquals(201, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(
-            array_merge(
-                array('id' => 3),
-                $newCriteriaFamily,
-                array('type' => 'criteria')
-            )
-        ));
-        $this->assertSame(3, $this->getDatabaseTester()->getConnection()->getRowCount('criteria_family'));
-    }
-
-    public function testAddOutputFamily(): void
-    {
-        $newOutputFamily = array('label' => 'New output family', 'display' => 30);
-        $request = $this->getRequestForPost($newOutputFamily);
-        $response = ($this->action)(
-            $request,
-            new \Slim\Http\Response(),
-            array('type' => 'output', 'instance' => 'default')
-        );
-        $this->assertEquals(201, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(
-            array_merge(
-                array('id' => 3),
-                $newOutputFamily,
-                array('type' => 'output')
-            )
-        ));
-        $this->assertSame(3, $this->getDatabaseTester()->getConnection()->getRowCount('output_family'));
-    }
-
-    private function fieldIsEmpty($field): void
-    {
-        $newDatasetFamily = $this->getNewDatasetFamily();
-        unset($newDatasetFamily[$field]);
-        $request = $this->getRequestForPost($newDatasetFamily);
-        $response = ($this->action)(
-            $request,
-            new \Slim\Http\Response(),
-            array('type' => 'dataset', 'instance' => 'default')
-        );
-        $this->assertEquals(400, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array(
-            'error' => 'Invalid request',
-            'error_description' => 'Param ' . $field . ' needed to add a new family'
-        )));
-    }
-
-    private function getNewDatasetFamily(): array
-    {
-        return array(
-            'label' => 'New dataset family',
-            'display' => 30
-        );
-    }
-}
diff --git a/tests/Action/Meta/FileActionTest.php b/tests/Action/Meta/FileActionTest.php
deleted file mode 100644
index 6c866f8..0000000
--- a/tests/Action/Meta/FileActionTest.php
+++ /dev/null
@@ -1,138 +0,0 @@
-<?php
-declare(strict_types=1);
-
-namespace App\Tests\Action\Meta;
-
-use Psr\Http\Message\ServerRequestInterface as Request;
-
-use App\Tests\AbstractActionMetaTestCase;
-
-final class FileActionTest extends AbstractActionMetaTestCase
-{
-    protected function setUp(): void
-    {
-        parent::setUp();
-        $this->action = new \App\Action\Meta\FileAction(
-            new \Psr\Log\NullLogger(),
-            $this->metaEntityManagerFactory
-        );
-    }
-
-    protected function getRequestForPut(array $parsedBody): Request
-    {
-        $environment = \Slim\Http\Environment::mock([
-            'REQUEST_METHOD' => 'PUT',
-            'REQUEST_URI' => '/metadata/file/1',
-            'CONTENT_TYPE'   => 'application/json',
-        ]);
-        return \Slim\Http\Request::createFromEnvironment($environment)->withParsedBody($parsedBody);
-    }
-
-    public function testGetFile(): void
-    {
-        $file = array(
-            'id' => 1,
-            'label' => 'My file',
-            'file_loc' => 'my_file.txt',
-            'type' => 'txt',
-            'display' => 10,
-            'visible' => true
-        );
-        $environment = \Slim\Http\Environment::mock([
-            'REQUEST_METHOD' => 'GET',
-            'REQUEST_URI' => '/metadata/file/1'
-        ]);
-        $request = \Slim\Http\Request::createFromEnvironment($environment);
-        $response = ($this->action)($request, new \Slim\Http\Response(), array('id' => 1, 'instance' => 'default'));
-        $this->assertEquals(200, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode($file));
-    }
-
-    public function testFileIsNotFound(): void
-    {
-        $environment = \Slim\Http\Environment::mock([
-            'REQUEST_METHOD' => 'GET',
-            'REQUEST_URI' => '/metadata/file/15'
-        ]);
-        $request = \Slim\Http\Request::createFromEnvironment($environment);
-        $response = ($this->action)($request, new \Slim\Http\Response(), array('id' => 15, 'instance' => 'default'));
-        $this->assertEquals(404, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array(
-            'error' => 'Invalid request',
-            'error_description' => 'File with id 15 is not found'
-        )));
-    }
-
-    public function testLabelIsEmpty(): void
-    {
-        $this->fieldIsEmpty('label');
-    }
-
-    public function testFileLocIsEmpty(): void
-    {
-        $this->fieldIsEmpty('file_loc');
-    }
-
-    public function testTypeIsEmpty(): void
-    {
-        $this->fieldIsEmpty('type');
-    }
-
-    public function testDisplayIsEmpty(): void
-    {
-        $this->fieldIsEmpty('display');
-    }
-
-    public function testVisibleIsEmpty(): void
-    {
-        $this->fieldIsEmpty('visible');
-    }
-
-    public function testEditFile(): void
-    {
-        $editedFile = $this->getEditedFile();
-        $request = $this->getRequestForPut($editedFile);
-        $response = ($this->action)($request, new \Slim\Http\Response(), array('id' => 1, 'instance' => 'default'));
-        $this->assertEquals(200, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode($editedFile));
-        $this->assertSame(2, $this->getDatabaseTester()->getConnection()->getRowCount('file'));
-    }
-
-    public function testRemoveFile(): void
-    {
-        $environment = \Slim\Http\Environment::mock([
-            'REQUEST_METHOD' => 'DELETE',
-            'REQUEST_URI' => '/metadata/file/1'
-        ]);
-        $request = \Slim\Http\Request::createFromEnvironment($environment);
-        $response = ($this->action)($request, new \Slim\Http\Response(), array('id' => 1, 'instance' => 'default'));
-        $this->assertEquals(200, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array('message' => 'File with id 1 is removed!')));
-        $this->assertSame(1, $this->getDatabaseTester()->getConnection()->getRowCount('file'));
-    }
-
-    private function fieldIsEmpty($field): void
-    {
-        $editedFile = $this->getEditedFile();
-        unset($editedFile[$field]);
-        $request = $this->getRequestForPut($editedFile);
-        $response = ($this->action)($request, new \Slim\Http\Response(), array('id' => 1, 'instance' => 'default'));
-        $this->assertEquals(400, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array(
-            'error' => 'Invalid request',
-            'error_description' => 'Param ' . $field . ' needed to edit the file'
-        )));
-    }
-
-    private function getEditedFile(): array
-    {
-        return array(
-            'id' => 1,
-            'label' => 'Edited File',
-            'file_loc' => 'my_edited_file.txt',
-            'type' => 'txt',
-            'display' => 20,
-            'visible' => true
-        );
-    }
-}
diff --git a/tests/Action/Meta/FileListActionTest.php b/tests/Action/Meta/FileListActionTest.php
deleted file mode 100644
index b98af73..0000000
--- a/tests/Action/Meta/FileListActionTest.php
+++ /dev/null
@@ -1,149 +0,0 @@
-<?php
-declare(strict_types=1);
-
-namespace App\Tests\Action\Meta;
-
-use Psr\Http\Message\ServerRequestInterface as Request;
-
-use App\Tests\AbstractActionMetaTestCase;
-
-final class FileListActionTest extends AbstractActionMetaTestCase
-{
-    protected function setUp(): void
-    {
-        parent::setUp();
-        $this->action = new \App\Action\Meta\FileListAction(
-            new \Psr\Log\NullLogger(),
-            $this->metaEntityManagerFactory
-        );
-    }
-
-    protected function getRequestForPost(array $parsedBody): Request
-    {
-        $environment = \Slim\Http\Environment::mock([
-            'REQUEST_METHOD' => 'POST',
-            'REQUEST_URI' => '/metadata/dataset/corot_targets/file',
-            'CONTENT_TYPE'   => 'application/json',
-        ]);
-        return \Slim\Http\Request::createFromEnvironment($environment)->withParsedBody($parsedBody);
-    }
-
-    public function testDatasetIsNotFound(): void
-    {
-        $environment = \Slim\Http\Environment::mock([
-            'REQUEST_METHOD' => 'GET',
-            'REQUEST_URI' => '/metadata/dataset/undifined/file'
-        ]);
-        $request = \Slim\Http\Request::createFromEnvironment($environment);
-        $response = ($this->action)(
-            $request,
-            new \Slim\Http\Response(),
-            array('name' => 'undifined', 'instance' => 'default')
-        );
-        $this->assertEquals(404, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array(
-            'error' => 'Invalid request',
-            'error_description' => 'Dataset with name undifined is not found'
-        )));
-    }
-
-    public function testGetFileList(): void
-    {
-        $environment = \Slim\Http\Environment::mock([
-            'REQUEST_METHOD' => 'GET',
-            'REQUEST_URI' => '/metadata/dataset/corot_targets/file'
-        ]);
-        $request = \Slim\Http\Request::createFromEnvironment($environment);
-        $response = ($this->action)(
-            $request,
-            new \Slim\Http\Response(),
-            array('name' => 'corot_targets', 'instance' => 'default')
-        );
-        $this->assertEquals(200, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array(
-            array(
-                'id' => 1,
-                'label' => 'My file',
-                'file_loc' => 'my_file.txt',
-                'type' => 'txt',
-                'display' => 10,
-                'visible' => true
-            ),
-            array(
-                'id' => 2,
-                'label' => 'My fits',
-                'file_loc' => 'file.fits',
-                'type' => 'fits',
-                'display' => 20,
-                'visible' => true
-            )
-        )));
-    }
-
-    public function testLabelIsEmpty(): void
-    {
-        $this->fieldIsEmpty('label');
-    }
-
-    public function testFileLocIsEmpty(): void
-    {
-        $this->fieldIsEmpty('file_loc');
-    }
-
-    public function testTypeIsEmpty(): void
-    {
-        $this->fieldIsEmpty('type');
-    }
-
-    public function testDisplayIsEmpty(): void
-    {
-        $this->fieldIsEmpty('display');
-    }
-
-    public function testVisibleIsEmpty(): void
-    {
-        $this->fieldIsEmpty('visible');
-    }
-
-    public function testAddFile(): void
-    {
-        $newFile = $this->getNewFile();
-        $request = $this->getRequestForPost($newFile);
-        $response = ($this->action)(
-            $request,
-            new \Slim\Http\Response(),
-            array('name' => 'corot_targets', 'instance' => 'default')
-        );
-        $this->assertEquals(201, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array_merge(array('id' => 3), $newFile)));
-        $this->assertSame(3, $this->getDatabaseTester()->getConnection()->getRowCount('file'));
-    }
-
-    private function fieldIsEmpty($field): void
-    {
-        $newFile = $this->getNewFile();
-        unset($newFile[$field]);
-        $request = $this->getRequestForPost($newFile);
-        $response = ($this->action)(
-            $request,
-            new \Slim\Http\Response(),
-            array('name' => 'corot_targets', 'instance' => 'default')
-        );
-        $this->assertEquals(400, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array(
-            'error' => 'Invalid request',
-            'error_description' => 'Param ' . $field . ' needed to add a new file'
-        )));
-    }
-
-    private function getNewFile(): array
-    {
-        return array(
-            'label' => 'My new file',
-            'file_loc' => 'new_file.fits',
-            'type' => 'fits',
-            'display' => 30,
-            'visible' => true
-        );
-    }
-}
diff --git a/tests/Action/Meta/GroupActionTest.php b/tests/Action/Meta/GroupActionTest.php
deleted file mode 100644
index 07302ac..0000000
--- a/tests/Action/Meta/GroupActionTest.php
+++ /dev/null
@@ -1,113 +0,0 @@
-<?php
-declare(strict_types=1);
-
-namespace App\Tests\Action\Meta;
-
-use Psr\Http\Message\ServerRequestInterface as Request;
-
-use App\Tests\AbstractActionMetaTestCase;
-
-final class GroupActionTest extends AbstractActionMetaTestCase
-{
-    protected function setUp(): void
-    {
-        parent::setUp();
-        $this->action = new \App\Action\Meta\GroupAction(
-            new \Psr\Log\NullLogger(),
-            $this->metaEntityManagerFactory
-        );
-    }
-
-    protected function getRequestForPut(array $parsedBody): Request
-    {
-        $environment = \Slim\Http\Environment::mock([
-            'REQUEST_METHOD' => 'PUT',
-            'REQUEST_URI' => '/metadata/group/1',
-            'CONTENT_TYPE'   => 'application/json',
-        ]);
-        return \Slim\Http\Request::createFromEnvironment($environment)->withParsedBody($parsedBody);
-    }
-
-    public function testGetGroup(): void
-    {
-        $group = array(
-            'id' => 1,
-            'label' => 'Default'
-        );
-        $environment = \Slim\Http\Environment::mock([
-            'REQUEST_METHOD' => 'GET',
-            'REQUEST_URI' => '/metadata/group/1'
-        ]);
-        $request = \Slim\Http\Request::createFromEnvironment($environment);
-        $response = ($this->action)($request, new \Slim\Http\Response(), array('id' => 1, 'instance' => 'default'));
-        $this->assertEquals(200, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode($group));
-    }
-
-    public function testGroupIsNotFound(): void
-    {
-        $environment = \Slim\Http\Environment::mock([
-            'REQUEST_METHOD' => 'GET',
-            'REQUEST_URI' => '/metadata/group/15'
-        ]);
-        $request = \Slim\Http\Request::createFromEnvironment($environment);
-        $response = ($this->action)($request, new \Slim\Http\Response(), array('id' => 15, 'instance' => 'default'));
-        $this->assertEquals(404, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array(
-            'error' => 'Invalid request',
-            'error_description' => 'Group with id 15 is not found'
-        )));
-    }
-
-    public function testLabelIsEmpty(): void
-    {
-        $this->fieldIsEmpty('label');
-    }
-
-    public function testEditGroup(): void
-    {
-        $editedGroup = $this->getEditedGroup();
-        $request = $this->getRequestForPut($editedGroup);
-        $response = ($this->action)($request, new \Slim\Http\Response(), array('id' => 1, 'instance' => 'default'));
-        $this->assertEquals(200, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode($editedGroup));
-        $this->assertSame(2, $this->getDatabaseTester()->getConnection()->getRowCount('anis_group'));
-    }
-
-    public function testRemoveGroup(): void
-    {
-        $environment = \Slim\Http\Environment::mock([
-            'REQUEST_METHOD' => 'DELETE',
-            'REQUEST_URI' => '/metadata/group/1'
-        ]);
-        $request = \Slim\Http\Request::createFromEnvironment($environment);
-        $response = ($this->action)($request, new \Slim\Http\Response(), array('id' => 1, 'instance' => 'default'));
-        $this->assertEquals(200, (int) $response->getStatusCode());
-        $this->assertSame(
-            (string) $response->getBody(),
-            json_encode(array('message' => 'Group with id 1 is removed!'))
-        );
-        $this->assertSame(1, $this->getDatabaseTester()->getConnection()->getRowCount('anis_group'));
-    }
-
-    private function fieldIsEmpty($field): void
-    {
-        $editedGroup = $this->getEditedGroup();
-        unset($editedGroup[$field]);
-        $request = $this->getRequestForPut($editedGroup);
-        $response = ($this->action)($request, new \Slim\Http\Response(), array('id' => 1, 'instance' => 'default'));
-        $this->assertEquals(400, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array(
-            'error' => 'Invalid request',
-            'error_description' => 'Param ' . $field . ' needed to edit the group'
-        )));
-    }
-
-    private function getEditedGroup(): array
-    {
-        return array(
-            'id' => 1,
-            'label' => 'Edited Group'
-        );
-    }
-}
diff --git a/tests/Action/Meta/GroupListActionTest.php b/tests/Action/Meta/GroupListActionTest.php
deleted file mode 100644
index 9831650..0000000
--- a/tests/Action/Meta/GroupListActionTest.php
+++ /dev/null
@@ -1,77 +0,0 @@
-<?php
-declare(strict_types=1);
-
-namespace App\Tests\Action\Meta;
-
-use Psr\Http\Message\ServerRequestInterface as Request;
-
-use App\Tests\AbstractActionMetaTestCase;
-
-final class GroupListActionTest extends AbstractActionMetaTestCase
-{
-    protected function setUp(): void
-    {
-        parent::setUp();
-        $this->action = new \App\Action\Meta\GroupListAction(
-            new \Psr\Log\NullLogger(),
-            $this->metaEntityManagerFactory
-        );
-    }
-
-    protected function getRequestForPost(array $parsedBody): Request
-    {
-        $environment = \Slim\Http\Environment::mock([
-            'REQUEST_METHOD' => 'POST',
-            'REQUEST_URI' => '/metadata/group',
-            'CONTENT_TYPE'   => 'application/json',
-        ]);
-        return \Slim\Http\Request::createFromEnvironment($environment)->withParsedBody($parsedBody);
-    }
-
-    public function testGetGroupList(): void
-    {
-        $environment = \Slim\Http\Environment::mock([
-            'REQUEST_METHOD' => 'GET',
-            'REQUEST_URI' => '/metadata/group'
-        ]);
-        $request = \Slim\Http\Request::createFromEnvironment($environment);
-        $response = ($this->action)($request, new \Slim\Http\Response(), array('instance' => 'default'));
-        $this->assertEquals(200, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), '[{"id":1,"label":"Default"},{"id":2,"label":"Admin"}]');
-    }
-
-    public function testLabelIsEmpty(): void
-    {
-        $this->fieldIsEmpty('label');
-    }
-
-    public function testAddGroup(): void
-    {
-        $newGroup = $this->getNewGroup();
-        $request = $this->getRequestForPost($newGroup);
-        $response = ($this->action)($request, new \Slim\Http\Response(), array('instance' => 'default'));
-        $this->assertEquals(201, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array_merge(array('id' => 3), $newGroup)));
-        $this->assertSame(3, $this->getDatabaseTester()->getConnection()->getRowCount('anis_group'));
-    }
-
-    private function fieldIsEmpty($field): void
-    {
-        $newGroup = $this->getNewGroup();
-        unset($newGroup[$field]);
-        $request = $this->getRequestForPost($newGroup);
-        $response = ($this->action)($request, new \Slim\Http\Response(), array('instance' => 'default'));
-        $this->assertEquals(400, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array(
-            'error' => 'Invalid request',
-            'error_description' => 'Param ' . $field . ' needed to add a new group'
-        )));
-    }
-
-    private function getNewGroup(): array
-    {
-        return array(
-            'label' => 'New Group'
-        );
-    }
-}
diff --git a/tests/Action/Meta/OutputCategoryActionTest.php b/tests/Action/Meta/OutputCategoryActionTest.php
deleted file mode 100644
index 7b4829a..0000000
--- a/tests/Action/Meta/OutputCategoryActionTest.php
+++ /dev/null
@@ -1,116 +0,0 @@
-<?php
-declare(strict_types=1);
-
-namespace App\Tests\Action\Meta;
-
-use Psr\Http\Message\ServerRequestInterface as Request;
-
-use App\Tests\AbstractActionMetaTestCase;
-
-final class OutputCategoryActionTest extends AbstractActionMetaTestCase
-{
-    protected function setUp(): void
-    {
-        parent::setUp();
-        $this->action = new \App\Action\Meta\OutputCategoryAction(
-            new \Psr\Log\NullLogger(),
-            $this->metaEntityManagerFactory
-        );
-    }
-
-    protected function getRequestForPut(array $parsedBody): Request
-    {
-        $environment = \Slim\Http\Environment::mock([
-            'REQUEST_METHOD' => 'PUT',
-            'REQUEST_URI' => '/metadata/category/1',
-            'CONTENT_TYPE'   => 'application/json',
-        ]);
-        return \Slim\Http\Request::createFromEnvironment($environment)->withParsedBody($parsedBody);
-    }
-
-    public function testCategoryIsNotFound(): void
-    {
-        $environment = \Slim\Http\Environment::mock([
-            'REQUEST_METHOD' => 'GET',
-            'REQUEST_URI' => '/metadata/category/15'
-        ]);
-        $request = \Slim\Http\Request::createFromEnvironment($environment);
-        $response = ($this->action)($request, new \Slim\Http\Response(), array('id' => 15, 'instance' => 'default'));
-        $this->assertEquals(404, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array(
-            'error' => 'Invalid request',
-            'error_description' => 'Output category with id 15 is not found'
-        )));
-    }
-
-    public function testGetCategory(): void
-    {
-        $environment = \Slim\Http\Environment::mock([
-            'REQUEST_METHOD' => 'GET',
-            'REQUEST_URI' => '/metadata/category/1'
-        ]);
-        $request = \Slim\Http\Request::createFromEnvironment($environment);
-        $response = ($this->action)($request, new \Slim\Http\Response(), array('id' => 1, 'instance' => 'default'));
-        $this->assertEquals(200, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array(
-            'id' => 1,
-            'label' => 'Default category',
-            'display' => 10,
-            'id_output_family' => 1
-        )));
-    }
-
-    public function testLabelIsEmpty(): void
-    {
-        $this->fieldIsEmpty('label');
-    }
-
-    public function testEditCategory(): void
-    {
-        $editedCategory = $this->getEditedCategory();
-        $request = $this->getRequestForPut($editedCategory);
-        $response = ($this->action)($request, new \Slim\Http\Response(), array('id' => 1, 'instance' => 'default'));
-        $this->assertEquals(200, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode($editedCategory));
-        $this->assertSame(2, $this->getDatabaseTester()->getConnection()->getRowCount('output_category'));
-    }
-
-    public function testRemoveCategory(): void
-    {
-        $environment = \Slim\Http\Environment::mock([
-            'REQUEST_METHOD' => 'DELETE',
-            'REQUEST_URI' => '/metadata/category/1'
-        ]);
-        $request = \Slim\Http\Request::createFromEnvironment($environment);
-        $response = ($this->action)($request, new \Slim\Http\Response(), array('id' => 1, 'instance' => 'default'));
-        $this->assertEquals(200, (int) $response->getStatusCode());
-        $this->assertSame(
-            (string) $response->getBody(),
-            json_encode(array('message' => 'Output category with id 1 is removed!'))
-        );
-        $this->assertSame(1, $this->getDatabaseTester()->getConnection()->getRowCount('output_category'));
-    }
-
-    private function fieldIsEmpty($field): void
-    {
-        $editedCategory = $this->getEditedCategory();
-        unset($editedCategory[$field]);
-        $request = $this->getRequestForPut($editedCategory);
-        $response = ($this->action)($request, new \Slim\Http\Response(), array('id' => 1, 'instance' => 'default'));
-        $this->assertEquals(400, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array(
-            'error' => 'Invalid request',
-            'error_description' => 'Param ' . $field . ' needed to edit the output category'
-        )));
-    }
-
-    private function getEditedCategory(): array
-    {
-        return array(
-            'id' => 1,
-            'label' => 'Edited Category',
-            'display' => 20,
-            'id_output_family' => 1
-        );
-    }
-}
diff --git a/tests/Action/Meta/OutputCategoryListActionTest.php b/tests/Action/Meta/OutputCategoryListActionTest.php
deleted file mode 100644
index 414b8fb..0000000
--- a/tests/Action/Meta/OutputCategoryListActionTest.php
+++ /dev/null
@@ -1,92 +0,0 @@
-<?php
-declare(strict_types=1);
-
-namespace App\Tests\Action\Meta;
-
-use Psr\Http\Message\ServerRequestInterface as Request;
-
-use App\Tests\AbstractActionMetaTestCase;
-
-final class OutputCategoryListActionTest extends AbstractActionMetaTestCase
-{
-    protected function setUp(): void
-    {
-        parent::setUp();
-        $this->action = new \App\Action\Meta\OutputCategoryListAction(
-            new \Psr\Log\NullLogger(),
-            $this->metaEntityManagerFactory
-        );
-    }
-
-    protected function getRequestForPost(array $parsedBody): Request
-    {
-        $environment = \Slim\Http\Environment::mock([
-            'REQUEST_METHOD' => 'POST',
-            'REQUEST_URI' => '/metadata/category',
-            'CONTENT_TYPE'   => 'application/json',
-        ]);
-        return \Slim\Http\Request::createFromEnvironment($environment)->withParsedBody($parsedBody);
-    }
-
-    public function testGetCategoryList(): void
-    {
-        $environment = \Slim\Http\Environment::mock([
-            'REQUEST_METHOD' => 'GET',
-            'REQUEST_URI' => '/metadata/category'
-        ]);
-        $request = \Slim\Http\Request::createFromEnvironment($environment);
-        $response = ($this->action)($request, new \Slim\Http\Response(), array('instance' => 'default'));
-        $this->assertEquals(200, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array(
-            array(
-                'id' => 1,
-                'label' => 'Default category',
-                'display' => 10,
-                'id_output_family' => 1
-            ),
-            array(
-                'id' => 2,
-                'label' => 'Photometry',
-                'display' => 20,
-                'id_output_family' => 1
-            )
-        )));
-    }
-
-    public function testLabelIsEmpty(): void
-    {
-        $this->fieldIsEmpty('label');
-    }
-
-    public function testAddCategory(): void
-    {
-        $newCategory = $this->getNewCategory();
-        $request = $this->getRequestForPost($newCategory);
-        $response = ($this->action)($request, new \Slim\Http\Response(), array('instance' => 'default'));
-        $this->assertEquals(201, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array_merge(array('id' => 3), $newCategory)));
-        $this->assertSame(3, $this->getDatabaseTester()->getConnection()->getRowCount('output_category'));
-    }
-
-    private function fieldIsEmpty($field): void
-    {
-        $newCategory = $this->getNewCategory();
-        unset($newCategory[$field]);
-        $request = $this->getRequestForPost($newCategory);
-        $response = ($this->action)($request, new \Slim\Http\Response(), array('instance' => 'default'));
-        $this->assertEquals(400, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array(
-            'error' => 'Invalid request',
-            'error_description' => 'Param ' . $field . ' needed to add a new output category'
-        )));
-    }
-
-    private function getNewCategory(): array
-    {
-        return array(
-            'label' => 'New Category',
-            'display' => 30,
-            'id_output_family' => 1
-        );
-    }
-}
diff --git a/tests/Action/Meta/ProjectActionTest.php b/tests/Action/Meta/ProjectActionTest.php
deleted file mode 100644
index 6f5aefc..0000000
--- a/tests/Action/Meta/ProjectActionTest.php
+++ /dev/null
@@ -1,159 +0,0 @@
-<?php
-declare(strict_types=1);
-
-namespace App\Tests\Action\Meta;
-
-use Psr\Http\Message\ServerRequestInterface as Request;
-
-use App\Tests\AbstractActionMetaTestCase;
-
-final class ProjectActionTest extends AbstractActionMetaTestCase
-{
-    protected function setUp(): void
-    {
-        parent::setUp();
-        $this->action = new \App\Action\Meta\ProjectAction(
-            new \Psr\Log\NullLogger(),
-            $this->metaEntityManagerFactory
-        );
-    }
-
-    protected function getRequestForPut(array $parsedBody): Request
-    {
-        $environment = \Slim\Http\Environment::mock([
-            'REQUEST_METHOD' => 'PUT',
-            'REQUEST_URI' => '/metadata/project/1',
-            'CONTENT_TYPE'   => 'application/json',
-        ]);
-        return \Slim\Http\Request::createFromEnvironment($environment)->withParsedBody($parsedBody);
-    }
-
-    public function testGetProject(): void
-    {
-        $project = array(
-            'name' => 'vuds',
-            'label' => 'VUDS',
-            'description' => 'VUDS project',
-            'link' => 'http://cesam.lam.fr/vuds',
-            'manager' => 'Olivier Le Fèvre',
-            'id_database' => 1
-        );
-        $environment = \Slim\Http\Environment::mock([
-            'REQUEST_METHOD' => 'GET',
-            'REQUEST_URI' => '/metadata/project/vuds'
-        ]);
-        $request = \Slim\Http\Request::createFromEnvironment($environment);
-        $response = ($this->action)(
-            $request,
-            new \Slim\Http\Response(),
-            array('name' => 'vuds', 'instance' => 'default')
-        );
-        $this->assertEquals(200, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode($project));
-    }
-
-    public function testLabelIsEmpty(): void
-    {
-        $this->fieldIsEmpty('label');
-    }
-
-    public function testDescriptionIsEmpty(): void
-    {
-        $this->fieldIsEmpty('description');
-    }
-
-    public function testLinkIsEmpty(): void
-    {
-        $this->fieldIsEmpty('link');
-    }
-
-    public function testManagerIsEmpty(): void
-    {
-        $this->fieldIsEmpty('manager');
-    }
-
-    public function testIdDatabaseIsEmpty(): void
-    {
-        $this->fieldIsEmpty('id_database');
-    }
-
-    public function testDatabaseIsNotFound(): void
-    {
-        $editedProject = $this->getEditedProject();
-        $editedProject['id_database'] = 15;
-        $request = $this->getRequestForPut($editedProject);
-        $response = ($this->action)(
-            $request,
-            new \Slim\Http\Response(),
-            array('name' => 'vuds', 'instance' => 'default')
-        );
-        $this->assertEquals(404, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array(
-            'error' => 'Invalid request',
-            'error_description' => 'Database with id 15 is not found'
-        )));
-    }
-
-    public function testEditProject(): void
-    {
-        $editedProject = $this->getEditedProject();
-        $request = $this->getRequestForPut($editedProject);
-        $response = ($this->action)(
-            $request,
-            new \Slim\Http\Response(),
-            array('name' => 'vuds', 'instance' => 'default')
-        );
-        $this->assertEquals(200, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode($editedProject));
-        $this->assertSame(2, $this->getDatabaseTester()->getConnection()->getRowCount('project'));
-    }
-
-    public function testRemoveProject(): void
-    {
-        $environment = \Slim\Http\Environment::mock([
-            'REQUEST_METHOD' => 'DELETE',
-            'REQUEST_URI' => '/metadata/project/vuds'
-        ]);
-        $request = \Slim\Http\Request::createFromEnvironment($environment);
-        $response = ($this->action)(
-            $request,
-            new \Slim\Http\Response(),
-            array('name' => 'vuds', 'instance' => 'default')
-        );
-        $this->assertEquals(200, (int) $response->getStatusCode());
-        $this->assertSame(
-            (string) $response->getBody(),
-            json_encode(array('message' => 'Project vuds is removed!'))
-        );
-        $this->assertSame(1, $this->getDatabaseTester()->getConnection()->getRowCount('project'));
-    }
-
-    private function fieldIsEmpty($field): void
-    {
-        $editedProject = $this->getEditedProject();
-        unset($editedProject[$field]);
-        $request = $this->getRequestForPut($editedProject);
-        $response = ($this->action)(
-            $request,
-            new \Slim\Http\Response(),
-            array('name' => 'vuds', 'instance' => 'default')
-        );
-        $this->assertEquals(400, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array(
-            'error' => 'Invalid request',
-            'error_description' => 'Param ' . $field . ' needed to edit the project'
-        )));
-    }
-
-    private function getEditedProject(): array
-    {
-        return array(
-            'name' => 'vuds',
-            'label' => 'Edited Project',
-            'description' => 'Description project',
-            'link' => 'http://link.fr',
-            'manager' => 'My Manager',
-            'id_database' => 1
-        );
-    }
-}
diff --git a/tests/Action/Meta/ProjectListActionTest.php b/tests/Action/Meta/ProjectListActionTest.php
deleted file mode 100644
index 2c46f03..0000000
--- a/tests/Action/Meta/ProjectListActionTest.php
+++ /dev/null
@@ -1,138 +0,0 @@
-<?php
-declare(strict_types=1);
-
-namespace App\Tests\Action\Meta;
-
-use Psr\Http\Message\ServerRequestInterface as Request;
-
-use App\Tests\AbstractActionMetaTestCase;
-
-final class ProjectListActionTest extends AbstractActionMetaTestCase
-{
-    protected function setUp(): void
-    {
-        parent::setUp();
-        $this->action = new \App\Action\Meta\ProjectListAction(
-            new \Psr\Log\NullLogger(),
-            $this->metaEntityManagerFactory
-        );
-    }
-
-    protected function getRequestForPost(array $parsedBody): Request
-    {
-        $environment = \Slim\Http\Environment::mock([
-            'REQUEST_METHOD' => 'POST',
-            'REQUEST_URI' => '/metadata/project',
-            'CONTENT_TYPE'   => 'application/json',
-        ]);
-        return \Slim\Http\Request::createFromEnvironment($environment)->withParsedBody($parsedBody);
-    }
-
-    public function testProjectList(): void
-    {
-        $environment = \Slim\Http\Environment::mock([
-            'REQUEST_METHOD' => 'GET',
-            'REQUEST_URI' => '/metadata/project'
-        ]);
-        $request = \Slim\Http\Request::createFromEnvironment($environment);
-        $response = ($this->action)($request, new \Slim\Http\Response(), array('instance' => 'default'));
-        $this->assertEquals(200, (int) $response->getStatusCode());
-        $json = array(
-            [
-                "name" => "vuds",
-                "label" => "VUDS",
-                "description" => "VUDS project",
-                "link" => "http://cesam.lam.fr/vuds",
-                "manager" => "Olivier Le Fèvre",
-                "id_database" => 1
-            ],
-            [
-                "name" => "corot",
-                "label" => "CoRoT",
-                "description" => "CoRoT project",
-                "link" => "http://corot.cnes.fr",
-                "manager" => "Magali Deleuil",
-                "id_database" => 2
-            ]
-        );
-        $this->assertSame((string) $response->getBody(), json_encode($json, JSON_UNESCAPED_SLASHES));
-    }
-
-    public function testNameIsEmpty(): void
-    {
-        $this->fieldIsEmpty('name');
-    }
-
-    public function testLabelIsEmpty(): void
-    {
-        $this->fieldIsEmpty('label');
-    }
-
-    public function testDescriptionIsEmpty(): void
-    {
-        $this->fieldIsEmpty('description');
-    }
-
-    public function testLinkIsEmpty(): void
-    {
-        $this->fieldIsEmpty('link');
-    }
-
-    public function testManagerIsEmpty(): void
-    {
-        $this->fieldIsEmpty('manager');
-    }
-
-    public function testIdDatabaseIsEmpty(): void
-    {
-        $this->fieldIsEmpty('id_database');
-    }
-
-    public function testDatabaseIsNotFound(): void
-    {
-        $newProject = $this->getNewProject();
-        $newProject['id_database'] = 15;
-        $request = $this->getRequestForPost($newProject);
-        $response = ($this->action)($request, new \Slim\Http\Response(), array('instance' => 'default'));
-        $this->assertEquals(404, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array(
-            'error' => 'Invalid request',
-            'error_description' => 'Database with id 15 is not found'
-        )));
-    }
-
-    public function testAddProject(): void
-    {
-        $newProject = $this->getNewProject();
-        $request = $this->getRequestForPost($newProject);
-        $response = ($this->action)($request, new \Slim\Http\Response(), array('instance' => 'default'));
-        $this->assertEquals(201, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode($newProject, JSON_UNESCAPED_SLASHES));
-        $this->assertSame(3, $this->getDatabaseTester()->getConnection()->getRowCount('project'));
-    }
-
-    private function fieldIsEmpty($field): void
-    {
-        $newProject = $this->getNewProject();
-        unset($newProject[$field]);
-        $request = $this->getRequestForPost($newProject);
-        $response = ($this->action)($request, new \Slim\Http\Response(), array('instance' => 'default'));
-        $this->assertEquals(400, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array(
-            'error' => 'Invalid request',
-            'error_description' => 'Param ' . $field . ' needed to add a new project'
-        )));
-    }
-
-    private function getNewProject(): array
-    {
-        return array(
-            'name' => 'project',
-            'label' => 'My Project',
-            'description' => 'Project description',
-            'link' => 'http://www.project.com',
-            'manager' => 'M. Nobody',
-            'id_database' => 1
-        );
-    }
-}
diff --git a/tests/Action/Meta/TableListActionTest.php b/tests/Action/Meta/TableListActionTest.php
deleted file mode 100644
index e09af09..0000000
--- a/tests/Action/Meta/TableListActionTest.php
+++ /dev/null
@@ -1,49 +0,0 @@
-<?php
-declare(strict_types=1);
-
-namespace App\Tests\Action\Meta;
-
-use Psr\Http\Message\ServerRequestInterface as Request;
-use Doctrine\DBAL\Connection as DBALConnection;
-
-use App\Utils\DBALConnectionFactory;
-use App\Tests\AbstractActionMetaTestCase;
-
-final class TableListActionTest extends AbstractActionMetaTestCase
-{
-    protected function setUp(): void
-    {
-        parent::setUp();
-        $this->action = new \App\Action\Meta\TableListAction(
-            new \Psr\Log\NullLogger(),
-            $this->metaEntityManagerFactory,
-            $this->getMockDBALConnectionFactory(),
-            base64_decode('r3Q8C7LgIrRcTtI8I6EPzFwrDXJ4adgnGQ9V/pWVI8M=')
-        );
-    }
-
-    protected function getMockDBALConnectionFactory(): DBALConnectionFactory
-    {
-        $mockDBALConnection = $this->createMock(DBALConnection::class);
-        $mockConnectionFactory = $this->createMock(DBALConnectionFactory::class);
-        $mockConnectionFactory
-            ->method('create')
-            ->willReturn($mockDBALConnection);
-        return $mockConnectionFactory;
-    }
-
-    public function testDatabaseIsNotFound(): void
-    {
-        $environment = \Slim\Http\Environment::mock([
-            'REQUEST_METHOD' => 'GET',
-            'REQUEST_URI' => '/metadata/database/15/table'
-        ]);
-        $request = \Slim\Http\Request::createFromEnvironment($environment);
-        $response = ($this->action)($request, new \Slim\Http\Response(), array('id' => 15, 'instance' => 'default'));
-        $this->assertEquals(404, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array(
-            'error' => 'Invalid request',
-            'error_description' => 'Database with id 15 is not found'
-        )));
-    }
-}
diff --git a/tests/Action/OutputCategoryActionTest.php b/tests/Action/OutputCategoryActionTest.php
new file mode 100644
index 0000000..277962e
--- /dev/null
+++ b/tests/Action/OutputCategoryActionTest.php
@@ -0,0 +1,146 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Tests\Action;
+
+use PHPUnit\Framework\TestCase;
+use Nyholm\Psr7\ServerRequest;
+use Nyholm\Psr7\Response;
+use Slim\Exception\HttpNotFoundException;
+use Slim\Exception\HttpBadRequestException;
+use App\tests\EntityManagerBuilder;
+use App\Entity\OutputCategory;
+use App\Entity\OutputFamily;
+
+final class OutputCategoryActionTest extends TestCase
+{
+    private $action;
+    private $entityManager;
+
+    protected function setUp(): void
+    {
+        $this->entityManager = EntityManagerBuilder::getInstance();
+        $this->action = new \App\Action\OutputCategoryAction($this->entityManager);
+    }
+
+    public function testOptionsHttpMethod(): void
+    {
+        $request = $this->getRequest('OPTIONS');
+        $response = ($this->action)($request, new Response(), array());
+        $this->assertSame($response->getHeaderLine('Access-Control-Allow-Methods'), 'GET, PUT, DELETE, OPTIONS');
+    }
+
+    public function testOutputCategoryIsNotFound(): void
+    {
+        $this->expectException(HttpNotFoundException::class);
+        $this->expectExceptionMessage('Output category with id 1 is not found');
+        $request = $this->getRequest('GET');
+        $response = ($this->action)($request, new Response(), array('id' => 1));
+        $this->assertEquals(404, (int) $response->getStatusCode());
+    }
+
+    public function testGetAnOutputCategoryById(): void
+    {
+        $outputCategory = $this->addAnOutputCategory();
+        $request = $this->getRequest('GET');
+        $response = ($this->action)($request, new Response(), array('id' => 1));
+        $this->assertSame(json_encode($outputCategory), (string) $response->getBody());
+    }
+
+    public function testEditAnOutputCategoryEmptyLabelField(): void
+    {
+        $this->addAnOutputCategory();
+        $this->expectException(HttpBadRequestException::class);
+        $this->expectExceptionMessage('Param label needed to edit the output category');
+        $request = $this->getRequest('PUT')->withParsedBody(array());
+        $response = ($this->action)($request, new Response(), array('id' => 1));
+        $this->assertEquals(400, (int) $response->getStatusCode());
+    }
+
+    public function testEditAnOutputCategoryOutputFamilyIsNotFound(): void
+    {
+        $this->addAnOutputCategory();
+        $this->expectException(HttpBadRequestException::class);
+        $this->expectExceptionMessage('Output family with id 2 is not found');
+        $fields = array(
+            'label' => 'Default output category',
+            'display' => 20,
+            'id_output_family' => 2
+        );
+        $request = $this->getRequest('PUT')->withParsedBody($fields);
+        $response = ($this->action)($request, new Response(), array('id' => 1));
+        $this->assertEquals(400, (int) $response->getStatusCode());
+    }
+
+    public function testEditAnOutputCategory(): void
+    {
+        $this->addAnOutputCategory();
+        $fields = array(
+            'label' => 'Default output category',
+            'display' => 20,
+            'id_output_family' => 1
+        );
+        $request = $this->getRequest('PUT')->withParsedBody($fields);
+        $response = ($this->action)($request, new Response(), array('id' => 1));
+        $this->assertSame(
+            json_encode(array_merge(['id' => 1], $fields)),
+            (string) $response->getBody()
+        );
+        $this->assertEquals(200, (int) $response->getStatusCode());
+    }
+
+    public function testDeleteAnOutputCategory(): void
+    {
+        $this->addAnOutputCategory();
+        $request = $this->getRequest('DELETE');
+        $response = ($this->action)($request, new Response(), array('id' => 1));
+        $this->assertSame(
+            json_encode(array('message' => 'Output category with id 1 is removed!')),
+            (string) $response->getBody()
+        );
+    }
+
+    protected function tearDown(): void
+    {
+        $this->entityManager->getConnection()->close();
+    }
+
+    private function getRequest(string $method): ServerRequest
+    {
+        return new ServerRequest($method, '/output-category/1', array(
+            'Content-Type' => 'application/json'
+        ));
+    }
+
+    private function addOutputFamily(): OutputFamily
+    {
+        $family = new OutputFamily();
+        $family->setLabel('Default output family');
+        $family->setDisplay(10);
+        $this->entityManager->persist($family);
+        $this->entityManager->flush();
+        return $family;
+    }
+
+    private function addAnOutputCategory(): OutputCategory
+    {
+        $outputFamily = $this->addOutputFamily();
+
+        $outputCategory = new OutputCategory();
+        $outputCategory->setLabel('Default output category');
+        $outputCategory->setDisplay(10);
+        $outputCategory->setOutputFamily($outputFamily);
+        $this->entityManager->persist($outputCategory);
+        $this->entityManager->flush();
+        return $outputCategory;
+    }
+}
diff --git a/tests/Action/OutputCategoryListActionTest.php b/tests/Action/OutputCategoryListActionTest.php
new file mode 100644
index 0000000..3c396e9
--- /dev/null
+++ b/tests/Action/OutputCategoryListActionTest.php
@@ -0,0 +1,133 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Tests\Action;
+
+use PHPUnit\Framework\TestCase;
+use Nyholm\Psr7\ServerRequest;
+use Nyholm\Psr7\Response;
+use Slim\Exception\HttpBadRequestException;
+use App\tests\EntityManagerBuilder;
+use App\Entity\OutputFamily;
+use App\Entity\OutputCategory;
+
+final class OutputCategoryListActionTest extends TestCase
+{
+    private $action;
+    private $entityManager;
+
+    protected function setUp(): void
+    {
+        $this->entityManager = EntityManagerBuilder::getInstance();
+        $this->action = new \App\Action\OutputCategoryListAction($this->entityManager);
+    }
+
+    public function testOptionsHttpMethod(): void
+    {
+        $request = $this->getRequest('OPTIONS');
+        $response = ($this->action)($request, new Response(), array());
+        $this->assertSame($response->getHeaderLine('Access-Control-Allow-Methods'), 'GET, POST, OPTIONS');
+    }
+
+    public function testGetAllOutputCategories(): void
+    {
+        $outputCategories = $this->addOutputCategories();
+        $request = $this->getRequest('GET');
+        $response = ($this->action)($request, new Response(), array());
+        $this->assertSame(
+            json_encode($outputCategories),
+            (string) $response->getBody()
+        );
+    }
+
+    public function testAddANewOutputCategoryEmptyLabelField(): void
+    {
+        $this->expectException(HttpBadRequestException::class);
+        $this->expectExceptionMessage('Param label needed to add a new output category');
+        $request = $this->getRequest('POST')->withParsedBody(array());
+        $response = ($this->action)($request, new Response(), array());
+        $this->assertEquals(400, (int) $response->getStatusCode());
+    }
+
+    public function testAddANewOutputCategoryOutputFamilyIsNotFound(): void
+    {
+        $this->expectException(HttpBadRequestException::class);
+        $this->expectExceptionMessage('Output family with id 1 is not found');
+        $fields = array(
+            'label' => 'Default output category',
+            'display' => 10,
+            'id_output_family' => 1
+        );
+        $request = $this->getRequest('POST')->withParsedBody($fields);
+        $response = ($this->action)($request, new Response(), array());
+        $this->assertEquals(400, (int) $response->getStatusCode());
+    }
+
+    public function testAddANewOutputCategory(): void
+    {
+        $this->addOutputFamily();
+        $fields = array(
+            'label' => 'Default output category',
+            'display' => 10,
+            'id_output_family' => 1
+        );
+        $request = $this->getRequest('POST')->withParsedBody($fields);
+        $response = ($this->action)($request, new Response(), array());
+        $this->assertSame(
+            json_encode(array_merge(['id' => 1], $fields)),
+            (string) $response->getBody()
+        );
+        $this->assertEquals(201, (int) $response->getStatusCode());
+    }
+
+    protected function tearDown(): void
+    {
+        $this->entityManager->getConnection()->close();
+    }
+
+    private function getRequest(string $method): ServerRequest
+    {
+        return new ServerRequest($method, '/output-category', array(
+            'Content-Type' => 'application/json'
+        ));
+    }
+
+    private function addOutputFamily(): OutputFamily
+    {
+        $family = new OutputFamily();
+        $family->setLabel('Default output family');
+        $family->setDisplay(10);
+        $this->entityManager->persist($family);
+        $this->entityManager->flush();
+        return $family;
+    }
+
+    private function addOutputCategories(): array
+    {
+        $outputFamily = $this->addOutputFamily();
+
+        $outputCategory1 = new OutputCategory();
+        $outputCategory1->setLabel('Default output category');
+        $outputCategory1->setDisplay(10);
+        $outputCategory1->setOutputFamily($outputFamily);
+        $this->entityManager->persist($outputCategory1);
+
+        $outputCategory2 = new OutputCategory();
+        $outputCategory2->setLabel('My output category');
+        $outputCategory2->setDisplay(20);
+        $outputCategory2->setOutputFamily($outputFamily);
+        $this->entityManager->persist($outputCategory2);
+
+        $this->entityManager->flush();
+        return array($outputCategory1, $outputCategory2);
+    }
+}
diff --git a/tests/Action/ProjectActionTest.php b/tests/Action/ProjectActionTest.php
new file mode 100644
index 0000000..5160a6c
--- /dev/null
+++ b/tests/Action/ProjectActionTest.php
@@ -0,0 +1,156 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Tests\Action;
+
+use PHPUnit\Framework\TestCase;
+use Nyholm\Psr7\ServerRequest;
+use Nyholm\Psr7\Response;
+use Slim\Exception\HttpNotFoundException;
+use Slim\Exception\HttpBadRequestException;
+use App\tests\EntityManagerBuilder;
+use App\Entity\Database;
+use App\Entity\Project;
+
+final class ProjectActionTest extends TestCase
+{
+    private $action;
+    private $entityManager;
+
+    protected function setUp(): void
+    {
+        $this->entityManager = EntityManagerBuilder::getInstance();
+        $this->action = new \App\Action\ProjectAction($this->entityManager);
+    }
+
+    public function testOptionsHttpMethod(): void
+    {
+        $request = $this->getRequest('OPTIONS');
+        $response = ($this->action)($request, new Response(), array());
+        $this->assertSame($response->getHeaderLine('Access-Control-Allow-Methods'), 'GET, PUT, DELETE, OPTIONS');
+    }
+
+    public function testProjectIsNotFound(): void
+    {
+        $this->expectException(HttpNotFoundException::class);
+        $this->expectExceptionMessage('Project with name anis_project is not found');
+        $request = $this->getRequest('GET');
+        $response = ($this->action)($request, new Response(), array('name' => 'anis_project'));
+        $this->assertEquals(404, (int) $response->getStatusCode());
+    }
+
+    public function testGetAProjectByName(): void
+    {
+        $project = $this->addAProject();
+        $request = $this->getRequest('GET');
+        $response = ($this->action)($request, new Response(), array('name' => 'anis_project'));
+        $this->assertSame(json_encode($project, JSON_UNESCAPED_SLASHES), (string) $response->getBody());
+    }
+
+    public function testEditAProjectEmptyLabelField(): void
+    {
+        $this->addAProject();
+        $this->expectException(HttpBadRequestException::class);
+        $this->expectExceptionMessage('Param label needed to edit the project');
+        $request = $this->getRequest('PUT')->withParsedBody(array());
+        $response = ($this->action)($request, new Response(), array('name' => 'anis_project'));
+        $this->assertEquals(400, (int) $response->getStatusCode());
+    }
+
+    public function testEditAProjectDatabaseNotFound(): void
+    {
+        $this->addAProject();
+        $fields = $this->getEditProjectFields();
+        $fields['id_database'] = 2;
+        $this->expectException(HttpBadRequestException::class);
+        $this->expectExceptionMessage('Database with id 2 is not found');
+        $request = $this->getRequest('PUT')->withParsedBody($fields);
+        $response = ($this->action)($request, new Response(), array('name' => 'anis_project'));
+        $this->assertEquals(400, (int) $response->getStatusCode());
+    }
+
+    public function testEditAProject(): void
+    {
+        $this->addAProject();
+        $fields = $this->getEditProjectFields();
+        $request = $this->getRequest('PUT')->withParsedBody($fields);
+        $response = ($this->action)($request, new Response(), array('name' => 'anis_project'));
+        $this->assertSame(
+            json_encode(array_merge(['name' => 'anis_project'], $fields), JSON_UNESCAPED_SLASHES),
+            (string) $response->getBody()
+        );
+    }
+
+    public function testDeleteAProject(): void
+    {
+        $this->addAProject();
+        $request = $this->getRequest('DELETE');
+        $response = ($this->action)($request, new Response(), array('name' => 'anis_project'));
+        $this->assertSame(
+            json_encode(array('message' => 'Project anis_project is removed!')),
+            (string) $response->getBody()
+        );
+    }
+
+    protected function tearDown(): void
+    {
+        $this->entityManager->getConnection()->close();
+    }
+
+    private function getRequest(string $method): ServerRequest
+    {
+        return new ServerRequest($method, '/project/anis_project', array(
+            'Content-Type' => 'application/json'
+        ));
+    }
+
+    private function getEditProjectFields(): array
+    {
+        return array(
+            'label' => 'New label project',
+            'description' => 'Anis Project Test',
+            'link' => 'http://project.com',
+            'manager' => 'M. Durand',
+            'id_database' => 1
+        );
+    }
+
+    private function addDatabase(): Database
+    {
+        $database = new Database();
+        $database->setLabel('Test1');
+        $database->setDbName('test1');
+        $database->setType('pgsql');
+        $database->setHost('db');
+        $database->setPort(5432);
+        $database->setLogin('test');
+        $database->setPassword('test');
+        $this->entityManager->persist($database);
+        $this->entityManager->flush();
+        return $database;
+    }
+
+    private function addAProject(): Project
+    {
+        $database = $this->addDatabase();
+
+        $project = new Project('anis_project');
+        $project->setLabel('Anis Project Test');
+        $project->setDescription('Anis Project Test');
+        $project->setLink('http://project.com');
+        $project->setManager('M. Durand');
+        $project->setDatabase($database);
+        $this->entityManager->persist($project);
+        $this->entityManager->flush();
+        return $project;
+    }
+}
diff --git a/tests/Action/ProjectListActionTest.php b/tests/Action/ProjectListActionTest.php
new file mode 100644
index 0000000..ef2a927
--- /dev/null
+++ b/tests/Action/ProjectListActionTest.php
@@ -0,0 +1,146 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Tests\Action;
+
+use PHPUnit\Framework\TestCase;
+use Nyholm\Psr7\ServerRequest;
+use Nyholm\Psr7\Response;
+use Slim\Exception\HttpBadRequestException;
+use App\tests\EntityManagerBuilder;
+use App\Entity\Database;
+use App\Entity\Project;
+
+final class ProjectListActionTest extends TestCase
+{
+    private $action;
+    private $entityManager;
+
+    protected function setUp(): void
+    {
+        $this->entityManager = EntityManagerBuilder::getInstance();
+        $this->action = new \App\Action\ProjectListAction($this->entityManager);
+    }
+
+    public function testOptionsHttpMethod(): void
+    {
+        $request = $this->getRequest('OPTIONS');
+        $response = ($this->action)($request, new Response(), array());
+        $this->assertSame($response->getHeaderLine('Access-Control-Allow-Methods'), 'GET, POST, OPTIONS');
+    }
+
+    public function testGetAllProjects(): void
+    {
+        $projects = $this->addProjects();
+        $request = $this->getRequest('GET');
+        $response = ($this->action)($request, new Response(), array());
+        $this->assertSame(
+            json_encode($projects, JSON_UNESCAPED_SLASHES),
+            (string) $response->getBody()
+        );
+    }
+
+    public function testAddANewProjectEmptyNameField(): void
+    {
+        $this->expectException(HttpBadRequestException::class);
+        $this->expectExceptionMessage('Param name needed to add a new project');
+        $request = $this->getRequest('POST')->withParsedBody(array());
+        $response = ($this->action)($request, new Response(), array());
+        $this->assertEquals(400, (int) $response->getStatusCode());
+    }
+
+    public function testAddANewProjectDatabaseNotFound(): void
+    {
+        $this->expectException(HttpBadRequestException::class);
+        $this->expectExceptionMessage('Database with id 1 is not found');
+        $fields = $this->getNewProjectFields();
+        $request = $this->getRequest('POST')->withParsedBody($fields);
+        $response = ($this->action)($request, new Response(), array());
+        $this->assertEquals(400, (int) $response->getStatusCode());
+    }
+
+    public function testAddANewProject(): void
+    {
+        $this->addDatabase();
+        $fields = $this->getNewProjectFields();
+        $request = $this->getRequest('POST')->withParsedBody($fields);
+        $response = ($this->action)($request, new Response(), array());
+        $this->assertSame(
+            json_encode($fields, JSON_UNESCAPED_SLASHES),
+            (string) $response->getBody()
+        );
+        $this->assertEquals(201, (int) $response->getStatusCode());
+    }
+
+    protected function tearDown(): void
+    {
+        $this->entityManager->getConnection()->close();
+    }
+
+    private function getRequest(string $method): ServerRequest
+    {
+        return new ServerRequest($method, '/project', array(
+            'Content-Type' => 'application/json'
+        ));
+    }
+
+    private function getNewProjectFields(): array
+    {
+        return array(
+            'name' => 'anis_project',
+            'label' => 'Anis Project Test',
+            'description' => 'Anis Project Test',
+            'link' => 'http://project.com',
+            'manager' => 'M. Durand',
+            'id_database' => 1
+        );
+    }
+
+    private function addDatabase(): Database
+    {
+        $database = new Database();
+        $database->setLabel('Test1');
+        $database->setDbName('test1');
+        $database->setType('pgsql');
+        $database->setHost('db');
+        $database->setPort(5432);
+        $database->setLogin('test');
+        $database->setPassword('test');
+        $this->entityManager->persist($database);
+        $this->entityManager->flush();
+        return $database;
+    }
+
+    private function addProjects(): array
+    {
+        $database = $this->addDatabase();
+
+        $project = new Project('test');
+        $project->setLabel('Test project');
+        $project->setDescription('Test description');
+        $project->setLink('http://test.com');
+        $project->setManager('User1');
+        $project->setDatabase($database);
+        $this->entityManager->persist($project);
+
+        $project2 = new Project('test2');
+        $project2->setLabel('Test2 project');
+        $project2->setDescription('Test2 description');
+        $project2->setLink('http://test2.com');
+        $project2->setManager('User2');
+        $project2->setDatabase($database);
+        $this->entityManager->persist($project2);
+
+        $this->entityManager->flush();
+        return array($project, $project2);
+    }
+}
diff --git a/tests/Action/Root/RootActionTest.php b/tests/Action/Root/RootActionTest.php
deleted file mode 100644
index 84dde23..0000000
--- a/tests/Action/Root/RootActionTest.php
+++ /dev/null
@@ -1,24 +0,0 @@
-<?php
-declare(strict_types=1);
-
-namespace App\Tests\Root;
-
-use PHPUnit\Framework\TestCase;
-
-final class RootActionTest extends TestCase
-{
-    public function testRootGet(): void
-    {
-        $action = new \App\Action\Root\RootAction(new \Psr\Log\NullLogger());
-
-        $environment = \Slim\Http\Environment::mock([
-            'REQUEST_METHOD' => 'GET',
-            'REQUEST_URI' => '/'
-        ]);
-        $request = \Slim\Http\Request::createFromEnvironment($environment);
-
-        $response = $action($request, new \Slim\Http\Response(), array());
-        $this->assertEquals(200, (int) $response->getStatusCode());
-        $this->assertSame((string) $response->getBody(), json_encode(array('message' => 'it works!')));
-    }
-}
diff --git a/tests/Action/RootActionTest.php b/tests/Action/RootActionTest.php
new file mode 100644
index 0000000..46bd6f4
--- /dev/null
+++ b/tests/Action/RootActionTest.php
@@ -0,0 +1,36 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Tests\Action;
+
+use PHPUnit\Framework\TestCase;
+use Nyholm\Psr7\ServerRequest;
+use Nyholm\Psr7\Response;
+
+final class RootActionTest extends TestCase
+{
+    private $action;
+
+    protected function setUp(): void
+    {
+        $this->action = new \App\Action\RootAction();
+    }
+
+    public function testAction(): void
+    {
+        $request = new ServerRequest('GET', '/', array(
+            'Content-Type' => 'application/json'
+        ));
+        $response = ($this->action)($request, new Response(), array());
+        $this->assertSame('{"message":"it works!"}', (string) $response->getBody());
+    }
+}
diff --git a/tests/Action/TableListActionTest.php b/tests/Action/TableListActionTest.php
new file mode 100644
index 0000000..767de4e
--- /dev/null
+++ b/tests/Action/TableListActionTest.php
@@ -0,0 +1,120 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Tests\Action;
+
+use PHPUnit\Framework\TestCase;
+use Nyholm\Psr7\ServerRequest;
+use Nyholm\Psr7\Response;
+use Slim\Exception\HttpNotFoundException;
+use Doctrine\DBAL\Connection;
+use Doctrine\DBAL\Schema\SqliteSchemaManager;
+use Doctrine\DBAL\Schema\Table;
+use Doctrine\DBAL\Schema\View;
+use App\Utils\DBALConnectionFactory;
+use App\tests\EntityManagerBuilder;
+use App\Entity\Database;
+
+final class TableListActionTest extends TestCase
+{
+    private $action;
+    private $entityManager;
+
+    protected function setUp(): void
+    {
+        $this->entityManager = EntityManagerBuilder::getInstance();
+        $this->action = new \App\Action\TableListAction($this->entityManager, $this->getConnectionFactory());
+    }
+
+    public function testOptionsHttpMethod(): void
+    {
+        $request = $this->getRequest('OPTIONS');
+        $response = ($this->action)($request, new Response(), array());
+        $this->assertSame($response->getHeaderLine('Access-Control-Allow-Methods'), 'GET, OPTIONS');
+    }
+
+    public function testDatabaseIsNotFound(): void
+    {
+        $this->expectException(HttpNotFoundException::class);
+        $this->expectExceptionMessage('Database with id 1 is not found');
+        $request = $this->getRequest('GET');
+        $response = ($this->action)($request, new Response(), array('id' => 1));
+        $this->assertEquals(404, (int) $response->getStatusCode());
+    }
+
+    public function testGetTablesAndViewsList(): void
+    {
+        $this->addADatabase();
+        $request = $this->getRequest('GET');
+        $response = ($this->action)($request, new Response(), array('id' => 1));
+        $this->assertSame(json_encode(array('table1', 'table2', 'view1', 'view2')), (string) $response->getBody());
+    }
+
+    protected function tearDown(): void
+    {
+        $this->entityManager->getConnection()->close();
+    }
+
+    private function getRequest(string $method): ServerRequest
+    {
+        return new ServerRequest($method, '/database/1/table', array(
+            'Content-Type' => 'application/json'
+        ));
+    }
+
+    private function getConnectionFactory(): DBALConnectionFactory
+    {
+        $schemaManager = $this->getMockBuilder(SqliteSchemaManager::class)
+            ->disableOriginalConstructor()
+            ->setMethods(['listTables', 'listViews'])
+            ->getMock();
+
+        $schemaManager->method('listTables')
+            ->will($this->returnValue(array(new Table('table1'), new Table('table2'))));
+
+        $schemaManager->method('listViews')
+            ->will($this->returnValue(array(new View('view1', ''), new View('view2', ''))));
+
+        $connection = $this->getMockBuilder(Connection::class)
+            ->disableOriginalConstructor()
+            ->setMethods(['getSchemaManager'])
+            ->getMock();
+
+        $connection->method('getSchemaManager')
+            ->will($this->returnValue($schemaManager));
+
+        $connectionFactory = $this->getMockBuilder(DBALConnectionFactory::class)
+            ->disableOriginalConstructor()
+            ->setMethods(['create'])
+            ->getMock();
+
+        $connectionFactory->method('create')
+            ->will($this->returnValue($connection));
+
+        return $connectionFactory;
+    }
+
+    private function addADatabase(): Database
+    {
+        $database = new Database();
+        $database->setLabel('Test1');
+        $database->setDbName('test1');
+        $database->setType('pgsql');
+        $database->setHost('db');
+        $database->setPort(5432);
+        $database->setLogin('test');
+        $database->setPassword('test');
+        $this->entityManager->persist($database);
+        $this->entityManager->flush();
+        return $database;
+    }
+}
diff --git a/tests/EntityManagerBuilder.php b/tests/EntityManagerBuilder.php
new file mode 100644
index 0000000..ecdb09b
--- /dev/null
+++ b/tests/EntityManagerBuilder.php
@@ -0,0 +1,35 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Tests;
+
+use Doctrine\ORM\Tools\Setup;
+use Doctrine\ORM\EntityManager;
+use Doctrine\ORM\Tools\SchemaTool;
+use App\Entity\User;
+
+abstract class EntityManagerBuilder
+{
+    public static function getInstance()
+    {
+        $isDevMode = true;
+        $config = Setup::createAnnotationMetadataConfiguration(array(__DIR__ . "/../src/Entity"), $isDevMode);
+        $conn = array(
+            'driver' => 'pdo_sqlite',
+            'memory' => true
+        );
+        $entityManager = EntityManager::create($conn, $config);
+        $schemaTool = new SchemaTool($entityManager);
+        $schemaTool->createSchema($entityManager->getMetadataFactory()->getAllMetadata());
+        return $entityManager;
+    }
+}
diff --git a/tests/Handlers/LogErrorHandlerTest.php b/tests/Handlers/LogErrorHandlerTest.php
new file mode 100644
index 0000000..110287b
--- /dev/null
+++ b/tests/Handlers/LogErrorHandlerTest.php
@@ -0,0 +1,48 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Tests\Handlers;
+
+use PHPUnit\Framework\TestCase;
+use Slim\Interfaces\CallableResolverInterface;
+use Psr\Http\Message\ResponseFactoryInterface;
+use Psr\Log\LoggerInterface;
+use ReflectionClass;
+
+final class LogErrorHandlerTest extends TestCase
+{
+    private $logErrorHandler;
+
+    protected function setUp(): void
+    {
+        $callableResolver = $this->createMock(CallableResolverInterface::class);
+        $responseFactory = $this->createMock(ResponseFactoryInterface::class);
+        $this->logErrorHandler = new \App\Handlers\LogErrorHandler($callableResolver, $responseFactory);
+    }
+
+    protected static function getMethod($name)
+    {
+        $class = new ReflectionClass('App\Handlers\LogErrorHandler');
+        $method = $class->getMethod($name);
+        $method->setAccessible(true);
+        return $method;
+    }
+
+    public function testLogError(): void
+    {
+        $logError = self::getMethod('logError');
+        $logger = $this->prophesize(LoggerInterface::class);
+        $logger->info('Log test')->shouldBeCalled();
+        $this->logErrorHandler->setLogger($logger->reveal());
+        $logError->invokeArgs($this->logErrorHandler, array('Log test'));
+    }
+}
diff --git a/tests/Middleware/ContentTypeJsonMiddlewareTest.php b/tests/Middleware/ContentTypeJsonMiddlewareTest.php
new file mode 100644
index 0000000..82d3e9f
--- /dev/null
+++ b/tests/Middleware/ContentTypeJsonMiddlewareTest.php
@@ -0,0 +1,41 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Tests\Middleware;
+
+use PHPUnit\Framework\TestCase;
+use Nyholm\Psr7\ServerRequest;
+use Nyholm\Psr7\Response;
+use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
+
+final class ContentTypeJsonMiddlewareTest extends TestCase
+{
+    public function testContentTypeJson()
+    {
+        $request = new ServerRequest('GET', '/', array(
+            'Content-Type' => 'application/json'
+        ));
+
+        $requestHandler = $this->getMockBuilder(RequestHandler::class)
+            ->disableOriginalConstructor()
+            ->setMethods(['handle'])
+            ->getMock();
+
+        $requestHandler->method('handle')
+            ->with($this->identicalTo($request))
+            ->will($this->returnValue(new Response()));
+
+        $contentTypeJsonMiddleware = new \App\Middleware\ContentTypeJsonMiddleware();
+        $response = $contentTypeJsonMiddleware->process($request, $requestHandler);
+        $this->assertSame((string) $response->getHeaderLine('Content-Type'), 'application/json');
+    }
+}
diff --git a/tests/admin.yaml b/tests/admin.yaml
deleted file mode 100644
index 9af83a7..0000000
--- a/tests/admin.yaml
+++ /dev/null
@@ -1,15 +0,0 @@
-anis_user:
-    -
-        email: 'user1@anis.fr'
-        password: 'sfpsdfpsdmsdlsdfdf'
-        adminsi: false
-        superuser: false
-        activation_key: 'noioioi'
-        activated: false
-    -
-        email: 'user2@anis.fr'
-        password: '$2y$10$TpRjQWm9KtwP7SDBqJbezemYXe02adGIturk.dVjKo86.I/IL.rNG'
-        adminsi: false
-        superuser: false
-        activation_key: 'fpsdf'
-        activated: true
\ No newline at end of file
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
index 8260685..1e9cf21 100644
--- a/tests/bootstrap.php
+++ b/tests/bootstrap.php
@@ -1,4 +1,15 @@
 <?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
 require __DIR__ . '/../vendor/autoload.php';
-require_once('AbstractActionAdminTestCase.php');
-require_once('AbstractActionMetaTestCase.php');
+require_once(__DIR__ . '/../app/constants.php');
+require_once('EntityManagerBuilder.php');
diff --git a/tests/database.yaml b/tests/database.yaml
deleted file mode 100644
index 0928fa1..0000000
--- a/tests/database.yaml
+++ /dev/null
@@ -1,121 +0,0 @@
-anis_group:
-    -
-        id: 1
-        label: 'Default'
-    -
-        id: 2
-        label: 'Admin'
-database:
-    -
-        id: 1
-        label: 'Cosmology'
-        dbname: 'cosmologydb'
-        type: 'pgsql'
-        host: 'pgserver'
-        port: 5432
-        login: 'consult'
-        password: 'password'
-    -
-        id: 2
-        label: 'ExoDat'
-        dbname: 'exodat_new'
-        type: 'pgsql'
-        host: 'pgserver'
-        port: 5432
-        login: 'consult'
-        password: 'exopassword'
-project:
-    -
-        name: 'vuds'
-        label: 'VUDS'
-        description: 'VUDS project'
-        link: 'http://cesam.lam.fr/vuds'
-        manager: 'Olivier Le Fèvre'
-        database_id: 1
-    -
-        name: 'corot'
-        label: 'CoRoT'
-        description: 'CoRoT project'
-        link: 'http://corot.cnes.fr'
-        manager: 'Magali Deleuil'
-        database_id: 2
-output_family:
-    -
-        id: 1
-        label: 'Parameters by default'
-        display: 10
-    -
-        id: 2
-        label: 'Others catalog ID'
-        display: 20
-criteria_family:
-    -
-        id: 1
-        label: 'Criteria by default'
-        display: 10
-    -
-        id: 2
-        label: 'Photometry'
-        display: 20
-dataset_family:
-    -
-        id: 1
-        label: 'VUDS dataset collection'
-        display: 10
-    -
-        id: 2
-        label: 'VVDS dataset collection'
-        display: 20
-output_category:
-    -
-        id: 1
-        label: 'Default category'
-        display: 10
-        output_family: 1
-    -
-        id: 2
-        label: 'Photometry'
-        display: 20
-        output_family: 1
-dataset:
-    -
-        name: 'corot_targets'
-        table_ref: 'v_corot_targets'
-        label: 'CoRoT Targets'
-        description: 'CoRoT Targets'
-        display: 10
-        count: 100582
-        vo: true
-        data_path: '/tmp/data'
-        selectable_row: false
-        project_name: 'corot'
-        id_dataset_family: 1
-    -
-        name: 'vuds_targets'
-        table_ref: 'v_vuds_targets'
-        label: 'VUDS Targets'
-        description: 'VUDS Targets display'
-        display: 20
-        count: 784512
-        vo: false
-        data_path: '/tmp/data'
-        selectable_row: false
-        project_name: 'vuds'
-        id_dataset_family: 2
-file:
-    -
-        id: 1
-        label: 'My file'
-        file_loc: 'my_file.txt'
-        type: 'txt'
-        display: 10
-        visible: true
-        dataset_name: 'corot_targets'
-    -
-        id: 2
-        label: 'My fits'
-        file_loc: 'file.fits'
-        type: 'fits'
-        display: 20
-        visible: true
-        dataset_name: 'corot_targets'
-- 
GitLab


From 82c73049a8babb01ab053c26cfd3f4a183aaec7f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Agneray?= <francois.agneray@lam.fr>
Date: Mon, 9 Dec 2019 21:58:24 +0100
Subject: [PATCH 02/31] Add the open api documentation (not finished)

---
 anis-server.yaml | 778 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 778 insertions(+)
 create mode 100644 anis-server.yaml

diff --git a/anis-server.yaml b/anis-server.yaml
new file mode 100644
index 0000000..4fc1895
--- /dev/null
+++ b/anis-server.yaml
@@ -0,0 +1,778 @@
+openapi: "3.0.0"
+info:
+  version: 3.1.0
+  title: Anis Server
+  description: 'AstroNomical Information System is a generic web tool that aims to facilitate the provision of data (Astrophysics), accessible from a database, to a community of scientists.'
+  contact:
+    email: francois.agneray@lam.fr
+  license:
+    name: CeCILL 2.1
+    url: http://www.cecill.info/licences/Licence_CeCILL_V2.1-en.html
+servers:
+  - url: http://localhost:8082
+tags:
+  - name: root
+    description: Root path
+  - name: metadata
+    description: Set of actions about metamodel database management
+  - name: search
+    description: Access to the data
+paths:
+  /:
+    get:
+      tags:
+        - root
+      summary: Root path
+      description: Ensures that the service responds
+      operationId: root
+      responses:
+        200:
+          description: The service responds
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Message'
+  /database:
+    get:
+      tags:
+        - metadata
+      summary: List all available databases
+      description: Anis can connect to databases (PostgreSQL, MySQL, SQLite, Oracle ...). This action lists the databases already registered and where anis can connect.
+      operationId: getDatabaseList
+      responses:
+        200:
+          description: Databases list
+          content:
+            application/json:
+              schema:
+                type: array
+                items:
+                  $ref: '#/components/schemas/Database'
+    post:
+      tags:
+        - metadata
+      summary: Add a new database
+      description: Anis can connect to databases (PostgreSQL, MySQL, SQLite, Oracle ...). This action add a new data source.
+      operationId: addDatabase
+      requestBody:
+        description: Database form object that needs to be added to the store
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/DatabaseForm'
+        required: true
+      responses:
+        201:
+          description: Database added
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Database'
+        400:
+          description: Invalid input
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Error'
+  /database/{id}:
+    get:
+      tags:
+        - metadata
+      summary: Find a database by ID
+      description: Returns a single database registered
+      operationId: getDatabase
+      parameters:
+        - name: id
+          in: path
+          description: ID of database to return
+          required: true
+          schema:
+            type: integer
+      responses:
+        200:
+          description: Successful operation
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Database'
+        404:
+          description: Database not found
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Error'
+    put:
+      tags:
+        - metadata
+      summary: Updates a database with form data
+      operationId: editDatabase
+      parameters:
+        - name: id
+          in: path
+          description: ID of database to return
+          required: true
+          schema:
+            type: integer
+      requestBody:
+        description: Database form object that needs to be added to the store
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/DatabaseForm'
+        required: true
+      responses:
+        200:
+          description: Database edited
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Database'
+        400:
+          description: Invalid input
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Error'
+        404:
+          description: Database not found
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Error'
+    delete:
+      tags:
+        - metadata
+      summary: Delete a registered database
+      operationId: deleteDatabase
+      parameters:
+        - name: id
+          in: path
+          description: ID of database to return
+          required: true
+          schema:
+            type: integer
+      responses:
+        200:
+          description: Successful operation
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Message'
+        404:
+          description: Database not found
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Error'
+  /database/{id}/table:
+    get:
+      tags:
+        - metadata
+      summary: Get tables and views list
+      description: Get tables and views listed in the database identified by the ID parameter
+      operationId: tableListDatabase
+      parameters:
+        - name: id
+          in: path
+          description: ID of database to return
+          required: true
+          schema:
+            type: integer
+      responses:
+        200:
+          description: Tables and views list
+          content:
+            application/json:
+              schema:
+                type: array
+                items:
+                  type: string
+        404:
+          description: Database not found
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Error'
+  /project:
+    get:
+      tags:
+        - metadata
+      summary: List all available projects
+      description: All searchable datasets are listed in one or more projects and a project is attached to a database
+      operationId: getProjectList
+      responses:
+        200:
+          description: Project list
+          content:
+            application/json:
+              schema:
+                type: array
+                items:
+                  $ref: '#/components/schemas/Project'
+    post:
+      tags:
+        - metadata
+      summary: Add a new project
+      description: All searchable datasets are listed in one or more projects and a project is attached to a database. This action add a new data project.
+      operationId: addProject
+      requestBody:
+        description: Project form object that needs to be added to the store
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/Project'
+        required: true
+      responses:
+        201:
+          description: Project added
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Project'
+        400:
+          description: Invalid input
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Error'
+  /project/{name}:
+    get:
+      tags:
+        - metadata
+      summary: Find a project by name
+      description: Returns a single project registered
+      operationId: getProject
+      parameters:
+        - name: name
+          in: path
+          description: Name of project to return
+          required: true
+          schema:
+            type: string
+      responses:
+        200:
+          description: Successful operation
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Project'
+        404:
+          description: Project not found
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Error'
+    put:
+      tags:
+        - metadata
+      summary: Updates a project with form data
+      operationId: editProject
+      parameters:
+        - name: name
+          in: path
+          description: Name of project to return
+          required: true
+          schema:
+            type: string
+      requestBody:
+        description: Project form object that needs to be edited to the store
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/Project'
+        required: true
+      responses:
+        200:
+          description: Project edited
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Project'
+        400:
+          description: Invalid input
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Error'
+        404:
+          description: Project not found
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Error'
+    delete:
+      tags:
+        - metadata
+      summary: Delete a registered project
+      operationId: deleteProject
+      parameters:
+        - name: name
+          in: path
+          description: Name of project to return
+          required: true
+          schema:
+            type: string
+      responses:
+        200:
+          description: Successful operation
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Message'
+        404:
+          description: Project not found
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Error'
+  /dataset:
+    get:
+      tags:
+        - metadata
+      summary: List all available datasets
+      description: Get all searchable datasets
+      operationId: getDatasetList
+      responses:
+        200:
+          description: Dataset list
+          content:
+            application/json:
+              schema:
+                type: array
+                items:
+                  $ref: '#/components/schemas/Dataset'
+    post:
+      tags:
+        - metadata
+      summary: Add a new dataset
+      description: Add a new dataset. This action add automatically the associated attributes (columns)
+      operationId: addDataset
+      requestBody:
+        description: Dataset form object that needs to be added to the store
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/Dataset'
+        required: true
+      responses:
+        201:
+          description: Dataset added
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Dataset'
+        400:
+          description: Invalid input
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Error'
+  /dataset/{name}:
+    get:
+      tags:
+        - metadata
+      summary: Find a dataset by name
+      description: Returns a single dataset registered
+      operationId: getDataset
+      parameters:
+        - name: name
+          in: path
+          description: Name of dataset to return
+          required: true
+          schema:
+            type: string
+      responses:
+        200:
+          description: Successful operation
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Dataset'
+        404:
+          description: Dataset not found
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Error'
+    put:
+      tags:
+        - metadata
+      summary: Updates a dataset with form data
+      operationId: editDataset
+      parameters:
+        - name: name
+          in: path
+          description: Name of dataset to return
+          required: true
+          schema:
+            type: string
+      requestBody:
+        description: Dataset form object that needs to be edited to the store
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/Dataset'
+        required: true
+      responses:
+        200:
+          description: Dataset edited
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Dataset'
+        400:
+          description: Invalid input
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Error'
+        404:
+          description: Dataset not found
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Error'
+    delete:
+      tags:
+        - metadata
+      summary: Delete a registered dataset
+      operationId: deleteDataset
+      parameters:
+        - name: name
+          in: path
+          description: Name of dataset to return
+          required: true
+          schema:
+            type: string
+      responses:
+        200:
+          description: Successful operation
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Message'
+        404:
+          description: Dataset not found
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Error'
+  /dataset/{name}/attribute:
+    get:
+      tags:
+        - metadata
+      summary: Retrieve all attributes for a dataset
+      operationId: getAttributes
+      parameters:
+        - name: name
+          in: path
+          description: Name of dataset to return
+          required: true
+          schema:
+            type: string
+      responses:
+        200:
+          description: Successful operation
+          content:
+            application/json:
+              schema:
+                type: array
+                items:
+                  $ref: '#/components/schemas/Attribute'
+        404:
+          description: Dataset not found
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Error'
+
+  /dataset/{name}/attribute/{id}:
+    get:
+      tags:
+        - metadata
+      summary: Find an attribute by dataset name and attribute id
+      description: Returns a single attribute registered
+      operationId: getAttribute
+      parameters:
+        - name: name
+          in: path
+          description: Name of dataset to return
+          required: true
+          schema:
+            type: string
+        - name: id
+          in: path
+          description: Id of attribute to return
+          required: true
+          schema:
+            type: integer
+      responses:
+        200:
+          description: Successful operation
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Attribute'
+        404:
+          description: Dataset or attribute not found
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Error'
+    put:
+      tags:
+        - metadata
+      summary: Updates an attribute with form data
+      operationId: editAttribute
+      parameters:
+        - name: name
+          in: path
+          description: Name of dataset to return
+          required: true
+          schema:
+            type: string
+        - name: id
+          in: path
+          description: Id of attribute to return
+          required: true
+          schema:
+            type: integer
+      requestBody:
+        description: Attribute form object that needs to be edited to the store
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/Attribute'
+        required: true
+      responses:
+        200:
+          description: Attribute edited
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Attribute'
+        400:
+          description: Invalid input
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Error'
+        404:
+          description: Dataset or attribute not found
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Error'
+  /search/{dname}:
+    get:
+      tags:
+        - search
+      summary: Retrieve all data of a search
+      parameters:
+        - name: dname
+          in: path
+          description: Name of dataset about the search
+          required: true
+          schema:
+            type: string
+        - name: c
+          in: query
+          description: Search criteria list separated by a semicolon
+          required: true
+          schema:
+            type: string
+        - name: a
+          in: query
+          description: Search id attributes output list separated by a semicolon
+          required: true
+          schema:
+            type: string
+        - name: o
+          in: query
+          description: Display order list of the search separated by a semicolon. This parameter is mandatory for pagination (p)
+          required: false
+          schema:
+            type: string
+        - name: p
+          in: query
+          description: Pagination settings separated by a colon
+          required: false
+          schema:
+            type: string
+      responses:
+        200:
+          description: Successful operation
+          content:
+            application/json:
+              schema:
+                type: array
+                items:
+                  type: object
+        400:
+          description: Invalid input
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Error'
+        404:
+          description: Dataset not found
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Error'              
+components:
+  schemas:
+    DatabaseForm:
+      type: object
+      properties:
+        label:
+          type: string
+        dbname:
+          type: string
+        dbtype:
+          type: string
+        dbhost:
+          type: string
+        dbport:
+          type: integer
+        dblogin:
+          type: string
+        dbpassword:
+          type: string
+    Database:
+      allOf:
+        - type: object
+          properties:
+            id:
+              type: string
+        - $ref: '#/components/schemas/DatabaseForm'
+    Project:
+      type: object
+      properties:
+        name:
+          type: string
+        label:
+          type: string
+        description:
+          type: string
+        link:
+          type: string
+        manager:
+          type: string
+        id_database:
+          type: integer
+    Dataset:
+      type: object
+      properties:
+        name:
+          type: string
+        table_ref:
+          type: string
+        label:
+          type: string
+        description:
+          type: string
+        display:
+          type: integer
+        count:
+          type: integer
+        vo:
+          type: boolean
+        data_path:
+          type: string
+        project_name:
+          type: string
+        id_dataset_family:
+          type: integer
+    Attribute:
+      type: object
+      properties:
+        id:
+          type: integer
+        name:
+          type: string
+        table_name:
+          type: string
+        label:
+          type: string
+        form_label:
+          type: string
+        description:
+          type: string
+        output_display:
+          type: integer
+        criteria_display:
+          type: integer
+        search_flag:
+          type: string
+        search_type:
+          type: string
+        type:
+          type: string
+        operator:
+          type: string
+        min:
+          type: number
+        max:
+          type: number
+        placeholder_min:
+          type: string
+        placeholder_max:
+          type: string
+        uri_action:
+          type: string
+        renderer:
+          type: string
+        display_detail:
+          type: string
+        selected:
+          type: boolean
+        order_by:
+          type: boolean
+        order_display:
+          type: integer
+        detail:
+          type: boolean
+        renderer_detail:
+          type: string
+        vo_utype:
+          type: string
+        vo_ucd:
+          type: string
+        vo_unit:
+          type: string
+        vo_description:
+          type: string
+        vo_datatype:
+          type: string
+        vo_size:
+          type: integer
+        id_criteria_family:
+          type: integer
+        id_output_family:
+          type: integer
+        id_category:
+          type: integer      
+    Message:
+      type: object
+      properties:
+        message:
+          type: string
+    Error:
+      type: object
+      properties:
+        message:
+          type: string
+        exception:
+          type: object
+          properties:
+            type:
+              type: string
+            code:
+              type: integer
+            message:
+              type: string
+            file:
+              type: string
+            line:
+              type: string
\ No newline at end of file
-- 
GitLab


From 4d2bae166c293b455e9cebbd955590ad1af56fd8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Agneray?= <francois.agneray@lam.fr>
Date: Tue, 10 Dec 2019 15:13:29 +0100
Subject: [PATCH 03/31] Adding Instance into metamodel

---
 Makefile                                 |   2 +-
 app/routes.php                           |   2 +
 src/Action/DatasetListAction.php         |  22 ++++-
 src/Action/InstanceAction.php            |  95 +++++++++++++++++++
 src/Action/InstanceListAction.php        |  81 +++++++++++++++++
 src/Entity/Dataset.php                   |  19 ++++
 src/Entity/Instance.php                  |  82 +++++++++++++++++
 tests/Action/AttributeActionTest.php     |  12 +++
 tests/Action/AttributeListActionTest.php |  12 +++
 tests/Action/DatasetActionTest.php       |  16 +++-
 tests/Action/DatasetListActionTest.php   |  27 ++++++
 tests/Action/InstanceActionTest.php      | 111 +++++++++++++++++++++++
 tests/Action/InstanceListActionTest.php  | 101 +++++++++++++++++++++
 13 files changed, 576 insertions(+), 6 deletions(-)
 create mode 100644 src/Action/InstanceAction.php
 create mode 100644 src/Action/InstanceListAction.php
 create mode 100644 src/Entity/Instance.php
 create mode 100644 tests/Action/InstanceActionTest.php
 create mode 100644 tests/Action/InstanceListActionTest.php

diff --git a/Makefile b/Makefile
index c5f2879..bf6867b 100644
--- a/Makefile
+++ b/Makefile
@@ -65,4 +65,4 @@ dev-meta:
 	@docker-compose exec php sh ./conf-dev/dev-meta.sh
 
 remove-pgdata:
-	@docker volume rm anis-metamodel_pgdata
+	@docker volume rm anis-server_pgdata
diff --git a/app/routes.php b/app/routes.php
index 42c5ec3..b21d644 100644
--- a/app/routes.php
+++ b/app/routes.php
@@ -22,6 +22,8 @@ $app->map([OPTIONS, GET, POST], '/family/{type}', App\Action\FamilyListAction::c
 $app->map([OPTIONS, GET, PUT, DELETE], '/family/{type}/{id}', App\Action\FamilyAction::class);
 $app->map([OPTIONS, GET, POST], '/output-category', App\Action\OutputCategoryListAction::class);
 $app->map([OPTIONS, GET, PUT, DELETE], '/output-category/{id}', App\Action\OutputCategoryAction::class);
+$app->map([OPTIONS, GET, POST], '/instance', App\Action\InstanceListAction::class);
+$app->map([OPTIONS, GET, PUT, DELETE], '/instance/{name}', App\Action\InstanceAction::class);
 $app->map([OPTIONS, GET, POST], '/dataset', App\Action\DatasetListAction::class);
 $app->map([OPTIONS, GET, PUT, DELETE], '/dataset/{name}', App\Action\DatasetAction::class);
 $app->map([OPTIONS, GET], '/dataset/{name}/attribute', App\Action\AttributeListAction::class);
diff --git a/src/Action/DatasetListAction.php b/src/Action/DatasetListAction.php
index fe022a2..dfee85e 100644
--- a/src/Action/DatasetListAction.php
+++ b/src/Action/DatasetListAction.php
@@ -19,6 +19,7 @@ use Doctrine\ORM\EntityManagerInterface;
 use App\Utils\DBALConnectionFactory;
 use App\Entity\Database;
 use App\Entity\Project;
+use App\Entity\Instance;
 use App\Entity\DatasetFamily;
 use App\Entity\Dataset;
 use App\Entity\Attribute;
@@ -75,6 +76,7 @@ final class DatasetListAction extends AbstractAction
                 'data_path',
                 'selectable_row',
                 'project_name',
+                'instance_name',
                 'id_dataset_family'
             );
             foreach ($fields as $a) {
@@ -95,6 +97,15 @@ final class DatasetListAction extends AbstractAction
                 );
             }
 
+            // Instance is mandatory to add a new dataset
+            $instance = $this->em->find('App\Entity\Instance', $parsedBody['instance_name']);
+            if (is_null($instance)) {
+                throw new HttpBadRequestException(
+                    $request,
+                    'Instance with name ' . $parsedBody['instance_name'] . ' is not found'
+                );
+            }
+
             $family = $this->em->find('App\Entity\DatasetFamily', $parsedBody['id_dataset_family']);
             if (is_null($family)) {
                 throw new HttpBadRequestException(
@@ -103,7 +114,7 @@ final class DatasetListAction extends AbstractAction
                 );
             }
 
-            $dataset = $this->postDataset($parsedBody, $project, $family);
+            $dataset = $this->postDataset($parsedBody, $project, $instance, $family);
             $payload = json_encode($dataset);
             $response = $response->withStatus(201);
         }
@@ -121,8 +132,12 @@ final class DatasetListAction extends AbstractAction
      *
      * @return Dataset
      */
-    private function postDataset(array $parsedBody, Project $project, DatasetFamily $family): Dataset
-    {
+    private function postDataset(
+        array $parsedBody,
+        Project $project,
+        Instance $instance,
+        DatasetFamily $family
+    ): Dataset {
         $dataset = new Dataset($parsedBody['name']);
         $dataset->setTableRef($parsedBody['table_ref']);
         $dataset->setLabel($parsedBody['label']);
@@ -133,6 +148,7 @@ final class DatasetListAction extends AbstractAction
         $dataset->setDataPath($parsedBody['data_path']);
         $dataset->setSelectableRow($parsedBody['selectable_row']);
         $dataset->setProject($project);
+        $dataset->setInstance($instance);
         $dataset->setDatasetFamily($family);
 
         $this->em->persist($dataset);
diff --git a/src/Action/InstanceAction.php b/src/Action/InstanceAction.php
new file mode 100644
index 0000000..9299926
--- /dev/null
+++ b/src/Action/InstanceAction.php
@@ -0,0 +1,95 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Action;
+
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Psr\Http\Message\ResponseInterface as Response;
+use Slim\Exception\HttpBadRequestException;
+use Slim\Exception\HttpNotFoundException;
+use App\Entity\Instance;
+
+final class InstanceAction extends AbstractAction
+{
+    /**
+     * `GET` Returns the instance found
+     * `PUT` Full update the instance and returns the new version
+     * `DELETE` Delete the instance found and return a confirmation message
+     *
+     * @param  ServerRequestInterface $request  PSR-7 This object represents the HTTP request
+     * @param  ResponseInterface      $response PSR-7 This object represents the HTTP response
+     * @param  string[]               $args     This table contains information transmitted in the URL (see routes.php)
+     *
+     * @return ResponseInterface
+     */
+    public function __invoke(Request $request, Response $response, array $args): Response
+    {
+        if ($request->getMethod() === OPTIONS) {
+            return $response->withHeader('Access-Control-Allow-Methods', 'GET, PUT, DELETE, OPTIONS');
+        }
+
+        // Search the correct instance with primary key
+        $instance = $this->em->find('App\Entity\Instance', $args['name']);
+
+        // If instance is not found 404
+        if (is_null($instance)) {
+            throw new HttpNotFoundException(
+                $request,
+                'Instance with name ' . $args['name'] . ' is not found'
+            );
+        }
+
+        if ($request->getMethod() === GET) {
+            $payload = json_encode($instance);
+        }
+
+        if ($request->getMethod() === PUT) {
+            $parsedBody = $request->getParsedBody();
+
+            // If mandatories empty fields 400
+            foreach (array('label', 'client_url') as $a) {
+                if ($this->isEmptyField($a, $parsedBody)) {
+                    throw new HttpBadRequestException(
+                        $request,
+                        'Param ' . $a . ' needed to edit the instance'
+                    );
+                }
+            }
+
+            $this->editInstance($instance, $parsedBody);
+            $payload = json_encode($instance);
+        }
+
+        if ($request->getMethod() === DELETE) {
+            $name = $instance->getName();
+            $this->em->remove($instance);
+            $this->em->flush();
+            $payload = json_encode(array('message' => 'Instance with name ' . $name . ' is removed!'));
+        }
+
+        $response->getBody()->write($payload);
+        return $response;
+    }
+
+    /**
+     * Update instance object with setters
+     *
+     * @param Instance $instance   The instance to update
+     * @param array    $parsedBody Contains the new values ​​of the instance sent by the user
+     */
+    private function editInstance(Instance $instance, array $parsedBody): void
+    {
+        $instance->setLabel($parsedBody['label']);
+        $instance->setClientUrl($parsedBody['client_url']);
+        $this->em->flush();
+    }
+}
diff --git a/src/Action/InstanceListAction.php b/src/Action/InstanceListAction.php
new file mode 100644
index 0000000..7cbfeac
--- /dev/null
+++ b/src/Action/InstanceListAction.php
@@ -0,0 +1,81 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Action;
+
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Psr\Http\Message\ResponseInterface as Response;
+use Slim\Exception\HttpBadRequestException;
+use App\Entity\Instance;
+
+final class InstanceListAction extends AbstractAction
+{
+    /**
+     * `GET`  Returns a list of all instances listed in the metamodel
+     * `POST` Add a new instance
+     *
+     * @param  ServerRequestInterface $request  PSR-7 This object represents the HTTP request
+     * @param  ResponseInterface      $response PSR-7 This object represents the HTTP response
+     * @param  string[]               $args     This table contains information transmitted in the URL (see routes.php)
+     *
+     * @return ResponseInterface
+     */
+    public function __invoke(Request $request, Response $response, array $args): Response
+    {
+        if ($request->getMethod() === OPTIONS) {
+            return $response->withHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
+        }
+
+        if ($request->getMethod() === GET) {
+            // Retrieve user with email adress
+            $instances = $this->em->getRepository('App\Entity\Instance')->findAll();
+            $payload = json_encode($instances);
+        }
+
+        if ($request->getMethod() === POST) {
+            $parsedBody = $request->getParsedBody();
+
+            // To work this action needs user information to update
+            foreach (array('name', 'label', 'client_url') as $a) {
+                if ($this->isEmptyField($a, $parsedBody)) {
+                    throw new HttpBadRequestException(
+                        $request,
+                        'Param ' . $a . ' needed to add a new instance'
+                    );
+                }
+            }
+
+            $instance = $this->postInstance($parsedBody);
+            $payload = json_encode($instance);
+            $response = $response->withStatus(201);
+        }
+
+        $response->getBody()->write($payload);
+        return $response;
+    }
+
+    /**
+     * Add a new instance into the metamodel
+     *
+     * @param array $parsedBody Contains the values ​​of the new instance sent by the user
+     */
+    private function postInstance(array $parsedBody): Instance
+    {
+        $instance = new Instance($parsedBody['name'], $parsedBody['label']);
+        $instance->setClientUrl($parsedBody['client_url']);
+
+        $this->em->persist($instance);
+        $this->em->flush();
+
+        return $instance;
+    }
+}
diff --git a/src/Entity/Dataset.php b/src/Entity/Dataset.php
index b2205f8..340fc66 100644
--- a/src/Entity/Dataset.php
+++ b/src/Entity/Dataset.php
@@ -92,6 +92,14 @@ class Dataset implements \JsonSerializable
      */
     protected $project;
 
+    /**
+     * @var Anis\Entity\Instance
+     *
+     * @ManyToOne(targetEntity="Instance")
+     * @JoinColumn(name="instance_name", referencedColumnName="name", nullable=false)
+     */
+    protected $instance;
+
     /**
      * @var Anis\Entity\Project
      *
@@ -222,6 +230,16 @@ class Dataset implements \JsonSerializable
         $this->project = $project;
     }
 
+    public function getInstance(): Instance
+    {
+        return $this->instance;
+    }
+
+    public function setInstance(Instance $instance): void
+    {
+        $this->instance = $instance;
+    }
+
     public function getDatasetFamily()
     {
         return $this->datasetFamily;
@@ -265,6 +283,7 @@ class Dataset implements \JsonSerializable
             'data_path' => $this->getDataPath(),
             'selectable_row' => $this->getSelectableRow(),
             'project_name' => $this->getProject()->getName(),
+            'instance_name' => $this->getInstance()->getName(),
             'id_dataset_family' => $this->getDatasetFamily()->getId()
         ];
     }
diff --git a/src/Entity/Instance.php b/src/Entity/Instance.php
new file mode 100644
index 0000000..24b6db1
--- /dev/null
+++ b/src/Entity/Instance.php
@@ -0,0 +1,82 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Entity;
+
+/**
+ * @Entity
+ * @Table(name="instance")
+ */
+class Instance implements \JsonSerializable
+{
+    /**
+     * @var integer
+     *
+     * @Id
+     * @Column(type="string", nullable=false)
+     */
+    protected $name;
+
+    /**
+     * @var string
+     *
+     * @Column(type="string", nullable=false)
+     */
+    protected $label;
+
+    /**
+     * @var string
+     *
+     * @Column(type="string", nullable=true)
+     */
+    protected $clientUrl;
+
+    public function __construct(string $name, string $label)
+    {
+        $this->name = $name;
+        $this->label = $label;
+    }
+
+    public function getName(): string
+    {
+        return $this->name;
+    }
+
+    public function getLabel(): string
+    {
+        return $this->label;
+    }
+
+    public function setLabel(string $label): void
+    {
+        $this->label = $label;
+    }
+
+    public function getClientUrl(): string
+    {
+        return $this->clientUrl;
+    }
+
+    public function setClientUrl($clientUrl): void
+    {
+        $this->clientUrl = $clientUrl;
+    }
+
+    public function jsonSerialize()
+    {
+        return [
+            'name' => $this->getName(),
+            'label' => $this->getLabel(),
+            'client_url' => $this->getClientUrl()
+        ];
+    }
+}
diff --git a/tests/Action/AttributeActionTest.php b/tests/Action/AttributeActionTest.php
index 4402516..97d423d 100644
--- a/tests/Action/AttributeActionTest.php
+++ b/tests/Action/AttributeActionTest.php
@@ -20,6 +20,7 @@ use Slim\Exception\HttpBadRequestException;
 use App\tests\EntityManagerBuilder;
 use App\Entity\Database;
 use App\Entity\Project;
+use App\Entity\Instance;
 use App\Entity\DatasetFamily;
 use App\Entity\CriteriaFamily;
 use App\Entity\OutputFamily;
@@ -161,6 +162,15 @@ final class AttributeActionTest extends TestCase
         return $project;
     }
 
+    private function addInstance(): Instance
+    {
+        $instance = new Instance('aspic', 'Aspic');
+        $instance->setClientUrl('http://cesam.lam.fr/aspic');
+        $this->entityManager->persist($instance);
+        $this->entityManager->flush();
+        return $instance;
+    }
+
     private function addDatasetFamily(): DatasetFamily
     {
         $family = new DatasetFamily();
@@ -208,6 +218,7 @@ final class AttributeActionTest extends TestCase
     private function addADataset(): Dataset
     {
         $project = $this->addProject();
+        $instance = $this->addInstance();
         $family = $this->addDatasetFamily();
 
         $dataset = new Dataset('obs_cat');
@@ -220,6 +231,7 @@ final class AttributeActionTest extends TestCase
         $dataset->setDataPath('/mnt/obs_cat');
         $dataset->setSelectableRow(false);
         $dataset->setProject($project);
+        $dataset->setInstance($instance);
         $dataset->setDatasetFamily($family);
         $this->entityManager->persist($dataset);
         $this->entityManager->flush();
diff --git a/tests/Action/AttributeListActionTest.php b/tests/Action/AttributeListActionTest.php
index f49816c..295327c 100644
--- a/tests/Action/AttributeListActionTest.php
+++ b/tests/Action/AttributeListActionTest.php
@@ -20,6 +20,7 @@ use Slim\Exception\HttpNotFoundException;
 use App\tests\EntityManagerBuilder;
 use App\Entity\Database;
 use App\Entity\Project;
+use App\Entity\Instance;
 use App\Entity\DatasetFamily;
 use App\Entity\Dataset;
 use App\Entity\Attribute;
@@ -97,6 +98,15 @@ final class AttributeListActionTest extends TestCase
         return $project;
     }
 
+    private function addInstance(): Instance
+    {
+        $instance = new Instance('aspic', 'Aspic');
+        $instance->setClientUrl('http://cesam.lam.fr/aspic');
+        $this->entityManager->persist($instance);
+        $this->entityManager->flush();
+        return $instance;
+    }
+
     private function addDatasetFamily(): DatasetFamily
     {
         $family = new DatasetFamily();
@@ -110,6 +120,7 @@ final class AttributeListActionTest extends TestCase
     private function addADataset(): Dataset
     {
         $project = $this->addProject();
+        $instance = $this->addInstance();
         $family = $this->addDatasetFamily();
 
         $dataset = new Dataset('obs_cat');
@@ -122,6 +133,7 @@ final class AttributeListActionTest extends TestCase
         $dataset->setDataPath('/mnt/obs_cat');
         $dataset->setSelectableRow(false);
         $dataset->setProject($project);
+        $dataset->setInstance($instance);
         $dataset->setDatasetFamily($family);
         $this->entityManager->persist($dataset);
         $this->entityManager->flush();
diff --git a/tests/Action/DatasetActionTest.php b/tests/Action/DatasetActionTest.php
index d3f4f54..15e3ec3 100644
--- a/tests/Action/DatasetActionTest.php
+++ b/tests/Action/DatasetActionTest.php
@@ -20,6 +20,7 @@ use Slim\Exception\HttpBadRequestException;
 use App\tests\EntityManagerBuilder;
 use App\Entity\Database;
 use App\Entity\Project;
+use App\Entity\Instance;
 use App\Entity\DatasetFamily;
 use App\Entity\Dataset;
 
@@ -92,7 +93,7 @@ final class DatasetActionTest extends TestCase
                 array_merge(
                     ['name' => 'obs_cat', 'table_ref' => 'v_obs_cat'],
                     $fields,
-                    ['project_name' => 'anis_project', 'id_dataset_family' => 1]
+                    ['project_name' => 'anis_project', 'instance_name' => 'aspic', 'id_dataset_family' => 1]
                 ),
                 JSON_UNESCAPED_SLASHES
             ),
@@ -101,7 +102,7 @@ final class DatasetActionTest extends TestCase
         $this->assertEquals(200, (int) $response->getStatusCode());
     }
 
-    public function testDeleteADatabase(): void
+    public function testDeleteADataset(): void
     {
         $this->addADataset();
         $request = $this->getRequest('DELETE');
@@ -162,6 +163,15 @@ final class DatasetActionTest extends TestCase
         return $project;
     }
 
+    private function addInstance(): Instance
+    {
+        $instance = new Instance('aspic', 'Aspic');
+        $instance->setClientUrl('http://cesam.lam.fr/aspic');
+        $this->entityManager->persist($instance);
+        $this->entityManager->flush();
+        return $instance;
+    }
+
     private function addDatasetFamily(): DatasetFamily
     {
         $family = new DatasetFamily();
@@ -176,6 +186,7 @@ final class DatasetActionTest extends TestCase
     private function addADataset(): Dataset
     {
         $project = $this->addProject();
+        $instance = $this->addInstance();
         $family = $this->addDatasetFamily();
 
         $dataset = new Dataset('obs_cat');
@@ -188,6 +199,7 @@ final class DatasetActionTest extends TestCase
         $dataset->setDataPath('/mnt/obs_cat');
         $dataset->setSelectableRow(false);
         $dataset->setProject($project);
+        $dataset->setInstance($instance);
         $dataset->setDatasetFamily($family);
         $this->entityManager->persist($dataset);
         $this->entityManager->flush();
diff --git a/tests/Action/DatasetListActionTest.php b/tests/Action/DatasetListActionTest.php
index dfcea82..2574981 100644
--- a/tests/Action/DatasetListActionTest.php
+++ b/tests/Action/DatasetListActionTest.php
@@ -24,6 +24,7 @@ use Doctrine\DBAL\Types\Type;
 use App\tests\EntityManagerBuilder;
 use App\Entity\Database;
 use App\Entity\Project;
+use App\Entity\Instance;
 use App\Entity\DatasetFamily;
 use App\Entity\Dataset;
 
@@ -78,6 +79,7 @@ final class DatasetListActionTest extends TestCase
     public function testAddANewDatasetFamilyNotFound(): void
     {
         $this->addProject();
+        $this->addInstance();
         $this->expectException(HttpBadRequestException::class);
         $this->expectExceptionMessage('Dataset family with id 1 is not found');
         $fields = $this->getNewDatasetFields();
@@ -86,9 +88,21 @@ final class DatasetListActionTest extends TestCase
         $this->assertEquals(400, (int) $response->getStatusCode());
     }
 
+    public function testAddANewDatasetInstanceNotFound(): void
+    {
+        $this->addProject();
+        $this->expectException(HttpBadRequestException::class);
+        $this->expectExceptionMessage('Instance with name aspic is not found');
+        $fields = $this->getNewDatasetFields();
+        $request = $this->getRequest('POST')->withParsedBody($fields);
+        $response = ($this->action)($request, new Response(), array());
+        $this->assertEquals(400, (int) $response->getStatusCode());
+    }
+
     public function testAddANewDataset(): void
     {
         $this->addProject();
+        $this->addInstance();
         $this->addDatasetFamily();
         $fields = $this->getNewDatasetFields();
         $request = $this->getRequest('POST')->withParsedBody($fields);
@@ -125,6 +139,7 @@ final class DatasetListActionTest extends TestCase
             'data_path' => '/mnt/dataset1',
             'selectable_row' => false,
             'project_name' => 'anis_project',
+            'instance_name' => 'aspic',
             'id_dataset_family' => 1
         );
     }
@@ -153,6 +168,15 @@ final class DatasetListActionTest extends TestCase
         return $project;
     }
 
+    private function addInstance(): Instance
+    {
+        $instance = new Instance('aspic', 'Aspic');
+        $instance->setClientUrl('http://cesam.lam.fr/aspic');
+        $this->entityManager->persist($instance);
+        $this->entityManager->flush();
+        return $instance;
+    }
+
     private function addDatasetFamily(): DatasetFamily
     {
         $family = new DatasetFamily();
@@ -167,6 +191,7 @@ final class DatasetListActionTest extends TestCase
     private function addDatasets(): array
     {
         $project = $this->addProject();
+        $instance = $this->addInstance();
         $family = $this->addDatasetFamily();
 
         $dataset1 = new Dataset('dataset1');
@@ -179,6 +204,7 @@ final class DatasetListActionTest extends TestCase
         $dataset1->setDataPath('/mnt/dataset1');
         $dataset1->setSelectableRow(false);
         $dataset1->setProject($project);
+        $dataset1->setInstance($instance);
         $dataset1->setDatasetFamily($family);
         $this->entityManager->persist($dataset1);
 
@@ -192,6 +218,7 @@ final class DatasetListActionTest extends TestCase
         $dataset2->setDataPath('/mnt/dataset2');
         $dataset2->setSelectableRow(false);
         $dataset2->setProject($project);
+        $dataset2->setInstance($instance);
         $dataset2->setDatasetFamily($family);
         $this->entityManager->persist($dataset2);
 
diff --git a/tests/Action/InstanceActionTest.php b/tests/Action/InstanceActionTest.php
new file mode 100644
index 0000000..743bc20
--- /dev/null
+++ b/tests/Action/InstanceActionTest.php
@@ -0,0 +1,111 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Tests\Action;
+
+use PHPUnit\Framework\TestCase;
+use Nyholm\Psr7\ServerRequest;
+use Nyholm\Psr7\Response;
+use Slim\Exception\HttpNotFoundException;
+use Slim\Exception\HttpBadRequestException;
+use App\tests\EntityManagerBuilder;
+use App\Entity\Instance;
+
+final class InstanceActionTest extends TestCase
+{
+    private $action;
+    private $entityManager;
+
+    protected function setUp(): void
+    {
+        $this->entityManager = EntityManagerBuilder::getInstance();
+        $this->action = new \App\Action\InstanceAction($this->entityManager);
+    }
+
+    public function testOptionsHttpMethod(): void
+    {
+        $request = $this->getRequest('OPTIONS');
+        $response = ($this->action)($request, new Response(), array());
+        $this->assertSame($response->getHeaderLine('Access-Control-Allow-Methods'), 'GET, PUT, DELETE, OPTIONS');
+    }
+
+    public function testInstanceIsNotFound(): void
+    {
+        $this->expectException(HttpNotFoundException::class);
+        $this->expectExceptionMessage('Instance with name aspic is not found');
+        $request = $this->getRequest('GET');
+        $response = ($this->action)($request, new Response(), array('name' => 'aspic'));
+        $this->assertEquals(404, (int) $response->getStatusCode());
+    }
+
+    public function testGetAnInstanceByName(): void
+    {
+        $instance = $this->addAnInstance();
+        $request = $this->getRequest('GET');
+        $response = ($this->action)($request, new Response(), array('name' => 'aspic'));
+        $this->assertSame(json_encode($instance), (string) $response->getBody());
+    }
+
+    public function testEditAnInstanceEmptyLabelField(): void
+    {
+        $this->addAnInstance();
+        $this->expectException(HttpBadRequestException::class);
+        $this->expectExceptionMessage('Param label needed to edit the instance');
+        $request = $this->getRequest('PUT')->withParsedBody(array());
+        $response = ($this->action)($request, new Response(), array('name' => 'aspic'));
+        $this->assertEquals(400, (int) $response->getStatusCode());
+    }
+
+    public function testEditAnInstance(): void
+    {
+        $fields = array(
+            'label' => 'AspiC',
+            'client_url' => 'http://aspic.lam.fr'
+        );
+        $this->addAnInstance();
+        $request = $this->getRequest('PUT')->withParsedBody($fields);
+        $response = ($this->action)($request, new Response(), array('name' => 'aspic'));
+        $this->assertSame(json_encode(array_merge(['name' => 'aspic'], $fields)), (string) $response->getBody());
+    }
+
+    public function testDeleteAnInstance(): void
+    {
+        $this->addAnInstance();
+        $request = $this->getRequest('DELETE');
+        $response = ($this->action)($request, new Response(), array('name' => 'aspic'));
+        $this->assertSame(
+            json_encode(array('message' => 'Instance with name aspic is removed!')),
+            (string) $response->getBody()
+        );
+    }
+
+    protected function tearDown(): void
+    {
+        $this->entityManager->getConnection()->close();
+    }
+
+    private function getRequest(string $method): ServerRequest
+    {
+        return new ServerRequest($method, '/instance/aspic', array(
+            'Content-Type' => 'application/json'
+        ));
+    }
+
+    private function addAnInstance(): Instance
+    {
+        $instance = new Instance('aspic', 'Aspic');
+        $instance->setClientUrl('http://cesam.lam.fr/aspic');
+        $this->entityManager->persist($instance);
+        $this->entityManager->flush();
+        return $instance;
+    }
+}
diff --git a/tests/Action/InstanceListActionTest.php b/tests/Action/InstanceListActionTest.php
new file mode 100644
index 0000000..9977f2c
--- /dev/null
+++ b/tests/Action/InstanceListActionTest.php
@@ -0,0 +1,101 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Tests\Action;
+
+use PHPUnit\Framework\TestCase;
+use Nyholm\Psr7\ServerRequest;
+use Nyholm\Psr7\Response;
+use Slim\Exception\HttpBadRequestException;
+use App\tests\EntityManagerBuilder;
+use App\Entity\Instance;
+
+final class InstanceListActionTest extends TestCase
+{
+    private $action;
+    private $entityManager;
+
+    protected function setUp(): void
+    {
+        $this->entityManager = EntityManagerBuilder::getInstance();
+        $this->action = new \App\Action\InstanceListAction($this->entityManager);
+    }
+
+    public function testOptionsHttpMethod(): void
+    {
+        $request = $this->getRequest('OPTIONS');
+        $response = ($this->action)($request, new Response(), array());
+        $this->assertSame($response->getHeaderLine('Access-Control-Allow-Methods'), 'GET, POST, OPTIONS');
+    }
+
+    public function testGetAllInstances(): void
+    {
+        $instances = $this->addInstances();
+        $request = $this->getRequest('GET');
+        $response = ($this->action)($request, new Response(), array());
+        $this->assertSame(
+            json_encode($instances),
+            (string) $response->getBody()
+        );
+    }
+
+    public function testAddANewInstanceEmptyNameField(): void
+    {
+        $this->expectException(HttpBadRequestException::class);
+        $this->expectExceptionMessage('Param name needed to add a new instance');
+        $request = $this->getRequest('POST')->withParsedBody(array());
+        $response = ($this->action)($request, new Response(), array());
+        $this->assertEquals(400, (int) $response->getStatusCode());
+    }
+
+    public function testAddANewInstance(): void
+    {
+        $fields = array(
+            'name' => 'aspic',
+            'label' => 'Aspic',
+            'client_url' => 'http://cesam.lam.fr/aspic'
+        );
+        $request = $this->getRequest('POST')->withParsedBody($fields);
+        $response = ($this->action)($request, new Response(), array());
+        $this->assertSame(
+            json_encode($fields),
+            (string) $response->getBody()
+        );
+        $this->assertEquals(201, (int) $response->getStatusCode());
+    }
+
+    protected function tearDown(): void
+    {
+        $this->entityManager->getConnection()->close();
+    }
+
+    private function getRequest(string $method): ServerRequest
+    {
+        return new ServerRequest($method, '/instance', array(
+            'Content-Type' => 'application/json'
+        ));
+    }
+
+    private function addInstances(): array
+    {
+        $instance = new Instance('aspic', 'Aspic');
+        $instance->setClientUrl('http://cesam.lam.fr/aspic');
+        $this->entityManager->persist($instance);
+
+        $instance2 = new  Instance('exodat', 'ExoDat');
+        $instance2->setClientUrl('http://cesam.lam.fr/exodat');
+        $this->entityManager->persist($instance2);
+
+        $this->entityManager->flush();
+        return array($instance, $instance2);
+    }
+}
-- 
GitLab


From bfefedb4a9a6837385b7bb5f1c2986a86cdadcd7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Agneray?= <francois.agneray@lam.fr>
Date: Tue, 10 Dec 2019 15:39:13 +0100
Subject: [PATCH 04/31] Modifying dataset route to adapt instance feature

---
 app/routes.php                         |  2 +-
 src/Action/DatasetListAction.php       | 23 +++++++++--------
 tests/Action/DatasetListActionTest.php | 35 +++++++++++++-------------
 3 files changed, 31 insertions(+), 29 deletions(-)

diff --git a/app/routes.php b/app/routes.php
index b21d644..6871f36 100644
--- a/app/routes.php
+++ b/app/routes.php
@@ -24,7 +24,7 @@ $app->map([OPTIONS, GET, POST], '/output-category', App\Action\OutputCategoryLis
 $app->map([OPTIONS, GET, PUT, DELETE], '/output-category/{id}', App\Action\OutputCategoryAction::class);
 $app->map([OPTIONS, GET, POST], '/instance', App\Action\InstanceListAction::class);
 $app->map([OPTIONS, GET, PUT, DELETE], '/instance/{name}', App\Action\InstanceAction::class);
-$app->map([OPTIONS, GET, POST], '/dataset', App\Action\DatasetListAction::class);
+$app->map([OPTIONS, GET, POST], '/instance/{name}/dataset', App\Action\DatasetListAction::class);
 $app->map([OPTIONS, GET, PUT, DELETE], '/dataset/{name}', App\Action\DatasetAction::class);
 $app->map([OPTIONS, GET], '/dataset/{name}/attribute', App\Action\AttributeListAction::class);
 $app->map([OPTIONS, GET, PUT], '/dataset/{name}/attribute/{id}', App\Action\AttributeAction::class);
diff --git a/src/Action/DatasetListAction.php b/src/Action/DatasetListAction.php
index dfee85e..5f5c7a6 100644
--- a/src/Action/DatasetListAction.php
+++ b/src/Action/DatasetListAction.php
@@ -15,6 +15,7 @@ namespace App\Action;
 use Psr\Http\Message\ServerRequestInterface as Request;
 use Psr\Http\Message\ResponseInterface as Response;
 use Slim\Exception\HttpBadRequestException;
+use Slim\Exception\HttpNotFoundException;
 use Doctrine\ORM\EntityManagerInterface;
 use App\Utils\DBALConnectionFactory;
 use App\Entity\Database;
@@ -56,8 +57,18 @@ final class DatasetListAction extends AbstractAction
             return $response->withHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
         }
 
+        $instance = $this->em->find('App\Entity\Instance', $args['name']);
+
+        // Returns HTTP 404 if the instance is not found
+        if (is_null($instance)) {
+            throw new HttpNotFoundException(
+                $request,
+                'Instance with name ' . $args['name'] . ' is not found'
+            );
+        }
+
         if ($request->getMethod() === GET) {
-            $datasets = $this->em->getRepository('App\Entity\Dataset')->findAll();
+            $datasets = $this->em->getRepository('App\Entity\Dataset')->findByInstance($instance);
             $payload = json_encode($datasets);
         }
 
@@ -76,7 +87,6 @@ final class DatasetListAction extends AbstractAction
                 'data_path',
                 'selectable_row',
                 'project_name',
-                'instance_name',
                 'id_dataset_family'
             );
             foreach ($fields as $a) {
@@ -97,15 +107,6 @@ final class DatasetListAction extends AbstractAction
                 );
             }
 
-            // Instance is mandatory to add a new dataset
-            $instance = $this->em->find('App\Entity\Instance', $parsedBody['instance_name']);
-            if (is_null($instance)) {
-                throw new HttpBadRequestException(
-                    $request,
-                    'Instance with name ' . $parsedBody['instance_name'] . ' is not found'
-                );
-            }
-
             $family = $this->em->find('App\Entity\DatasetFamily', $parsedBody['id_dataset_family']);
             if (is_null($family)) {
                 throw new HttpBadRequestException(
diff --git a/tests/Action/DatasetListActionTest.php b/tests/Action/DatasetListActionTest.php
index 2574981..9be0dce 100644
--- a/tests/Action/DatasetListActionTest.php
+++ b/tests/Action/DatasetListActionTest.php
@@ -16,6 +16,7 @@ use PHPUnit\Framework\TestCase;
 use Nyholm\Psr7\ServerRequest;
 use Nyholm\Psr7\Response;
 use Slim\Exception\HttpBadRequestException;
+use Slim\Exception\HttpNotFoundException;
 use Doctrine\DBAL\Connection;
 use Doctrine\DBAL\Schema\SqliteSchemaManager;
 use Doctrine\DBAL\Schema\Column;
@@ -46,11 +47,20 @@ final class DatasetListActionTest extends TestCase
         $this->assertSame($response->getHeaderLine('Access-Control-Allow-Methods'), 'GET, POST, OPTIONS');
     }
 
+    public function testInstanceNotFound(): void
+    {
+        $this->expectException(HttpNotFoundException::class);
+        $this->expectExceptionMessage('Instance with name aspic is not found');
+        $request = $this->getRequest('GET');
+        $response = ($this->action)($request, new Response(), array('name' => 'aspic'));
+        $this->assertEquals(404, (int) $response->getStatusCode());
+    }
+
     public function testGetAllDatasets(): void
     {
         $datasets = $this->addDatasets();
         $request = $this->getRequest('GET');
-        $response = ($this->action)($request, new Response(), array());
+        $response = ($this->action)($request, new Response(), array('name' => 'aspic'));
         $this->assertSame(
             json_encode($datasets),
             (string) $response->getBody()
@@ -59,20 +69,22 @@ final class DatasetListActionTest extends TestCase
 
     public function testAddANewDatasetEmptyNameField(): void
     {
+        $this->addInstance();
         $this->expectException(HttpBadRequestException::class);
         $this->expectExceptionMessage('Param name needed to add a new dataset');
         $request = $this->getRequest('POST')->withParsedBody(array());
-        $response = ($this->action)($request, new Response(), array());
+        $response = ($this->action)($request, new Response(), array('name' => 'aspic'));
         $this->assertEquals(400, (int) $response->getStatusCode());
     }
 
     public function testAddANewDatasetProjectNotFound(): void
     {
+        $this->addInstance();
         $this->expectException(HttpBadRequestException::class);
         $this->expectExceptionMessage('Project with name anis_project is not found');
         $fields = $this->getNewDatasetFields();
         $request = $this->getRequest('POST')->withParsedBody($fields);
-        $response = ($this->action)($request, new Response(), array());
+        $response = ($this->action)($request, new Response(), array('name' => 'aspic'));
         $this->assertEquals(400, (int) $response->getStatusCode());
     }
 
@@ -84,18 +96,7 @@ final class DatasetListActionTest extends TestCase
         $this->expectExceptionMessage('Dataset family with id 1 is not found');
         $fields = $this->getNewDatasetFields();
         $request = $this->getRequest('POST')->withParsedBody($fields);
-        $response = ($this->action)($request, new Response(), array());
-        $this->assertEquals(400, (int) $response->getStatusCode());
-    }
-
-    public function testAddANewDatasetInstanceNotFound(): void
-    {
-        $this->addProject();
-        $this->expectException(HttpBadRequestException::class);
-        $this->expectExceptionMessage('Instance with name aspic is not found');
-        $fields = $this->getNewDatasetFields();
-        $request = $this->getRequest('POST')->withParsedBody($fields);
-        $response = ($this->action)($request, new Response(), array());
+        $response = ($this->action)($request, new Response(), array('name' => 'aspic'));
         $this->assertEquals(400, (int) $response->getStatusCode());
     }
 
@@ -106,7 +107,7 @@ final class DatasetListActionTest extends TestCase
         $this->addDatasetFamily();
         $fields = $this->getNewDatasetFields();
         $request = $this->getRequest('POST')->withParsedBody($fields);
-        $response = ($this->action)($request, new Response(), array());
+        $response = ($this->action)($request, new Response(), array('name' => 'aspic'));
         $this->assertSame(
             json_encode($fields),
             (string) $response->getBody()
@@ -121,7 +122,7 @@ final class DatasetListActionTest extends TestCase
 
     private function getRequest(string $method): ServerRequest
     {
-        return new ServerRequest($method, '/dataset', array(
+        return new ServerRequest($method, '/instance/aspic/dataset', array(
             'Content-Type' => 'application/json'
         ));
     }
-- 
GitLab


From b80f36c9edcec5318702ad691786dc5fc6e3de93 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Agneray?= <francois.agneray@lam.fr>
Date: Tue, 10 Dec 2019 21:27:10 +0100
Subject: [PATCH 05/31] Tests + code coverage

---
 src/Entity/Attribute.php                   |  11 +-
 src/Entity/CriteriaFamily.php              |  12 --
 src/Entity/Dataset.php                     |  45 +------
 src/Entity/DatasetFamily.php               |  19 +--
 src/Entity/DatasetPrivileges.php           |  21 +--
 src/Entity/File.php                        | 149 ---------------------
 src/Entity/Group.php                       |  12 --
 src/Entity/OutputCategory.php              |  14 +-
 src/Entity/OutputFamily.php                |  12 --
 src/Entity/Project.php                     |  12 --
 src/Entity/User.php                        |  90 +------------
 src/Middleware/AuthorizationMiddleware.php | 137 -------------------
 tests/Action/FamilyActionTest.php          |   2 +-
 tests/Action/FamilyListActionTest.php      |  34 ++++-
 14 files changed, 43 insertions(+), 527 deletions(-)
 delete mode 100644 src/Entity/File.php
 delete mode 100644 src/Middleware/AuthorizationMiddleware.php

diff --git a/src/Entity/Attribute.php b/src/Entity/Attribute.php
index 30ecbea..210379b 100644
--- a/src/Entity/Attribute.php
+++ b/src/Entity/Attribute.php
@@ -32,7 +32,7 @@ class Attribute implements \JsonSerializable
      * @var App\Entity\Dataset
      *
      * @Id
-     * @ManyToOne(targetEntity="Dataset", inversedBy="attributes")
+     * @ManyToOne(targetEntity="Dataset")
      * @JoinColumn(name="dataset_name", referencedColumnName="name", nullable=false)
      */
     protected $dataset;
@@ -250,7 +250,7 @@ class Attribute implements \JsonSerializable
     /**
      * @var App\Entity\CriteriaFamily
      *
-     * @ManyToOne(targetEntity="CriteriaFamily", inversedBy="attributes")
+     * @ManyToOne(targetEntity="CriteriaFamily")
      * @JoinColumn(name="criteria_family", referencedColumnName="id", nullable=true)
      */
     protected $criteriaFamily;
@@ -258,7 +258,7 @@ class Attribute implements \JsonSerializable
     /**
      * @var App\Entity\OutputCategory
      *
-     * @ManyToOne(targetEntity="OutputCategory", inversedBy="attributes")
+     * @ManyToOne(targetEntity="OutputCategory")
      * @JoinColumn(name="output_category", referencedColumnName="id", nullable=true)
      */
     protected $outputCategory;
@@ -574,11 +574,6 @@ class Attribute implements \JsonSerializable
         $this->options = $options;
     }
 
-    public function getDataset()
-    {
-        return $this->dataset;
-    }
-
     public function getCriteriaFamily()
     {
         return $this->criteriaFamily;
diff --git a/src/Entity/CriteriaFamily.php b/src/Entity/CriteriaFamily.php
index 1646654..d5e43e4 100644
--- a/src/Entity/CriteriaFamily.php
+++ b/src/Entity/CriteriaFamily.php
@@ -41,13 +41,6 @@ class CriteriaFamily implements \JsonSerializable
      */
     protected $display;
 
-    /**
-     * @var Anis\Entity\Attribute[]
-     *
-     * @OneToMany(targetEntity="Attribute", mappedBy="criteriaFamily")
-     */
-    protected $attributes;
-
     public function getId()
     {
         return $this->id;
@@ -73,11 +66,6 @@ class CriteriaFamily implements \JsonSerializable
         $this->display = $display;
     }
 
-    public function getAttributes()
-    {
-        return $this->attributes;
-    }
-
     public function jsonSerialize()
     {
         return [
diff --git a/src/Entity/Dataset.php b/src/Entity/Dataset.php
index 340fc66..1740f0d 100644
--- a/src/Entity/Dataset.php
+++ b/src/Entity/Dataset.php
@@ -87,7 +87,7 @@ class Dataset implements \JsonSerializable
     /**
      * @var Anis\Entity\Project
      *
-     * @ManyToOne(targetEntity="Project", inversedBy="datasets")
+     * @ManyToOne(targetEntity="Project")
      * @JoinColumn(name="project_name", referencedColumnName="name", nullable=false)
      */
     protected $project;
@@ -103,31 +103,10 @@ class Dataset implements \JsonSerializable
     /**
      * @var Anis\Entity\Project
      *
-     * @ManyToOne(targetEntity="DatasetFamily", inversedBy="datasets")
+     * @ManyToOne(targetEntity="DatasetFamily")
      * @JoinColumn(name="id_dataset_family", referencedColumnName="id", nullable=true)
      */
     protected $datasetFamily;
-    
-    /**
-     * @var Anis\Entity\Attribute[]
-     *
-     * @OneToMany(targetEntity="Attribute", mappedBy="dataset", cascade={"remove"})
-     */
-    protected $attributes;
-    
-    /**
-     * @var Anis\Entity\File[]
-     *
-     * @OneToMany(targetEntity="File", mappedBy="dataset", cascade={"remove"})
-     */
-    protected $files;
-    
-    /**
-     * @var Anis\Entity\DatasetPrivileges
-     *
-     * @OneToMany(targetEntity="DatasetPrivileges", mappedBy="dataset")
-     */
-    protected $datasetPrivileges;
 
     public function __construct($name)
     {
@@ -249,26 +228,6 @@ class Dataset implements \JsonSerializable
     {
         $this->datasetFamily = $datasetFamily;
     }
-    
-    public function getAttributes()
-    {
-        return $this->attributes;
-    }
-    
-    public function getFiles()
-    {
-        return $this->files;
-    }
-    
-    public function getJoins()
-    {
-        return $this->joins;
-    }
-    
-    public function getDatasetPrivileges()
-    {
-        return $this->datasetPrivileges;
-    }
 
     public function jsonSerialize()
     {
diff --git a/src/Entity/DatasetFamily.php b/src/Entity/DatasetFamily.php
index 1137e75..f679255 100644
--- a/src/Entity/DatasetFamily.php
+++ b/src/Entity/DatasetFamily.php
@@ -43,13 +43,6 @@ class DatasetFamily implements \JsonSerializable
      */
     protected $display;
 
-    /**
-     * @var Anis\Entity\Dataset[]
-     *
-     * @OneToMany(targetEntity="Dataset", mappedBy="datasetFamily")
-     */
-    protected $datasets;
-
     public function __construct()
     {
         $this->datasets = new ArrayCollection();
@@ -80,23 +73,13 @@ class DatasetFamily implements \JsonSerializable
         $this->display = (int) $display;
     }
 
-    public function getDatasets()
-    {
-        return $this->datasets;
-    }
-
     public function jsonSerialize()
     {
-        $datasets = array();
-        foreach ($this->getDatasets() as $dataset) {
-            $datasets[] = $dataset->getName();
-        }
         return [
             'id' => $this->getId(),
             'label' => $this->getLabel(),
             'display' => $this->getDisplay(),
-            'type' => 'dataset',
-            'datasets' => $datasets
+            'type' => 'dataset'
         ];
     }
 }
diff --git a/src/Entity/DatasetPrivileges.php b/src/Entity/DatasetPrivileges.php
index d7c4361..8fbc8d0 100644
--- a/src/Entity/DatasetPrivileges.php
+++ b/src/Entity/DatasetPrivileges.php
@@ -22,7 +22,7 @@ class DatasetPrivileges
      * @var Anis\Entity\Dataset
      *
      * @Id
-     * @ManyToOne(targetEntity="Dataset", inversedBy="datasetPrivileges")
+     * @ManyToOne(targetEntity="Dataset")
      * @JoinColumn(name="dataset_name", referencedColumnName="name", nullable=false)
      */
     protected $dataset;
@@ -36,17 +36,10 @@ class DatasetPrivileges
      */
     protected $group;
 
-    /**
-     * @var bool
-     *
-     * @Column(type="boolean", nullable=false)
-     */
-    protected $visible;
-
     public function __construct(Dataset $dataset, Group $group)
     {
         $this->dataset = $dataset;
-        $this->group   = $group;
+        $this->group = $group;
     }
 
     public function getDataset()
@@ -58,14 +51,4 @@ class DatasetPrivileges
     {
         return $this->group;
     }
-
-    public function getVisible()
-    {
-        return $this->visible;
-    }
-
-    public function setVisible($visible)
-    {
-        $this->visible = (bool) $visible;
-    }
 }
diff --git a/src/Entity/File.php b/src/Entity/File.php
deleted file mode 100644
index 1065603..0000000
--- a/src/Entity/File.php
+++ /dev/null
@@ -1,149 +0,0 @@
-<?php
-
-/*
- * This file is part of Anis Server.
- *
- * (c) Laboratoire d'Astrophysique de Marseille / CNRS
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-declare(strict_types=1);
-
-namespace App\Entity;
-
-/**
-* @Entity
-* @Table(name="file")
-*/
-class File implements \JsonSerializable
-{
-    /**
-     * @var integer
-     *
-     * @Id
-     * @Column(type="integer", nullable=false)
-     * @GeneratedValue
-     */
-    protected $id;
-
-    /**
-     * @var string
-     *
-     * @Column(type="string", nullable=false)
-     */
-    protected $label;
-
-    /**
-     * @var string
-     *
-     * @Column(type="string", name="file_loc", nullable=true)
-     */
-    protected $fileLoc;
-
-    /**
-     * @var string
-     *
-     * @Column(type="string", nullable=false)
-     */
-    protected $type;
-
-    /**
-     * @var integer
-     *
-     * @Column(type="integer", nullable=false)
-     */
-    protected $display;
-
-    /**
-     * @var bool
-     *
-     *  @Column(type="boolean", nullable=false)
-     */
-    protected $visible;
-
-    /**
-     * @var Anis\Entity\Dataset
-     *
-     * @ManyToOne(targetEntity="Dataset", inversedBy="files")
-     * @JoinColumn(name="dataset_name", referencedColumnName="name", nullable=false)
-     */
-    protected $dataset;
-
-    public function __construct(Dataset $dataset)
-    {
-        $this->dataset = $dataset;
-    }
-
-    public function getId()
-    {
-        return $this->id;
-    }
-
-    public function getLabel()
-    {
-        return $this->label;
-    }
-
-    public function setLabel($label)
-    {
-        $this->label = $label;
-    }
-
-    public function getFileLoc()
-    {
-        return $this->fileLoc;
-    }
-
-    public function setFileLoc($fileLoc)
-    {
-        $this->fileLoc = $fileLoc;
-    }
-
-    public function getType()
-    {
-        return $this->type;
-    }
-
-    public function setType($type)
-    {
-        $this->type = $type;
-    }
-
-    public function getDisplay()
-    {
-        return $this->display;
-    }
-
-    public function setDisplay($display)
-    {
-        $this->display = (int) $display;
-    }
-
-    public function getVisible()
-    {
-        return $this->visible;
-    }
-
-    public function setVisible($visible)
-    {
-        $this->visible = $visible;
-    }
-
-    public function getDataset()
-    {
-        return $this->dataset;
-    }
-
-    public function jsonSerialize()
-    {
-        return [
-            'id' => $this->getId(),
-            'label' => $this->getLabel(),
-            'file_loc' => $this->getFileLoc(),
-            'type' => $this->getType(),
-            'display' => $this->getDisplay(),
-            'visible' => $this->getVisible()
-        ];
-    }
-}
diff --git a/src/Entity/Group.php b/src/Entity/Group.php
index f4fe223..006cdf3 100644
--- a/src/Entity/Group.php
+++ b/src/Entity/Group.php
@@ -34,13 +34,6 @@ class Group implements \JsonSerializable
      */
     protected $label;
 
-    /**
-     * @var Anis\Entity\User[]
-     *
-     * @OneToMany(targetEntity="User", mappedBy="group")
-     */
-    protected $users;
-
     /**
      * @var Anis\Entity\DatasetPrivileges
      *
@@ -63,11 +56,6 @@ class Group implements \JsonSerializable
         $this->label = $label;
     }
 
-    public function getUsers()
-    {
-        return $this->users;
-    }
-
     public function getDatasetPrivileges()
     {
         return $this->datasetPrivileges;
diff --git a/src/Entity/OutputCategory.php b/src/Entity/OutputCategory.php
index 9fdff31..f848f34 100644
--- a/src/Entity/OutputCategory.php
+++ b/src/Entity/OutputCategory.php
@@ -44,17 +44,10 @@ class OutputCategory implements \JsonSerializable
     /**
      * @var Anis\Entity\OutputFamily
      *
-     * @ManyToOne(targetEntity="OutputFamily", inversedBy="outputCategories")
+     * @ManyToOne(targetEntity="OutputFamily")
      * @JoinColumn(name="output_family", referencedColumnName="id", nullable=false)
      */
     protected $outputFamily;
-
-    /**
-     * @var Anis\Entity\Attribute[]
-     *
-     * @OneToMany(targetEntity="Attribute", mappedBy="outputCategory")
-     */
-    protected $attributes;
   
     public function getId()
     {
@@ -91,11 +84,6 @@ class OutputCategory implements \JsonSerializable
         return $this->outputFamily;
     }
 
-    public function getAttributes()
-    {
-        return $this->attributes;
-    }
-
     public function jsonSerialize()
     {
         return [
diff --git a/src/Entity/OutputFamily.php b/src/Entity/OutputFamily.php
index d768a47..e9ca386 100644
--- a/src/Entity/OutputFamily.php
+++ b/src/Entity/OutputFamily.php
@@ -41,13 +41,6 @@ class OutputFamily implements \JsonSerializable
      */
     protected $display;
 
-    /**
-     * @var Anis\Entity\OutputCategory[]
-     *
-     * @OneToMany(targetEntity="OutputCategory", mappedBy="outputFamily", cascade={"remove"})
-     */
-    protected $outputCategories;
-
     public function getId()
     {
         return $this->id;
@@ -73,11 +66,6 @@ class OutputFamily implements \JsonSerializable
         return $this->display;
     }
 
-    public function getOutputCategories()
-    {
-        return $this->outputCategories;
-    }
-
     public function jsonSerialize()
     {
         return [
diff --git a/src/Entity/Project.php b/src/Entity/Project.php
index e4f642f..67e1e09 100644
--- a/src/Entity/Project.php
+++ b/src/Entity/Project.php
@@ -62,13 +62,6 @@ class Project implements \JsonSerializable
      */
     protected $database;
 
-    /**
-     * @var Anis\Entity\Dataset[]
-     *
-     * @OneToMany(targetEntity="Dataset", mappedBy="project", cascade={"remove"})
-     */
-    protected $datasets;
-
     public function __construct($name)
     {
         $this->name = $name;
@@ -129,11 +122,6 @@ class Project implements \JsonSerializable
         return $this->database;
     }
 
-    public function getDatasets()
-    {
-        return $this->datasets;
-    }
-
     public function jsonSerialize()
     {
         return [
diff --git a/src/Entity/User.php b/src/Entity/User.php
index 17a5294..c2e1089 100644
--- a/src/Entity/User.php
+++ b/src/Entity/User.php
@@ -26,45 +26,10 @@ class User implements \JsonSerializable
      */
     protected $email;
 
-    /**
-     * @var string
-     *
-     * @Column(type="string", nullable=false)
-     */
-    protected $password;
-
-    /**
-     * @var string
-     *
-     * @Column(type="string", name="activation_key", nullable=false)
-     */
-    protected $activationKey;
-
-    /**
-     * @var bool
-     *
-     * @Column(type="boolean", nullable=false)
-     */
-    protected $activated;
-
-    /**
-     * @var bool
-     *
-     * @Column(type="boolean", nullable=false)
-     */
-    protected $adminsi;
-
-    /**
-     * @var bool
-     *
-     * @Column(type="boolean", nullable=false)
-     */
-    protected $superuser;
-
     /**
      * @var Anis\Entity\Group
      *
-     * @ManyToOne(targetEntity="Group", inversedBy="users")
+     * @ManyToOne(targetEntity="Group")
      * @JoinColumn(name="group_id", referencedColumnName="id", nullable=true)
      */
     protected $group;
@@ -79,56 +44,6 @@ class User implements \JsonSerializable
         return $this->email;
     }
 
-    public function getPassword()
-    {
-        return $this->password;
-    }
-
-    public function setPassword($password)
-    {
-        $this->password = $password;
-    }
-
-    public function getActivationKey()
-    {
-        return $this->activationKey;
-    }
-
-    public function setActivationKey($activationKey)
-    {
-        $this->activationKey = $activationKey;
-    }
-
-    public function getActivated()
-    {
-        return $this->activated;
-    }
-
-    public function setActivated($activated)
-    {
-        $this->activated = $activated;
-    }
-
-    public function getAdminsi()
-    {
-        return $this->adminsi;
-    }
-
-    public function setAdminsi($adminsi)
-    {
-        $this->adminsi = $adminsi;
-    }
-
-    public function getSuperuser()
-    {
-        return $this->superuser;
-    }
-
-    public function setSuperuser($superuser)
-    {
-        $this->superuser = $superuser;
-    }
-
     public function getGroup()
     {
         return $this->group;
@@ -143,9 +58,6 @@ class User implements \JsonSerializable
     {
         return [
             'email' => $this->getEmail(),
-            'activated' => $this->getActivated(),
-            'adminsi' => $this->getAdminsi(),
-            'superuser' => $this->getSuperuser(),
             'id_group' => $this->getGroup()->getId()
         ];
     }
diff --git a/src/Middleware/AuthorizationMiddleware.php b/src/Middleware/AuthorizationMiddleware.php
deleted file mode 100644
index 739dd60..0000000
--- a/src/Middleware/AuthorizationMiddleware.php
+++ /dev/null
@@ -1,137 +0,0 @@
-<?php
-
-/*
- * This file is part of Anis Server.
- *
- * (c) Laboratoire d'Astrophysique de Marseille / CNRS
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-declare(strict_types=1);
-
-namespace App\Middleware;
-
-use Psr\Http\Message\ResponseInterface as Response;
-use Psr\Http\Message\ServerRequestInterface as Request;
-use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
-use Psr\Http\Server\MiddlewareInterface;
-use Slim\Exception\HttpBadRequestException;
-use Slim\Exception\HttpUnauthorizedException;
-use Doctrine\ORM\EntityManagerInterface;
-use Lcobucci\JWT\ValidationData;
-use Lcobucci\JWT\Parser;
-use Lcobucci\JWT\Signer\Hmac\Sha256;
-
-/**
- * Middleware to handle authentication jwt
- *
- * @author François Agneray <francois.agneray@lam.fr>
- * @package App\Middleware
- */
-final class AuthorizationMiddleware implements MiddlewareInterface
-{
-    /**
-     * The EntityManager is the central access point to Doctrine ORM functionality
-     *
-     * @var EntityManagerInterface
-     */
-    protected $em;
-
-    /**
-     * This object contains token options from settings file
-     *
-     * @var array
-     */
-    private $tokenOptions;
-
-    /**
-     * Create the classe before call process to execute the middleware
-     *
-     * @param array                  $tokenOptions  Options to create the token
-     */
-    public function __construct(EntityManagerInterface $em, array $tokenOptions)
-    {
-        $this->em = $em;
-        $this->tokenOptions = $tokenOptions;
-    }
-
-    /**
-     * Handle jwt authentication
-     *
-     * @param  ServerRequest  $request PSR-7 request
-     * @param  RequestHandler $handler PSR-15 request handler
-     *
-     * @return Response
-     */
-    public function process(Request $request, RequestHandler $handler): Response
-    {
-        if (!$request->hasHeader('Authorization')) {
-            throw new HttpBadRequestException(
-                $request,
-                'Header Authorization needed for this route'
-            );
-        }
-
-        $authorization = $request->getHeaderLine('Authorization');
-
-        list($title, $jwt) = explode(' ', $authorization);
-        if ($title !== 'Bearer') {
-            throw new HttpBadRequestException(
-                $request,
-                'The format of the header Authorization must be Bearer jwt'
-            );
-        }
-
-        $token = (new Parser())->parse((string) $jwt);
-
-        if (!$token->validate($this->getValidationData())) {
-            throw new HttpUnauthorizedException(
-                $request,
-                'The jwt token is not validate'
-            );
-        }
-
-        $key = file_get_contents($this->tokenOptions['key']);
-        if (!$token->verify(new Sha256(), $key)) {
-            throw new HttpUnauthorizedException(
-                $request,
-                'The jwt token signature is not verify'
-            );
-        }
-
-        $jti = $token->getClaim('jti');
-        if ($this->getRevoked($jti)) {
-            throw new HttpUnauthorizedException(
-                $request,
-                'The jwt token is revoked'
-            );
-        }
-
-        return $handler->handle($request->withAttribute('superuser', $token->getClaim('superuser')));
-    }
-
-    /**
-     * Create and return the object used to validate a token
-     *
-     * @return ValidationData The object to validate a token
-     */
-    private function getValidationData(): ValidationData
-    {
-        $validationData = new ValidationData();
-        $validationData->setIssuer($this->tokenOptions['iss']);
-        $validationData->setAudience($this->tokenOptions['aud']);
-        return $validationData;
-    }
-
-    /**
-     * Return the state of the token (revoked or not)
-     *
-     * @return bool The state of the token
-     */
-    private function getRevoked($jti): bool
-    {
-        $entityToken = $this->em->find('App\Entity\Token', $jti);
-        return $entityToken->getRevoked();
-    }
-}
diff --git a/tests/Action/FamilyActionTest.php b/tests/Action/FamilyActionTest.php
index 313d9a2..84ba23c 100644
--- a/tests/Action/FamilyActionTest.php
+++ b/tests/Action/FamilyActionTest.php
@@ -84,7 +84,7 @@ final class FamilyActionTest extends TestCase
         $request = $this->getRequest('PUT')->withParsedBody($fields);
         $response = ($this->action)($request, new Response(), array('type' => 'dataset', 'id' => 1));
         $this->assertSame(
-            json_encode(array_merge(['id' => 1], $fields, ['type' => 'dataset', 'datasets' => array()])),
+            json_encode(array_merge(['id' => 1], $fields, ['type' => 'dataset'])),
             (string) $response->getBody()
         );
     }
diff --git a/tests/Action/FamilyListActionTest.php b/tests/Action/FamilyListActionTest.php
index 2c7ab2b..8206c58 100644
--- a/tests/Action/FamilyListActionTest.php
+++ b/tests/Action/FamilyListActionTest.php
@@ -66,7 +66,7 @@ final class FamilyListActionTest extends TestCase
         $this->assertEquals(400, (int) $response->getStatusCode());
     }
 
-    public function testAddANewDatabase(): void
+    public function testAddANewDatasetFamily(): void
     {
         $fields = array(
             'label' => 'Default family',
@@ -75,7 +75,37 @@ final class FamilyListActionTest extends TestCase
         $request = $this->getRequest('POST')->withParsedBody($fields);
         $response = ($this->action)($request, new Response(), array('type' => 'dataset'));
         $this->assertSame(
-            json_encode(array_merge(['id' => 1], $fields, ['type' => 'dataset', 'datasets' => array()])),
+            json_encode(array_merge(['id' => 1], $fields, ['type' => 'dataset'])),
+            (string) $response->getBody()
+        );
+        $this->assertEquals(201, (int) $response->getStatusCode());
+    }
+
+    public function testAddANewCriteriaFamily(): void
+    {
+        $fields = array(
+            'label' => 'Default criteria',
+            'display' => 10
+        );
+        $request = $this->getRequest('POST')->withParsedBody($fields);
+        $response = ($this->action)($request, new Response(), array('type' => 'criteria'));
+        $this->assertSame(
+            json_encode(array_merge(['id' => 1], $fields, ['type' => 'criteria'])),
+            (string) $response->getBody()
+        );
+        $this->assertEquals(201, (int) $response->getStatusCode());
+    }
+
+    public function testAddANewOutputFamily(): void
+    {
+        $fields = array(
+            'label' => 'Default output',
+            'display' => 10
+        );
+        $request = $this->getRequest('POST')->withParsedBody($fields);
+        $response = ($this->action)($request, new Response(), array('type' => 'output'));
+        $this->assertSame(
+            json_encode(array_merge(['id' => 1], $fields, ['type' => 'output'])),
             (string) $response->getBody()
         );
         $this->assertEquals(201, (int) $response->getStatusCode());
-- 
GitLab


From 903c16a9e0f993f0d014bd8e6f8fb5594cd3ede7 Mon Sep 17 00:00:00 2001
From: Tifenn GUILLAS <tifenn.guillas@lam.fr>
Date: Mon, 16 Dec 2019 15:04:24 +0100
Subject: [PATCH 06/31] Add CORS support

---
 app/middlewares.php                     |  1 +
 docker-compose.yml                      |  2 +-
 src/Middleware/CorsMiddleware.php       | 49 +++++++++++++++++++++
 tests/Middleware/CorsMiddlewareTest.php | 58 +++++++++++++++++++++++++
 4 files changed, 109 insertions(+), 1 deletion(-)
 create mode 100644 src/Middleware/CorsMiddleware.php
 create mode 100644 tests/Middleware/CorsMiddlewareTest.php

diff --git a/app/middlewares.php b/app/middlewares.php
index b029af0..0b27e31 100644
--- a/app/middlewares.php
+++ b/app/middlewares.php
@@ -12,3 +12,4 @@ declare(strict_types=1);
 
 $app->add(new App\Middleware\JsonBodyParserMiddleware());
 $app->add(new App\Middleware\ContentTypeJsonMiddleware());
+$app->add(new App\Middleware\CorsMiddleware());
diff --git a/docker-compose.yml b/docker-compose.yml
index 704697d..42f384a 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -19,7 +19,7 @@ services:
             LOGGER_PATH: "php://stderr"
             LOGGER_LEVEL: "debug"
         ports:
-            - 8082:80
+            - 8080:80
         volumes:
             - .:/project
             - ./conf-dev/dev-php.ini:/usr/local/etc/php/conf.d/dev-php.ini
diff --git a/src/Middleware/CorsMiddleware.php b/src/Middleware/CorsMiddleware.php
new file mode 100644
index 0000000..ea050f8
--- /dev/null
+++ b/src/Middleware/CorsMiddleware.php
@@ -0,0 +1,49 @@
+<?php
+
+/*
+ * This file is part of Anis Auth.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Middleware;
+
+use Psr\Http\Message\ResponseInterface as Response;
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
+use Psr\Http\Server\MiddlewareInterface;
+
+/**
+ * Middleware to allow resources to be requested from another origin
+ *
+ * @author Tifenn Guillas <tifenn.guillas@lam.fr>
+ * @package App\Middleware
+ */
+final class CorsMiddleware implements MiddlewareInterface
+{
+    /**
+     * Allow resources to be requested from another origin
+     *
+     * @param  ServerRequest  $request PSR-7 request
+     * @param  RequestHandler $handler PSR-15 request handler
+     *
+     * @return Response
+     */
+    public function process(Request $request, RequestHandler $handler): Response
+    {
+        $response = $handler->handle($request);
+
+        if ($request->getMethod() === OPTIONS) {
+            return $response
+                ->withHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization')
+                ->withHeader('Access-Control-Allow-Origin', '*');
+        }
+
+        return $response
+            ->withHeader('Access-Control-Allow-Origin', '*');
+    }
+}
diff --git a/tests/Middleware/CorsMiddlewareTest.php b/tests/Middleware/CorsMiddlewareTest.php
new file mode 100644
index 0000000..d74c690
--- /dev/null
+++ b/tests/Middleware/CorsMiddlewareTest.php
@@ -0,0 +1,58 @@
+<?php
+
+/*
+ * This file is part of Anis Auth.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Tests\Middleware;
+
+use PHPUnit\Framework\TestCase;
+use Nyholm\Psr7\ServerRequest;
+use Nyholm\Psr7\Response;
+use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
+
+final class CorsMiddlewareTest extends TestCase
+{
+    public function testCorsHeadersForOptionsMethod()
+    {
+        $request = new ServerRequest('OPTIONS', '/');
+
+        $requestHandler = $this->getMockBuilder(RequestHandler::class)
+            ->disableOriginalConstructor()
+            ->setMethods(['handle'])
+            ->getMock();
+
+        $requestHandler->method('handle')
+            ->with($this->identicalTo($request))
+            ->will($this->returnValue(new Response()));
+
+        $corsMiddleware = new \App\Middleware\CorsMiddleware();
+        $response = $corsMiddleware->process($request, $requestHandler);
+        $this->assertSame((string) $response->getHeaderLine('Access-Control-Allow-Origin'), '*');
+        $this->assertSame('Content-Type, Authorization', (string) $response->getHeaderLine('Access-Control-Allow-Headers'));
+    }
+    
+    public function testCorsHeadersForGetMethod()
+    {
+        $request = new ServerRequest('GET', '/');
+
+        $requestHandler = $this->getMockBuilder(RequestHandler::class)
+            ->disableOriginalConstructor()
+            ->setMethods(['handle'])
+            ->getMock();
+
+        $requestHandler->method('handle')
+            ->with($this->identicalTo($request))
+            ->will($this->returnValue(new Response()));
+
+        $corsMiddleware = new \App\Middleware\CorsMiddleware();
+        $response = $corsMiddleware->process($request, $requestHandler);
+        $this->assertSame((string) $response->getHeaderLine('Access-Control-Allow-Origin'), '*');
+    }
+}
-- 
GitLab


From af8995abada93ef3ec46866977bdb8626186f546 Mon Sep 17 00:00:00 2001
From: Tifenn GUILLAS <tifenn.guillas@lam.fr>
Date: Mon, 16 Dec 2019 15:11:27 +0100
Subject: [PATCH 07/31] Change port 8082 for 8080

---
 README.md        | 8 ++++----
 anis-server.yaml | 2 +-
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/README.md b/README.md
index 8b3e7ff..238d2bd 100644
--- a/README.md
+++ b/README.md
@@ -45,7 +45,7 @@ You can find an open api documentation into the `anis-server.yaml` file.
 
 ## Few examples with curl
 
-* To list all datasets available in the default instance => http://localhost:8082/dataset
-* To print all data for the obs_cat dataset with column 1, 2 and 3 => http://localhost:8082/search/obs_cat?a=1;2;3
-* To count the number of data available for a request => http://localhost:8082/search/obs_cat?a=count
-* To print only 3 obs_cat data (search by id) => http://localhost:8082/search/obs_cat?a=1;2;3&c=1::in::104600094|104600095|104600108
\ No newline at end of file
+* To list all datasets available in the default instance => http://localhost:8080/dataset
+* To print all data for the obs_cat dataset with column 1, 2 and 3 => http://localhost:8080/search/obs_cat?a=1;2;3
+* To count the number of data available for a request => http://localhost:8080/search/obs_cat?a=count
+* To print only 3 obs_cat data (search by id) => http://localhost:8080/search/obs_cat?a=1;2;3&c=1::in::104600094|104600095|104600108
\ No newline at end of file
diff --git a/anis-server.yaml b/anis-server.yaml
index 4fc1895..ac525c1 100644
--- a/anis-server.yaml
+++ b/anis-server.yaml
@@ -9,7 +9,7 @@ info:
     name: CeCILL 2.1
     url: http://www.cecill.info/licences/Licence_CeCILL_V2.1-en.html
 servers:
-  - url: http://localhost:8082
+  - url: http://localhost:8080
 tags:
   - name: root
     description: Root path
-- 
GitLab


From 25fe9c5ecda3f9d976d947a41c35c0315fe673a0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Agneray?= <francois.agneray@lam.fr>
Date: Mon, 16 Dec 2019 15:58:46 +0100
Subject: [PATCH 08/31] Fixed bug: Instance dependancies + dev_meta routes

---
 app/dependencies.php | 8 ++++++++
 conf-dev/dev-meta.sh | 5 +++--
 2 files changed, 11 insertions(+), 2 deletions(-)

diff --git a/app/dependencies.php b/app/dependencies.php
index e64cc63..4df1ae2 100644
--- a/app/dependencies.php
+++ b/app/dependencies.php
@@ -83,6 +83,14 @@ $container->set('App\Action\OutputCategoryAction', function (ContainerInterface
     return new App\Action\OutputCategoryAction($c->get('em'));
 });
 
+$container->set('App\Action\InstanceListAction', function (ContainerInterface $c) {
+    return new App\Action\InstanceListAction($c->get('em'));
+});
+
+$container->set('App\Action\InstanceAction', function (ContainerInterface $c) {
+    return new App\Action\InstanceAction($c->get('em'));
+});
+
 $container->set('App\Action\DatasetListAction', function (ContainerInterface $c) {
     return new App\Action\DatasetListAction($c->get('em'), new App\Utils\DBALConnectionFactory());
 });
diff --git a/conf-dev/dev-meta.sh b/conf-dev/dev-meta.sh
index 9db6662..c9efe8a 100644
--- a/conf-dev/dev-meta.sh
+++ b/conf-dev/dev-meta.sh
@@ -5,5 +5,6 @@ curl -d '{"label":"Test","dbname":"anis_test","dbtype":"pdo_pgsql","dbhost":"db"
 curl -d '{"name":"anis_project","label":"Anis Project Test","description":"Project used for testing","link":"http://project.com","manager":"M. Durand","id_database":1}' -H "Content-Type: application/json" -X POST http://localhost/project
 
 curl -d '{"label":"Default dataset family","display":10}' -H "Content-Type: application/json" -X POST http://localhost/family/dataset
-curl -d '{"name":"obs_cat","table_ref":"obs_cat","label":"ObsCat dataset","description":"ObsCat","display":"10","count":"10000","vo":false,"data_path":"/mnt/mount","selectable_row":true,"project_name":"anis_project","id_dataset_family":1}' -H "Content-Type: application/json" -X POST http://localhost/dataset
-curl -d '{"name":"observations","table_ref":"observations_info","label":"Observations dataset","description":"Observations","display":"20","count":"177454","vo":false,"data_path":"/mnt/mount","selectable_row":false,"project_name":"anis_project","id_dataset_family":1}' -H "Content-Type: application/json" -X POST http://localhost/dataset
+curl -d '{"name":"default","label":"Default instance","client_url":"http://localhost:4200"}' -H "Content-Type: application/json" -X POST http://localhost/instance
+curl -d '{"name":"obs_cat","table_ref":"obs_cat","label":"ObsCat dataset","description":"ObsCat","display":"10","count":"10000","vo":false,"data_path":"/mnt/mount","selectable_row":true,"project_name":"anis_project","id_dataset_family":1}' -H "Content-Type: application/json" -X POST http://localhost/instance/default/dataset
+curl -d '{"name":"observations","table_ref":"observations_info","label":"Observations dataset","description":"Observations","display":"20","count":"177454","vo":false,"data_path":"/mnt/mount","selectable_row":false,"project_name":"anis_project","id_dataset_family":1}' -H "Content-Type: application/json" -X POST http://localhost/instance/default/dataset
-- 
GitLab


From 7235d8afd5fceba383169ea76132b5149f0db22d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Agneray?= <francois.agneray@lam.fr>
Date: Mon, 16 Dec 2019 17:42:44 +0100
Subject: [PATCH 09/31] Fixed bug:  Dockerfile copy files into /project

---
 Dockerfile | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/Dockerfile b/Dockerfile
index dc688d2..28aed33 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -7,9 +7,9 @@ RUN apt-get update \
 
 RUN a2enmod rewrite
 
-COPY . /srv/app
+COPY . /project
 COPY ./conf-dev/vhost.conf /etc/apache2/sites-available/000-default.conf
 
-WORKDIR /srv/app
+WORKDIR /project
 
 CMD ["apache2-foreground"]
-- 
GitLab


From 3c6bfe8cb787b22a2070e4bc58ccf7191e895e92 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Agneray?= <francois.agneray@lam.fr>
Date: Tue, 17 Dec 2019 11:55:24 +0100
Subject: [PATCH 10/31] Add getAttributes method to the dataset class needed by
 searchAction

---
 src/Entity/Attribute.php |  2 +-
 src/Entity/Dataset.php   | 12 ++++++++++++
 2 files changed, 13 insertions(+), 1 deletion(-)

diff --git a/src/Entity/Attribute.php b/src/Entity/Attribute.php
index 210379b..cdb7191 100644
--- a/src/Entity/Attribute.php
+++ b/src/Entity/Attribute.php
@@ -32,7 +32,7 @@ class Attribute implements \JsonSerializable
      * @var App\Entity\Dataset
      *
      * @Id
-     * @ManyToOne(targetEntity="Dataset")
+     * @ManyToOne(targetEntity="Dataset", inversedBy="attributes")
      * @JoinColumn(name="dataset_name", referencedColumnName="name", nullable=false)
      */
     protected $dataset;
diff --git a/src/Entity/Dataset.php b/src/Entity/Dataset.php
index 1740f0d..84f07f2 100644
--- a/src/Entity/Dataset.php
+++ b/src/Entity/Dataset.php
@@ -107,6 +107,13 @@ class Dataset implements \JsonSerializable
      * @JoinColumn(name="id_dataset_family", referencedColumnName="id", nullable=true)
      */
     protected $datasetFamily;
+    
+    /**
+     * @var Anis\Entity\Attribute[]
+     *
+     * @OneToMany(targetEntity="Attribute", mappedBy="dataset", cascade={"remove"})
+     */
+    protected $attributes;
 
     public function __construct($name)
     {
@@ -229,6 +236,11 @@ class Dataset implements \JsonSerializable
         $this->datasetFamily = $datasetFamily;
     }
 
+    public function getAttributes()
+    {
+        return $this->attributes;
+    }
+
     public function jsonSerialize()
     {
         return [
-- 
GitLab


From d72d5ff3589c445f918f6c052a9a86db2c533a9f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Agneray?= <francois.agneray@lam.fr>
Date: Wed, 18 Dec 2019 15:17:08 +0100
Subject: [PATCH 11/31] Add create-db shell script

---
 Makefile                               | 5 +----
 conf-dev/{dev-meta.sh => create-db.sh} | 7 +++++++
 2 files changed, 8 insertions(+), 4 deletions(-)
 rename conf-dev/{dev-meta.sh => create-db.sh} (73%)

diff --git a/Makefile b/Makefile
index bf6867b..bfbd169 100644
--- a/Makefile
+++ b/Makefile
@@ -59,10 +59,7 @@ phpcs:
 	-w /project jakzal/phpqa phpcs --standard=PSR12 --extensions=php --colors src tests
 
 create-db:
-	@docker-compose exec php ./vendor/bin/doctrine orm:schema-tool:create
-
-dev-meta:
-	@docker-compose exec php sh ./conf-dev/dev-meta.sh
+	@docker-compose exec php sh ./conf-dev/create-db.sh
 
 remove-pgdata:
 	@docker volume rm anis-server_pgdata
diff --git a/conf-dev/dev-meta.sh b/conf-dev/create-db.sh
similarity index 73%
rename from conf-dev/dev-meta.sh
rename to conf-dev/create-db.sh
index c9efe8a..72f2746 100644
--- a/conf-dev/dev-meta.sh
+++ b/conf-dev/create-db.sh
@@ -1,6 +1,9 @@
 #!/bin/sh
 set -e
 
+# Create the settings database (only tables)
+./vendor/bin/doctrine orm:schema-tool:create
+
 curl -d '{"label":"Test","dbname":"anis_test","dbtype":"pdo_pgsql","dbhost":"db","dbport":5432,"dblogin":"anis","dbpassword":"anis"}' -H "Content-Type: application/json" -X POST http://localhost/database
 curl -d '{"name":"anis_project","label":"Anis Project Test","description":"Project used for testing","link":"http://project.com","manager":"M. Durand","id_database":1}' -H "Content-Type: application/json" -X POST http://localhost/project
 
@@ -8,3 +11,7 @@ curl -d '{"label":"Default dataset family","display":10}' -H "Content-Type: appl
 curl -d '{"name":"default","label":"Default instance","client_url":"http://localhost:4200"}' -H "Content-Type: application/json" -X POST http://localhost/instance
 curl -d '{"name":"obs_cat","table_ref":"obs_cat","label":"ObsCat dataset","description":"ObsCat","display":"10","count":"10000","vo":false,"data_path":"/mnt/mount","selectable_row":true,"project_name":"anis_project","id_dataset_family":1}' -H "Content-Type: application/json" -X POST http://localhost/instance/default/dataset
 curl -d '{"name":"observations","table_ref":"observations_info","label":"Observations dataset","description":"Observations","display":"20","count":"177454","vo":false,"data_path":"/mnt/mount","selectable_row":false,"project_name":"anis_project","id_dataset_family":1}' -H "Content-Type: application/json" -X POST http://localhost/instance/default/dataset
+
+curl -d '{"label":"Default criteria family","display":10}' -H "Content-Type: application/json" -X POST http://localhost/family/criteria
+curl -d '{"label":"Default output family","display":10}' -H "Content-Type: application/json" -X POST http://localhost/family/output
+curl -d '{"label":"Default output category","display":10,"id_output_family":1}' -H "Content-Type: application/json" -X POST http://localhost/output-category
-- 
GitLab


From 8d0f6b8c58d13dd5bf7dc8a0ee584b46269e7b67 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Agneray?= <francois.agneray@lam.fr>
Date: Wed, 18 Dec 2019 22:22:25 +0100
Subject: [PATCH 12/31] WIP

---
 app/routes.php                |  14 ++--
 src/Entity/Attribute.php      | 134 +++++++++++++++++-----------------
 src/Entity/CriteriaFamily.php |  30 +++++---
 src/Entity/Database.php       |  30 ++++----
 src/Entity/Dataset.php        |  71 +++++++-----------
 src/Entity/DatasetFamily.php  |  26 ++++---
 src/Entity/OutputCategory.php |  14 ++--
 src/Entity/OutputFamily.php   |  23 ++++--
 src/Entity/Project.php        |  24 +++---
 9 files changed, 192 insertions(+), 174 deletions(-)

diff --git a/app/routes.php b/app/routes.php
index 6871f36..0056afa 100644
--- a/app/routes.php
+++ b/app/routes.php
@@ -18,14 +18,18 @@ $app->map([OPTIONS, GET, PUT, DELETE], '/database/{id}', App\Action\DatabaseActi
 $app->map([OPTIONS, GET], '/database/{id}/table', App\Action\TableListAction::class);
 $app->map([OPTIONS, GET, POST], '/project', App\Action\ProjectListAction::class);
 $app->map([OPTIONS, GET, PUT, DELETE], '/project/{name}', App\Action\ProjectAction::class);
-$app->map([OPTIONS, GET, POST], '/family/{type}', App\Action\FamilyListAction::class);
-$app->map([OPTIONS, GET, PUT, DELETE], '/family/{type}/{id}', App\Action\FamilyAction::class);
-$app->map([OPTIONS, GET, POST], '/output-category', App\Action\OutputCategoryListAction::class);
-$app->map([OPTIONS, GET, PUT, DELETE], '/output-category/{id}', App\Action\OutputCategoryAction::class);
 $app->map([OPTIONS, GET, POST], '/instance', App\Action\InstanceListAction::class);
 $app->map([OPTIONS, GET, PUT, DELETE], '/instance/{name}', App\Action\InstanceAction::class);
-$app->map([OPTIONS, GET, POST], '/instance/{name}/dataset', App\Action\DatasetListAction::class);
+$app->map([OPTIONS, GET, POST], '/instance/{name}/dataset-family', App\Action\DatasetFamilyListAction::class);
+$app->map([OPTIONS, GET, PUT, DELETE], '/dataset-family/{id}', App\Action\DatasetFamilyAction::class);
+$app->map([OPTIONS, GET, POST], '/dataset-family/{id}/dataset', App\Action\DatasetListAction::class);
 $app->map([OPTIONS, GET, PUT, DELETE], '/dataset/{name}', App\Action\DatasetAction::class);
+$app->map([OPTIONS, GET, POST], '/dataset/{name}/criteria-family', App\Action\CriteriaFamilyListAction::class);
+$app->map([OPTIONS, GET, POST], '/dataset/{name}/output-family', App\Action\OutputFamilyListAction::class);
 $app->map([OPTIONS, GET], '/dataset/{name}/attribute', App\Action\AttributeListAction::class);
 $app->map([OPTIONS, GET, PUT], '/dataset/{name}/attribute/{id}', App\Action\AttributeAction::class);
+$app->map([OPTIONS, GET, PUT, DELETE], '/criteria-family/{id}', App\Action\CriteriaFamilyAction::class);
+$app->map([OPTIONS, GET, PUT, DELETE], '/output-family/{id}', App\Action\OutputFamilyAction::class);
+$app->map([OPTIONS, GET, POST], '/output-family/{id}/output-category', App\Action\OutputCategoryListAction::class);
+$app->map([OPTIONS, GET, PUT, DELETE], '/output-category/{id}', App\Action\OutputCategoryAction::class);
 $app->get('/search/{dname}', App\Action\SearchAction::class);
diff --git a/src/Entity/Attribute.php b/src/Entity/Attribute.php
index cdb7191..4bcfa35 100644
--- a/src/Entity/Attribute.php
+++ b/src/Entity/Attribute.php
@@ -263,333 +263,333 @@ class Attribute implements \JsonSerializable
      */
     protected $outputCategory;
 
-    public function __construct($id, $dataset)
+    public function __construct(int $id, Dataset $dataset)
     {
         $this->id = $id;
         $this->dataset = $dataset;
     }
 
-    public function getId()
+    public function getId(): int
     {
         return $this->id;
     }
 
-    public function getName()
+    public function getName(): string
     {
         return $this->name;
     }
 
-    public function setName($name)
+    public function setName(string $name): void
     {
         $this->name = $name;
     }
 
-    public function getTableName()
+    public function getTableName(): string
     {
         return $this->tableName;
     }
 
-    public function setTableName($tableName)
+    public function setTableName(string $tableName): void
     {
         $this->tableName = $tableName;
     }
 
-    public function getLabel()
+    public function getLabel(): string
     {
         return $this->label;
     }
 
-    public function setLabel($label)
+    public function setLabel(string $label): void
     {
         $this->label = $label;
     }
 
-    public function getFormLabel()
+    public function getFormLabel(): string
     {
         return $this->formLabel;
     }
 
-    public function setFormLabel($formLabel)
+    public function setFormLabel(string $formLabel): void
     {
         $this->formLabel = $formLabel;
     }
 
-    public function getDescription()
+    public function getDescription(): string
     {
         return $this->description;
     }
 
-    public function setDescription($description)
+    public function setDescription(string $description): void
     {
         $this->description = $description;
     }
 
-    public function getOutputDisplay()
+    public function getOutputDisplay(): int
     {
         return $this->outputDisplay;
     }
 
-    public function setOutputDisplay($outputDisplay)
+    public function setOutputDisplay(int $outputDisplay): void
     {
         $this->outputDisplay = $outputDisplay;
     }
 
-    public function setCriteriaDisplay($criteriaDisplay)
+    public function setCriteriaDisplay(int $criteriaDisplay): void
     {
         $this->criteriaDisplay = $criteriaDisplay;
     }
 
-    public function getCriteriaDisplay()
+    public function getCriteriaDisplay(): int
     {
         return $this->criteriaDisplay;
     }
 
-    public function getSearchFlag()
+    public function getSearchFlag(): string
     {
         return $this->searchFlag;
     }
 
-    public function setSearchFlag($searchFlag)
+    public function setSearchFlag(string $searchFlag): void
     {
         $this->searchFlag = $searchFlag;
     }
 
-    public function getSearchType()
+    public function getSearchType(): string
     {
         return $this->searchType;
     }
 
-    public function setSearchType($searchType)
+    public function setSearchType(string $searchType): void
     {
         $this->searchType = $searchType;
     }
 
-    public function getOperator()
+    public function getOperator(): string
     {
         return $this->operator;
     }
 
-    public function setOperator($operator)
+    public function setOperator(string $operator): void
     {
         $this->operator = $operator;
     }
 
-    public function getType()
+    public function getType(): string
     {
         return $this->type;
     }
 
-    public function setType($type)
+    public function setType(string $type): void
     {
         $this->type = $type;
     }
 
-    public function getMin()
+    public function getMin(): string
     {
         return $this->min;
     }
 
-    public function setMin($min)
+    public function setMin(string $min): void
     {
         $this->min = $min;
     }
 
-    public function getMax()
+    public function getMax(): string
     {
         return $this->max;
     }
 
-    public function setMax($max)
+    public function setMax(string $max): void
     {
         $this->max = $max;
     }
 
-    public function getPlaceholderMin()
+    public function getPlaceholderMin(): string
     {
         return $this->placeholderMin;
     }
 
-    public function setPlaceholderMin($placeholderMin)
+    public function setPlaceholderMin(string $placeholderMin): void
     {
         $this->placeholderMin = $placeholderMin;
     }
 
-    public function getPlaceholderMax()
+    public function getPlaceholderMax(): string
     {
         return $this->placeholderMax;
     }
 
-    public function setPlaceholderMax($placeholderMax)
+    public function setPlaceholderMax(string $placeholderMax): void
     {
         $this->placeholderMax = $placeholderMax;
     }
 
-    public function getUriAction()
+    public function getUriAction(): string
     {
         return $this->uriAction;
     }
 
-    public function setUriAction($uriAction)
+    public function setUriAction(string $uriAction): void
     {
         $this->uriAction = $uriAction;
     }
 
-    public function getRenderer()
+    public function getRenderer(): string
     {
         return $this->renderer;
     }
 
-    public function setRenderer($renderer)
+    public function setRenderer(string $renderer): void
     {
         $this->renderer = $renderer;
     }
 
-    public function getDisplayDetail()
+    public function getDisplayDetail(): int
     {
         return $this->displayDetail;
     }
 
-    public function setDisplayDetail($displayDetail)
+    public function setDisplayDetail(int $displayDetail): void
     {
         $this->displayDetail = $displayDetail;
     }
 
-    public function getVoUtype()
+    public function getVoUtype(): string
     {
         return $this->voUtype;
     }
 
-    public function setVoUtype($voUtype)
+    public function setVoUtype(string $voUtype): void
     {
         $this->voUtype = $voUtype;
     }
 
-    public function getVoUcd()
+    public function getVoUcd(): string
     {
         return $this->voUcd;
     }
 
-    public function setVoUcd($voUcd)
+    public function setVoUcd(string $voUcd): void
     {
         $this->voUcd = $voUcd;
     }
 
-    public function getVoUnit()
+    public function getVoUnit(): string
     {
         return $this->voUnit;
     }
 
-    public function setVoUnit($voUnit)
+    public function setVoUnit(string $voUnit): void
     {
         $this->voUnit = $voUnit;
     }
 
-    public function getVoDescription()
+    public function getVoDescription(): string
     {
         return $this->voDescription;
     }
 
-    public function setVoDescription($voDescription)
+    public function setVoDescription(string $voDescription): void
     {
         $this->voDescription = $voDescription;
     }
 
-    public function getVoDatatype()
+    public function getVoDatatype(): string
     {
         return $this->voDatatype;
     }
 
-    public function setVoDatatype($voDatatype)
+    public function setVoDatatype(string $voDatatype): void
     {
         $this->voDatatype = $voDatatype;
     }
 
-    public function getVoSize()
+    public function getVoSize(): int
     {
         return $this->voSize;
     }
 
-    public function setVoSize($voSize)
+    public function setVoSize(int $voSize): void
     {
-        $this->voSize = (int) $voSize;
+        $this->voSize = $voSize;
     }
 
-    public function getSelected()
+    public function getSelected(): bool
     {
         return $this->selected;
     }
 
-    public function setSelected($selected)
+    public function setSelected(bool $selected): void
     {
         $this->selected = $selected;
     }
 
-    public function getOrderBy()
+    public function getOrderBy(): bool
     {
         return $this->orderBy;
     }
 
-    public function setOrderBy($orderBy)
+    public function setOrderBy(bool $orderBy): void
     {
         $this->orderBy = $orderBy;
     }
 
-    public function getOrderDisplay()
+    public function getOrderDisplay(): int
     {
         return $this->orderDisplay;
     }
 
-    public function setOrderDisplay($orderDisplay)
+    public function setOrderDisplay(int $orderDisplay): void
     {
         $this->orderDisplay = $orderDisplay;
     }
 
-    public function getDetail()
+    public function getDetail(): bool
     {
         return $this->detail;
     }
 
-    public function setDetail($detail)
+    public function setDetail(bool $detail): void
     {
         $this->detail = $detail;
     }
 
-    public function getRendererDetail()
+    public function getRendererDetail(): string
     {
         return $this->rendererDetail;
     }
 
-    public function setRendererDetail($rendererDetail)
+    public function setRendererDetail(string $rendererDetail): void
     {
         $this->rendererDetail = $rendererDetail;
     }
 
-    public function getOptions()
+    public function getOptions(): string
     {
         return $this->options;
     }
 
-    public function setOptions($options)
+    public function setOptions(string $options): void
     {
         $this->options = $options;
     }
 
-    public function getCriteriaFamily()
+    public function getCriteriaFamily(): CriteriaFamily
     {
         return $this->criteriaFamily;
     }
 
-    public function setCriteriaFamily($criteriaFamily)
+    public function setCriteriaFamily(CriteriaFamily $criteriaFamily): void
     {
         $this->criteriaFamily = $criteriaFamily;
     }
 
-    public function getOutputCategory()
+    public function getOutputCategory(): OutputCategory
     {
         return $this->outputCategory;
     }
 
-    public function setOutputCategory($outputCategory)
+    public function setOutputCategory(OutputCategory $outputCategory): void
     {
         $this->outputCategory = $outputCategory;
     }
diff --git a/src/Entity/CriteriaFamily.php b/src/Entity/CriteriaFamily.php
index d5e43e4..158be1d 100644
--- a/src/Entity/CriteriaFamily.php
+++ b/src/Entity/CriteriaFamily.php
@@ -19,7 +19,7 @@ namespace App\Entity;
 class CriteriaFamily implements \JsonSerializable
 {
     /**
-     * @var integer
+     * @var int
      *
      * @Id
      * @Column(type="integer", nullable=false)
@@ -35,33 +35,46 @@ class CriteriaFamily implements \JsonSerializable
     protected $label;
 
     /**
-     * @var integer
+     * @var int
      *
      * @Column(type="integer", nullable=false)
      */
     protected $display;
 
-    public function getId()
+    /**
+     * @var Anis\Entity\Dataset
+     *
+     * @ManyToOne(targetEntity="Dataset")
+     * @JoinColumn(name="dataset_name", referencedColumnName="name", nullable=false)
+     */
+    protected $dataset;
+
+    public function __construct(Dataset $dataset)
+    {
+        $this->dataset = $dataset;
+    }
+
+    public function getId(): int
     {
         return $this->id;
     }
   
-    public function getLabel()
+    public function getLabel(): string
     {
         return $this->label;
     }
   
-    public function setLabel($label)
+    public function setLabel(string $label): void
     {
         $this->label = $label;
     }
 
-    public function getDisplay()
+    public function getDisplay(): int
     {
         return $this->display;
     }
 
-    public function setDisplay($display)
+    public function setDisplay(int $display): void
     {
         $this->display = $display;
     }
@@ -71,8 +84,7 @@ class CriteriaFamily implements \JsonSerializable
         return [
             'id' => $this->getId(),
             'label' => $this->getLabel(),
-            'display' => $this->getDisplay(),
-            'type' => 'criteria'
+            'display' => $this->getDisplay()
         ];
     }
 }
diff --git a/src/Entity/Database.php b/src/Entity/Database.php
index 1cf4c5d..5d36199 100644
--- a/src/Entity/Database.php
+++ b/src/Entity/Database.php
@@ -81,7 +81,7 @@ class Database implements \JsonSerializable
      *
      * @return int
      */
-    public function getId()
+    public function getId(): int
     {
         return $this->id;
     }
@@ -91,7 +91,7 @@ class Database implements \JsonSerializable
      *
      * @return string
      */
-    public function getLabel()
+    public function getLabel(): string
     {
         return $this->label;
     }
@@ -101,7 +101,7 @@ class Database implements \JsonSerializable
      *
      * @param string $label
      */
-    public function setLabel($label)
+    public function setLabel(string $label): void
     {
         $this->label = $label;
     }
@@ -111,7 +111,7 @@ class Database implements \JsonSerializable
      *
      * @return string
      */
-    public function getDbName()
+    public function getDbName(): string
     {
         return $this->dbname;
     }
@@ -121,57 +121,57 @@ class Database implements \JsonSerializable
      *
      * @param string $dbname
      */
-    public function setDbName($dbname)
+    public function setDbName(string $dbname): void
     {
         $this->dbname = $dbname;
     }
 
-    public function getType()
+    public function getType(): string
     {
         return $this->type;
     }
 
-    public function setType($type)
+    public function setType(string $type): void
     {
         $this->type = $type;
     }
 
-    public function getHost()
+    public function getHost(): string
     {
         return $this->host;
     }
 
-    public function setHost($host)
+    public function setHost(string $host): void
     {
         $this->host = $host;
     }
 
-    public function getPort()
+    public function getPort(): int
     {
         return $this->port;
     }
 
-    public function setPort($port)
+    public function setPort(int $port): void
     {
         $this->port = $port;
     }
 
-    public function getLogin()
+    public function getLogin(): string
     {
         return $this->login;
     }
 
-    public function setLogin($login)
+    public function setLogin(string $login): void
     {
         $this->login = $login;
     }
 
-    public function getPassword()
+    public function getPassword(): string
     {
         return $this->password;
     }
 
-    public function setPassword($password)
+    public function setPassword(string $password): void
     {
         $this->password = $password;
     }
diff --git a/src/Entity/Dataset.php b/src/Entity/Dataset.php
index 84f07f2..5d47433 100644
--- a/src/Entity/Dataset.php
+++ b/src/Entity/Dataset.php
@@ -92,14 +92,6 @@ class Dataset implements \JsonSerializable
      */
     protected $project;
 
-    /**
-     * @var Anis\Entity\Instance
-     *
-     * @ManyToOne(targetEntity="Instance")
-     * @JoinColumn(name="instance_name", referencedColumnName="name", nullable=false)
-     */
-    protected $instance;
-
     /**
      * @var Anis\Entity\Project
      *
@@ -121,122 +113,112 @@ class Dataset implements \JsonSerializable
         $this->attributes = new ArrayCollection();
     }
     
-    public function getName()
+    public function getName(): string
     {
         return $this->name;
     }
     
-    public function getTableRef()
+    public function getTableRef(): string
     {
         return $this->tableRef;
     }
     
-    public function setTableRef($tableRef)
+    public function setTableRef(string $tableRef): void
     {
-        return $this->tableRef = $tableRef;
+        $this->tableRef = $tableRef;
     }
     
-    public function getLabel()
+    public function getLabel(): string
     {
         return $this->label;
     }
     
-    public function setLabel($label)
+    public function setLabel(string $label): void
     {
         $this->label = $label;
     }
     
-    public function getDescription()
+    public function getDescription(): string
     {
         return $this->description;
     }
     
-    public function setDescription($description)
+    public function setDescription(string $description): void
     {
         $this->description = $description;
     }
     
-    public function getDisplay()
+    public function getDisplay(): int
     {
         return $this->display;
     }
     
-    public function setDisplay($display)
+    public function setDisplay(int $display): void
     {
-        $this->display = (int) $display;
+        $this->display = $display;
     }
     
-    public function getCount()
+    public function getCount(): int
     {
         return $this->count;
     }
     
-    public function setCount($count)
+    public function setCount(int $count): void
     {
-        $this->count = (int) $count;
+        $this->count = $count;
     }
     
-    public function getVo()
+    public function getVo(): bool
     {
         return $this->vo;
     }
     
-    public function setVo($vo)
+    public function setVo(bool $vo): void
     {
-        $this->vo = (bool) $vo;
+        $this->vo = $vo;
     }
     
-    public function getDataPath()
+    public function getDataPath(): string
     {
         return $this->dataPath;
     }
 
-    public function setDataPath($dataPath)
+    public function setDataPath(string $dataPath): void
     {
         $this->dataPath = $dataPath;
     }
 
-    public function getSelectableRow()
+    public function getSelectableRow(): bool
     {
         return $this->selectableRow;
     }
 
-    public function setSelectableRow($selectableRow)
+    public function setSelectableRow(bool $selectableRow): void
     {
         $this->selectableRow = $selectableRow;
     }
 
-    public function getProject()
+    public function getProject(): Project
     {
         return $this->project;
     }
     
-    public function setProject(Project $project)
+    public function setProject(Project $project): void
     {
         $this->project = $project;
     }
 
-    public function getInstance(): Instance
-    {
-        return $this->instance;
-    }
-
-    public function setInstance(Instance $instance): void
-    {
-        $this->instance = $instance;
-    }
-
-    public function getDatasetFamily()
+    public function getDatasetFamily(): DatasetFamily
     {
         return $this->datasetFamily;
     }
 
-    public function setDatasetFamily($datasetFamily)
+    public function setDatasetFamily(DatasetFamily $datasetFamily): void
     {
         $this->datasetFamily = $datasetFamily;
     }
 
-    public function getAttributes()
+    public function getAttributes(): ArrayCollection
     {
         return $this->attributes;
     }
@@ -254,7 +236,6 @@ class Dataset implements \JsonSerializable
             'data_path' => $this->getDataPath(),
             'selectable_row' => $this->getSelectableRow(),
             'project_name' => $this->getProject()->getName(),
-            'instance_name' => $this->getInstance()->getName(),
             'id_dataset_family' => $this->getDatasetFamily()->getId()
         ];
     }
diff --git a/src/Entity/DatasetFamily.php b/src/Entity/DatasetFamily.php
index f679255..0736edb 100644
--- a/src/Entity/DatasetFamily.php
+++ b/src/Entity/DatasetFamily.php
@@ -43,34 +43,43 @@ class DatasetFamily implements \JsonSerializable
      */
     protected $display;
 
-    public function __construct()
+    /**
+     * @var Anis\Entity\Instance
+     *
+     * @ManyToOne(targetEntity="Instance")
+     * @JoinColumn(name="instance_name", referencedColumnName="name", nullable=false)
+     */
+    protected $instance;
+
+    public function __construct(Instance $instance)
     {
+        $this->instance = $instance;
         $this->datasets = new ArrayCollection();
     }
 
-    public function getId()
+    public function getId(): int
     {
         return $this->id;
     }
 
-    public function getLabel()
+    public function getLabel(): string
     {
         return $this->label;
     }
 
-    public function setLabel($label)
+    public function setLabel(string $label): void
     {
         $this->label = $label;
     }
 
-    public function getDisplay()
+    public function getDisplay(): int
     {
         return $this->display;
     }
 
-    public function setDisplay($display)
+    public function setDisplay(int $display): void
     {
-        $this->display = (int) $display;
+        $this->display = $display;
     }
 
     public function jsonSerialize()
@@ -78,8 +87,7 @@ class DatasetFamily implements \JsonSerializable
         return [
             'id' => $this->getId(),
             'label' => $this->getLabel(),
-            'display' => $this->getDisplay(),
-            'type' => 'dataset'
+            'display' => $this->getDisplay()
         ];
     }
 }
diff --git a/src/Entity/OutputCategory.php b/src/Entity/OutputCategory.php
index f848f34..5458879 100644
--- a/src/Entity/OutputCategory.php
+++ b/src/Entity/OutputCategory.php
@@ -49,37 +49,37 @@ class OutputCategory implements \JsonSerializable
      */
     protected $outputFamily;
   
-    public function getId()
+    public function getId(): int
     {
         return $this->id;
     }
 
-    public function getLabel()
+    public function getLabel(): string
     {
         return $this->label;
     }
 
-    public function setLabel($label)
+    public function setLabel(string $label): void
     {
         $this->label = $label;
     }
 
-    public function getDisplay()
+    public function getDisplay(): int
     {
         return $this->display;
     }
 
-    public function setDisplay($display)
+    public function setDisplay(int $display): void
     {
         $this->display = $display;
     }
 
-    public function setOutputFamily($outputFamily)
+    public function setOutputFamily(OutputFamily $outputFamily): void
     {
         $this->outputFamily = $outputFamily;
     }
     
-    public function getOutputFamily()
+    public function getOutputFamily(): OutputFamily
     {
         return $this->outputFamily;
     }
diff --git a/src/Entity/OutputFamily.php b/src/Entity/OutputFamily.php
index e9ca386..6f54613 100644
--- a/src/Entity/OutputFamily.php
+++ b/src/Entity/OutputFamily.php
@@ -41,27 +41,40 @@ class OutputFamily implements \JsonSerializable
      */
     protected $display;
 
-    public function getId()
+    /**
+     * @var Anis\Entity\Dataset
+     *
+     * @ManyToOne(targetEntity="Dataset")
+     * @JoinColumn(name="dataset_name", referencedColumnName="name", nullable=false)
+     */
+    protected $dataset;
+
+    public function __construct(Dataset $dataset)
+    {
+        $this->dataset = $dataset;
+    }
+
+    public function getId(): int
     {
         return $this->id;
     }
   
-    public function getLabel()
+    public function getLabel(): string
     {
         return $this->label;
     }
   
-    public function setLabel($label)
+    public function setLabel(string $label): void
     {
         $this->label = $label;
     }
 
-    public function setDisplay($display)
+    public function setDisplay(int $display): void
     {
         $this->display = $display;
     }
 
-    public function getDisplay()
+    public function getDisplay(): int
     {
         return $this->display;
     }
diff --git a/src/Entity/Project.php b/src/Entity/Project.php
index 67e1e09..0151129 100644
--- a/src/Entity/Project.php
+++ b/src/Entity/Project.php
@@ -62,62 +62,62 @@ class Project implements \JsonSerializable
      */
     protected $database;
 
-    public function __construct($name)
+    public function __construct(string $name)
     {
         $this->name = $name;
     }
 
-    public function getName()
+    public function getName(): string
     {
         return $this->name;
     }
 
-    public function getLabel()
+    public function getLabel(): string
     {
         return $this->label;
     }
 
-    public function setLabel($label)
+    public function setLabel(string $label): void
     {
         $this->label = $label;
     }
 
-    public function getDescription()
+    public function getDescription(): string
     {
         return $this->description;
     }
 
-    public function setDescription($description)
+    public function setDescription(string $description): void
     {
         $this->description = $description;
     }
 
-    public function getLink()
+    public function getLink(): string
     {
         return $this->link;
     }
 
-    public function setLink($link)
+    public function setLink(string $link): void
     {
         $this->link = $link;
     }
 
-    public function getManager()
+    public function getManager(): string
     {
         return $this->manager;
     }
 
-    public function setManager($manager)
+    public function setManager(string $manager): void
     {
         $this->manager = $manager;
     }
 
-    public function setDatabase(Database $database)
+    public function setDatabase(Database $database): void
     {
         $this->database = $database;
     }
 
-    public function getDatabase()
+    public function getDatabase(): Database
     {
         return $this->database;
     }
-- 
GitLab


From 0c1654c78f0d64cff10e04488bc50cdf79c230c4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Agneray?= <francois.agneray@lam.fr>
Date: Thu, 19 Dec 2019 11:45:33 +0100
Subject: [PATCH 13/31] DatasetFamilyListAction => done

---
 app/routes.php                               |   7 +-
 src/Action/DatasetFamilyListAction.php       |  87 +++++++++++++
 src/Entity/Dataset.php                       |   2 +-
 tests/Action/DatasetFamilyListActionTest.php | 127 +++++++++++++++++++
 4 files changed, 219 insertions(+), 4 deletions(-)
 create mode 100644 src/Action/DatasetFamilyListAction.php
 create mode 100644 tests/Action/DatasetFamilyListActionTest.php

diff --git a/app/routes.php b/app/routes.php
index 0056afa..14afa08 100644
--- a/app/routes.php
+++ b/app/routes.php
@@ -21,15 +21,16 @@ $app->map([OPTIONS, GET, PUT, DELETE], '/project/{name}', App\Action\ProjectActi
 $app->map([OPTIONS, GET, POST], '/instance', App\Action\InstanceListAction::class);
 $app->map([OPTIONS, GET, PUT, DELETE], '/instance/{name}', App\Action\InstanceAction::class);
 $app->map([OPTIONS, GET, POST], '/instance/{name}/dataset-family', App\Action\DatasetFamilyListAction::class);
+$app->map([OPTIONS, GET], '/instance/{name}/dataset', App\Action\InstanceAction::class);
 $app->map([OPTIONS, GET, PUT, DELETE], '/dataset-family/{id}', App\Action\DatasetFamilyAction::class);
 $app->map([OPTIONS, GET, POST], '/dataset-family/{id}/dataset', App\Action\DatasetListAction::class);
 $app->map([OPTIONS, GET, PUT, DELETE], '/dataset/{name}', App\Action\DatasetAction::class);
 $app->map([OPTIONS, GET, POST], '/dataset/{name}/criteria-family', App\Action\CriteriaFamilyListAction::class);
-$app->map([OPTIONS, GET, POST], '/dataset/{name}/output-family', App\Action\OutputFamilyListAction::class);
-$app->map([OPTIONS, GET], '/dataset/{name}/attribute', App\Action\AttributeListAction::class);
-$app->map([OPTIONS, GET, PUT], '/dataset/{name}/attribute/{id}', App\Action\AttributeAction::class);
 $app->map([OPTIONS, GET, PUT, DELETE], '/criteria-family/{id}', App\Action\CriteriaFamilyAction::class);
+$app->map([OPTIONS, GET, POST], '/dataset/{name}/output-family', App\Action\OutputFamilyListAction::class);
 $app->map([OPTIONS, GET, PUT, DELETE], '/output-family/{id}', App\Action\OutputFamilyAction::class);
 $app->map([OPTIONS, GET, POST], '/output-family/{id}/output-category', App\Action\OutputCategoryListAction::class);
 $app->map([OPTIONS, GET, PUT, DELETE], '/output-category/{id}', App\Action\OutputCategoryAction::class);
+$app->map([OPTIONS, GET], '/dataset/{name}/attribute', App\Action\AttributeListAction::class);
+$app->map([OPTIONS, GET, PUT], '/dataset/{name}/attribute/{id}', App\Action\AttributeAction::class);
 $app->get('/search/{dname}', App\Action\SearchAction::class);
diff --git a/src/Action/DatasetFamilyListAction.php b/src/Action/DatasetFamilyListAction.php
new file mode 100644
index 0000000..ec2a650
--- /dev/null
+++ b/src/Action/DatasetFamilyListAction.php
@@ -0,0 +1,87 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Action;
+
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Psr\Http\Message\ResponseInterface as Response;
+use Slim\Exception\HttpBadRequestException;
+use Slim\Exception\HttpNotFoundException;
+use App\Entity\Instance;
+use App\Entity\DatasetFamily;
+
+final class DatasetFamilyListAction extends AbstractAction
+{
+    /**
+     * `GET`  Returns a list of all dataset family for a given instance
+     *
+     * @param  ServerRequestInterface $request  PSR-7 This object represents the HTTP request
+     * @param  ResponseInterface      $response PSR-7 This object represents the HTTP response
+     * @param  string[]               $args     This table contains information transmitted in the URL (see routes.php)
+     *
+     * @return ResponseInterface
+     */
+    public function __invoke(Request $request, Response $response, array $args): Response
+    {
+        if ($request->getMethod() === OPTIONS) {
+            return $response->withHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
+        }
+
+        $instance = $this->em->find('App\Entity\Instance', $args['name']);
+
+        // Returns HTTP 404 if the dataset is not found
+        if (is_null($instance)) {
+            throw new HttpNotFoundException(
+                $request,
+                'Instance with name ' . $args['name'] . ' is not found'
+            );
+        }
+
+        if ($request->getMethod() === GET) {
+            $families = $this->em->getRepository('App\Entity\DatasetFamily')->findByInstance($instance);
+            $payload = json_encode($families);
+        }
+
+        if ($request->getMethod() === POST) {
+            $parsedBody = $request->getParsedBody();
+
+            // To work this action needs information
+            foreach (array('label', 'display') as $a) {
+                if ($this->isEmptyField($a, $parsedBody)) {
+                    throw new HttpBadRequestException(
+                        $request,
+                        'Param ' . $a . ' needed to add a new dataset family'
+                    );
+                }
+            }
+
+            $family = $this->postDatasetFamily($parsedBody, $instance);
+            $payload = json_encode($family);
+            $response = $response->withStatus(201);
+        }
+
+        $response->getBody()->write($payload);
+        return $response;
+    }
+
+    private function postDatasetFamily(array $parsedBody, Instance $instance): DatasetFamily
+    {
+        $family = new DatasetFamily($instance);
+        $family->setLabel($parsedBody['label']);
+        $family->setDisplay($parsedBody['display']);
+
+        $this->em->persist($family);
+        $this->em->flush();
+
+        return $family;
+    }
+}
diff --git a/src/Entity/Dataset.php b/src/Entity/Dataset.php
index 5d47433..ff6da07 100644
--- a/src/Entity/Dataset.php
+++ b/src/Entity/Dataset.php
@@ -96,7 +96,7 @@ class Dataset implements \JsonSerializable
      * @var Anis\Entity\Project
      *
      * @ManyToOne(targetEntity="DatasetFamily")
-     * @JoinColumn(name="id_dataset_family", referencedColumnName="id", nullable=true)
+     * @JoinColumn(name="id_dataset_family", referencedColumnName="id", nullable=false)
      */
     protected $datasetFamily;
     
diff --git a/tests/Action/DatasetFamilyListActionTest.php b/tests/Action/DatasetFamilyListActionTest.php
new file mode 100644
index 0000000..2a66c4b
--- /dev/null
+++ b/tests/Action/DatasetFamilyListActionTest.php
@@ -0,0 +1,127 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Tests\Action;
+
+use PHPUnit\Framework\TestCase;
+use Nyholm\Psr7\ServerRequest;
+use Nyholm\Psr7\Response;
+use Slim\Exception\HttpBadRequestException;
+use Slim\Exception\HttpNotFoundException;
+use App\tests\EntityManagerBuilder;
+use App\Entity\Instance;
+use App\Entity\DatasetFamily;
+
+final class DatasetFamilyListActionTest extends TestCase
+{
+    private $action;
+    private $entityManager;
+
+    protected function setUp(): void
+    {
+        $this->entityManager = EntityManagerBuilder::getInstance();
+        $this->action = new \App\Action\DatasetFamilyListAction($this->entityManager);
+    }
+
+    public function testOptionsHttpMethod(): void
+    {
+        $request = $this->getRequest('OPTIONS');
+        $response = ($this->action)($request, new Response(), array());
+        $this->assertSame($response->getHeaderLine('Access-Control-Allow-Methods'), 'GET, POST, OPTIONS');
+    }
+
+    public function testInstanceIsNotFound(): void
+    {
+        $this->expectException(HttpNotFoundException::class);
+        $this->expectExceptionMessage('Instance with name default is not found');
+        $request = $this->getRequest('GET');
+        $response = ($this->action)($request, new Response(), array('name' => 'default'));
+        $this->assertEquals(404, (int) $response->getStatusCode());
+    }
+
+    public function testGetAllDatasetFamiliesOfAnInstance(): void
+    {
+        $families = $this->addFamilies();
+        $request = $this->getRequest('GET');
+        $response = ($this->action)($request, new Response(), array('name' => 'default'));
+        $this->assertSame(
+            json_encode($families),
+            (string) $response->getBody()
+        );
+    }
+
+    public function testAddANewDatasetFamilyEmptyLabelField(): void
+    {
+        $this->addInstance();
+        $this->expectException(HttpBadRequestException::class);
+        $this->expectExceptionMessage('Param label needed to add a new dataset family');
+        $request = $this->getRequest('POST')->withParsedBody(array());
+        $response = ($this->action)($request, new Response(), array('name' => 'default'));
+        $this->assertEquals(400, (int) $response->getStatusCode());
+    }
+
+    public function testAddANewDatasetFamily(): void
+    {
+        $fields = array(
+            'label' => 'Default family',
+            'display' => 10
+        );
+        $this->addInstance();
+        $request = $this->getRequest('POST')->withParsedBody($fields);
+        $response = ($this->action)($request, new Response(), array('name' => 'default'));
+        $this->assertSame(
+            json_encode(array_merge(['id' => 1], $fields)),
+            (string) $response->getBody()
+        );
+        $this->assertEquals(201, (int) $response->getStatusCode());
+    }
+
+    protected function tearDown(): void
+    {
+        $this->entityManager->getConnection()->close();
+    }
+
+    private function getRequest(string $method): ServerRequest
+    {
+        return new ServerRequest($method, '/instance/default/dataset-family', array(
+            'Content-Type' => 'application/json'
+        ));
+    }
+
+    private function addInstance(): Instance
+    {
+        $instance = new Instance('default', 'Default instance');
+        $instance->setClientUrl('http://anis.lam.fr');
+        $this->entityManager->persist($instance);
+        $this->entityManager->flush();
+        return $instance;
+    }
+
+    private function addFamilies(): array
+    {
+        $instance = $this->addInstance();
+
+        $family = new DatasetFamily($instance);
+        $family->setLabel('Default dataset');
+        $family->setDisplay(10);
+        $this->entityManager->persist($family);
+
+        $family2 = new DatasetFamily($instance);
+        $family2->setLabel('Another family');
+        $family2->setDisplay(20);
+        $this->entityManager->persist($family2);
+
+        $this->entityManager->flush();
+
+        return array($family, $family2);
+    }
+}
-- 
GitLab


From a47d2e48e621b957ba17d446126d89649236792b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Agneray?= <francois.agneray@lam.fr>
Date: Thu, 19 Dec 2019 12:06:15 +0100
Subject: [PATCH 14/31] Add DatasetFamilyListAction into dependencies

---
 app/dependencies.php | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/app/dependencies.php b/app/dependencies.php
index 4df1ae2..1a357f3 100644
--- a/app/dependencies.php
+++ b/app/dependencies.php
@@ -91,6 +91,10 @@ $container->set('App\Action\InstanceAction', function (ContainerInterface $c) {
     return new App\Action\InstanceAction($c->get('em'));
 });
 
+$container->set('App\Action\DatasetFamilyListAction', function (ContainerInterface $c) {
+    return new App\Action\DatasetFamilyListAction($c->get('em'));
+});
+
 $container->set('App\Action\DatasetListAction', function (ContainerInterface $c) {
     return new App\Action\DatasetListAction($c->get('em'), new App\Utils\DBALConnectionFactory());
 });
-- 
GitLab


From cca62613c98bb630956d9ba3dc183ecdf48d2364 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Agneray?= <francois.agneray@lam.fr>
Date: Thu, 19 Dec 2019 12:36:17 +0100
Subject: [PATCH 15/31] DatasetListByInstanceAction => done

---
 app/dependencies.php                          |   4 +
 app/routes.php                                |   2 +-
 src/Action/DatasetFamilyListAction.php        |   1 +
 src/Action/DatasetListByInstanceAction.php    |  60 +++++++
 .../DatasetListByInstanceActionTest.php       | 155 ++++++++++++++++++
 5 files changed, 221 insertions(+), 1 deletion(-)
 create mode 100644 src/Action/DatasetListByInstanceAction.php
 create mode 100644 tests/Action/DatasetListByInstanceActionTest.php

diff --git a/app/dependencies.php b/app/dependencies.php
index 1a357f3..786e97d 100644
--- a/app/dependencies.php
+++ b/app/dependencies.php
@@ -95,6 +95,10 @@ $container->set('App\Action\DatasetFamilyListAction', function (ContainerInterfa
     return new App\Action\DatasetFamilyListAction($c->get('em'));
 });
 
+$container->set('App\Action\DatasetListByInstanceAction', function (ContainerInterface $c) {
+    return new App\Action\DatasetListByInstanceAction($c->get('em'));
+});
+
 $container->set('App\Action\DatasetListAction', function (ContainerInterface $c) {
     return new App\Action\DatasetListAction($c->get('em'), new App\Utils\DBALConnectionFactory());
 });
diff --git a/app/routes.php b/app/routes.php
index 14afa08..57a2bed 100644
--- a/app/routes.php
+++ b/app/routes.php
@@ -21,7 +21,7 @@ $app->map([OPTIONS, GET, PUT, DELETE], '/project/{name}', App\Action\ProjectActi
 $app->map([OPTIONS, GET, POST], '/instance', App\Action\InstanceListAction::class);
 $app->map([OPTIONS, GET, PUT, DELETE], '/instance/{name}', App\Action\InstanceAction::class);
 $app->map([OPTIONS, GET, POST], '/instance/{name}/dataset-family', App\Action\DatasetFamilyListAction::class);
-$app->map([OPTIONS, GET], '/instance/{name}/dataset', App\Action\InstanceAction::class);
+$app->map([OPTIONS, GET], '/instance/{name}/dataset', App\Action\DatasetListByInstanceAction::class);
 $app->map([OPTIONS, GET, PUT, DELETE], '/dataset-family/{id}', App\Action\DatasetFamilyAction::class);
 $app->map([OPTIONS, GET, POST], '/dataset-family/{id}/dataset', App\Action\DatasetListAction::class);
 $app->map([OPTIONS, GET, PUT, DELETE], '/dataset/{name}', App\Action\DatasetAction::class);
diff --git a/src/Action/DatasetFamilyListAction.php b/src/Action/DatasetFamilyListAction.php
index ec2a650..a35af6c 100644
--- a/src/Action/DatasetFamilyListAction.php
+++ b/src/Action/DatasetFamilyListAction.php
@@ -23,6 +23,7 @@ final class DatasetFamilyListAction extends AbstractAction
 {
     /**
      * `GET`  Returns a list of all dataset family for a given instance
+     * `POST` Add a new dataset family to a given instance
      *
      * @param  ServerRequestInterface $request  PSR-7 This object represents the HTTP request
      * @param  ResponseInterface      $response PSR-7 This object represents the HTTP response
diff --git a/src/Action/DatasetListByInstanceAction.php b/src/Action/DatasetListByInstanceAction.php
new file mode 100644
index 0000000..dc45baf
--- /dev/null
+++ b/src/Action/DatasetListByInstanceAction.php
@@ -0,0 +1,60 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Action;
+
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Psr\Http\Message\ResponseInterface as Response;
+use Slim\Exception\HttpNotFoundException;
+
+final class DatasetListByInstanceAction extends AbstractAction
+{
+    /**
+     * `GET`  Returns a list of all datasets for a given instance
+     *
+     * @param  ServerRequestInterface $request  PSR-7 This object represents the HTTP request
+     * @param  ResponseInterface      $response PSR-7 This object represents the HTTP response
+     * @param  string[]               $args     This table contains information transmitted in the URL (see routes.php)
+     *
+     * @return ResponseInterface
+     */
+    public function __invoke(Request $request, Response $response, array $args): Response
+    {
+        if ($request->getMethod() === OPTIONS) {
+            return $response->withHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
+        }
+
+        $instance = $this->em->find('App\Entity\Instance', $args['name']);
+
+        // Returns HTTP 404 if the dataset is not found
+        if (is_null($instance)) {
+            throw new HttpNotFoundException(
+                $request,
+                'Instance with name ' . $args['name'] . ' is not found'
+            );
+        }
+
+        if ($request->getMethod() === GET) {
+            $qb = $this->em->createQueryBuilder();
+            $qb->select('d')
+                ->from('App\Entity\Dataset', 'd')
+                ->join('d.datasetFamily', 'f')
+                ->where($qb->expr()->eq('IDENTITY(f.instance)', ':instanceName'))
+                ->setParameter('instanceName', $instance->getName());
+            $datasets = $qb->getQuery()->getResult();
+            $payload = json_encode($datasets);
+        }
+
+        $response->getBody()->write($payload);
+        return $response;
+    }
+}
diff --git a/tests/Action/DatasetListByInstanceActionTest.php b/tests/Action/DatasetListByInstanceActionTest.php
new file mode 100644
index 0000000..041da99
--- /dev/null
+++ b/tests/Action/DatasetListByInstanceActionTest.php
@@ -0,0 +1,155 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Tests\Action;
+
+use PHPUnit\Framework\TestCase;
+use Nyholm\Psr7\ServerRequest;
+use Nyholm\Psr7\Response;
+use Slim\Exception\HttpBadRequestException;
+use Slim\Exception\HttpNotFoundException;
+use App\tests\EntityManagerBuilder;
+use App\Entity\Database;
+use App\Entity\Project;
+use App\Entity\Instance;
+use App\Entity\DatasetFamily;
+use App\Entity\Dataset;
+
+final class DatasetListByInstanceActionTest extends TestCase
+{
+    private $action;
+    private $entityManager;
+
+    protected function setUp(): void
+    {
+        $this->entityManager = EntityManagerBuilder::getInstance();
+        $this->action = new \App\Action\DatasetListByInstanceAction($this->entityManager);
+    }
+
+    public function testOptionsHttpMethod(): void
+    {
+        $request = $this->getRequest('OPTIONS');
+        $response = ($this->action)($request, new Response(), array());
+        $this->assertSame($response->getHeaderLine('Access-Control-Allow-Methods'), 'GET, OPTIONS');
+    }
+
+    public function testInstanceIsNotFound(): void
+    {
+        $this->expectException(HttpNotFoundException::class);
+        $this->expectExceptionMessage('Instance with name default is not found');
+        $request = $this->getRequest('GET');
+        $response = ($this->action)($request, new Response(), array('name' => 'default'));
+        $this->assertEquals(404, (int) $response->getStatusCode());
+    }
+
+    public function testGetAllDatasetsForAnInstance(): void
+    {
+        $datasets = $this->addDatasets();
+        $request = $this->getRequest('GET');
+        $response = ($this->action)($request, new Response(), array('name' => 'default'));
+        $this->assertSame(
+            json_encode($datasets),
+            (string) $response->getBody()
+        );
+    }
+
+    protected function tearDown(): void
+    {
+        $this->entityManager->getConnection()->close();
+    }
+
+    private function getRequest(string $method): ServerRequest
+    {
+        return new ServerRequest($method, '/instance/default/dataset', array(
+            'Content-Type' => 'application/json'
+        ));
+    }
+
+    private function addInstance(): Instance
+    {
+        $instance = new Instance('default', 'Default instance');
+        $instance->setClientUrl('http://anis.lam.fr');
+        $this->entityManager->persist($instance);
+        $this->entityManager->flush();
+        return $instance;
+    }
+
+    private function addDatasetFamily(): DatasetFamily
+    {
+        $instance = $this->addInstance();
+        $family = new DatasetFamily($instance);
+        $family->setLabel('Default dataset family');
+        $family->setDisplay(10);
+        $this->entityManager->persist($family);
+
+        return $family;
+    }
+
+    private function addProject(): Project
+    {
+        $database = new Database();
+        $database->setLabel('Test1');
+        $database->setDbName('test1');
+        $database->setType('pgsql');
+        $database->setHost('db');
+        $database->setPort(5432);
+        $database->setLogin('test');
+        $database->setPassword('test');
+        $this->entityManager->persist($database);
+
+        $project = new Project('anis_project');
+        $project->setLabel('Test project');
+        $project->setDescription('Test description');
+        $project->setLink('http://test.com');
+        $project->setManager('User1');
+        $project->setDatabase($database);
+        $this->entityManager->persist($project);
+
+        return $project;
+    }
+
+    private function addDatasets(): array
+    {
+        $project = $this->addProject();
+        $family = $this->addDatasetFamily();
+
+        $dataset = new Dataset('obs_cat');
+        $dataset->setTableRef('v_obs_cat');
+        $dataset->setLabel('Obscat label');
+        $dataset->setDescription('Obscat description');
+        $dataset->setDisplay(10);
+        $dataset->setCount(10000);
+        $dataset->setVo(false);
+        $dataset->setDataPath('/mnt/obs_cat');
+        $dataset->setSelectableRow(false);
+        $dataset->setProject($project);
+        $dataset->setDatasetFamily($family);
+        $this->entityManager->persist($dataset);
+
+        $dataset2 = new Dataset('observations');
+        $dataset2->setTableRef('v_observations');
+        $dataset2->setLabel('Observations label');
+        $dataset2->setDescription('Observations description');
+        $dataset2->setDisplay(20);
+        $dataset2->setCount(5000);
+        $dataset2->setVo(false);
+        $dataset2->setDataPath('/mnt/observations');
+        $dataset2->setSelectableRow(false);
+        $dataset2->setProject($project);
+        $dataset2->setDatasetFamily($family);
+        $this->entityManager->persist($dataset2);
+
+        $this->entityManager->flush();
+
+        return array($dataset, $dataset2);
+    }
+}
-- 
GitLab


From 89dace2747cff9adeacb7ab047a4135bc40a1e30 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Agneray?= <francois.agneray@lam.fr>
Date: Thu, 19 Dec 2019 14:19:35 +0100
Subject: [PATCH 16/31] DatasetFamilyAction => done

---
 app/dependencies.php                     |   4 +
 src/Action/DatasetFamilyAction.php       |  80 +++++++++++++++
 tests/Action/DatasetFamilyActionTest.php | 124 +++++++++++++++++++++++
 3 files changed, 208 insertions(+)
 create mode 100644 src/Action/DatasetFamilyAction.php
 create mode 100644 tests/Action/DatasetFamilyActionTest.php

diff --git a/app/dependencies.php b/app/dependencies.php
index 786e97d..53a5bc3 100644
--- a/app/dependencies.php
+++ b/app/dependencies.php
@@ -99,6 +99,10 @@ $container->set('App\Action\DatasetListByInstanceAction', function (ContainerInt
     return new App\Action\DatasetListByInstanceAction($c->get('em'));
 });
 
+$container->set('App\Action\DatasetFamilyAction', function (ContainerInterface $c) {
+    return new App\Action\DatasetFamilyAction($c->get('em'));
+});
+
 $container->set('App\Action\DatasetListAction', function (ContainerInterface $c) {
     return new App\Action\DatasetListAction($c->get('em'), new App\Utils\DBALConnectionFactory());
 });
diff --git a/src/Action/DatasetFamilyAction.php b/src/Action/DatasetFamilyAction.php
new file mode 100644
index 0000000..c22f483
--- /dev/null
+++ b/src/Action/DatasetFamilyAction.php
@@ -0,0 +1,80 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Action;
+
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Psr\Http\Message\ResponseInterface as Response;
+use Slim\Exception\HttpBadRequestException;
+use Slim\Exception\HttpNotFoundException;
+use App\Entity\DatasetFamily;
+
+final class DatasetFamilyAction extends AbstractAction
+{
+    public function __invoke(Request $request, Response $response, array $args): Response
+    {
+        if ($request->getMethod() === OPTIONS) {
+            return $response->withHeader('Access-Control-Allow-Methods', 'GET, PUT, DELETE, OPTIONS');
+        }
+
+        // Search the correct dataset family with primary key
+        $datasetFamily = $this->em->find('App\Entity\DatasetFamily', $args['id']);
+
+        // If dataset family is not found 404
+        if (is_null($datasetFamily)) {
+            throw new HttpNotFoundException(
+                $request,
+                'Dataset family with id ' . $args['id'] . ' is not found'
+            );
+        }
+
+        if ($request->getMethod() === GET) {
+            $payload = json_encode($datasetFamily);
+        }
+
+        if ($request->getMethod() === PUT) {
+            $parsedBody = $request->getParsedBody();
+
+            $fields = array('label', 'display');
+            foreach ($fields as $a) {
+                if ($this->isEmptyField($a, $parsedBody)) {
+                    throw new HttpBadRequestException(
+                        $request,
+                        'Param ' . $a . ' needed to edit the dataset family'
+                    );
+                }
+            }
+
+            $this->editDatasetFamily($datasetFamily, $parsedBody);
+            $payload = json_encode($datasetFamily);
+        }
+
+        if ($request->getMethod() === DELETE) {
+            $id = $datasetFamily->getId();
+            $this->em->remove($datasetFamily);
+            $this->em->flush();
+            $payload = json_encode(array(
+                'message' => 'Dataset family with id ' . $id . ' is removed!'
+            ));
+        }
+
+        $response->getBody()->write($payload);
+        return $response;
+    }
+
+    private function editDatasetFamily(DatasetFamily $family, array $parsedBody): void
+    {
+        $family->setLabel($parsedBody['label']);
+        $family->setDisplay($parsedBody['display']);
+        $this->em->flush();
+    }
+}
diff --git a/tests/Action/DatasetFamilyActionTest.php b/tests/Action/DatasetFamilyActionTest.php
new file mode 100644
index 0000000..6968f4f
--- /dev/null
+++ b/tests/Action/DatasetFamilyActionTest.php
@@ -0,0 +1,124 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Tests\Action;
+
+use PHPUnit\Framework\TestCase;
+use Nyholm\Psr7\ServerRequest;
+use Nyholm\Psr7\Response;
+use Slim\Exception\HttpNotFoundException;
+use Slim\Exception\HttpBadRequestException;
+use App\tests\EntityManagerBuilder;
+use App\Entity\Instance;
+use App\Entity\DatasetFamily;
+
+final class DatasetFamilyActionTest extends TestCase
+{
+    private $action;
+    private $entityManager;
+
+    protected function setUp(): void
+    {
+        $this->entityManager = EntityManagerBuilder::getInstance();
+        $this->action = new \App\Action\DatasetFamilyAction($this->entityManager);
+    }
+
+    public function testOptionsHttpMethod(): void
+    {
+        $request = $this->getRequest('OPTIONS');
+        $response = ($this->action)($request, new Response(), array());
+        $this->assertSame($response->getHeaderLine('Access-Control-Allow-Methods'), 'GET, PUT, DELETE, OPTIONS');
+    }
+
+    public function testDatasetFamilyIsNotFound(): void
+    {
+        $this->expectException(HttpNotFoundException::class);
+        $this->expectExceptionMessage('Dataset family with id 1 is not found');
+        $request = $this->getRequest('GET');
+        $response = ($this->action)($request, new Response(), array('id' => 1));
+        $this->assertEquals(404, (int) $response->getStatusCode());
+    }
+
+    public function testGetADatasetFamilyById(): void
+    {
+        $datasetFamily = $this->addADatasetFamily();
+        $request = $this->getRequest('GET');
+        $response = ($this->action)($request, new Response(), array('id' => 1));
+        $this->assertSame(json_encode($datasetFamily), (string) $response->getBody());
+    }
+
+    public function testEditADatasetFamilyEmptyLabelField(): void
+    {
+        $this->addADatasetFamily();
+        $this->expectException(HttpBadRequestException::class);
+        $this->expectExceptionMessage('Param label needed to edit the dataset family');
+        $request = $this->getRequest('PUT')->withParsedBody(array());
+        $response = ($this->action)($request, new Response(), array('id' => 1));
+        $this->assertEquals(400, (int) $response->getStatusCode());
+    }
+
+    public function testEditADatasetFamily(): void
+    {
+        $fields = array(
+            'label' => 'Modfied family',
+            'display' => 20
+        );
+        $this->addADatasetFamily();
+        $request = $this->getRequest('PUT')->withParsedBody($fields);
+        $response = ($this->action)($request, new Response(), array('id' => 1));
+        $this->assertSame(json_encode(array_merge(['id' => 1], $fields)), (string) $response->getBody());
+    }
+
+    public function testDeleteADatasetFamily(): void
+    {
+        $this->addADatasetFamily();
+        $request = $this->getRequest('DELETE');
+        $response = ($this->action)($request, new Response(), array('id' => 1));
+        $this->assertSame(
+            json_encode(array('message' => 'Dataset family with id 1 is removed!')),
+            (string) $response->getBody()
+        );
+    }
+
+    protected function tearDown(): void
+    {
+        $this->entityManager->getConnection()->close();
+    }
+
+    private function getRequest(string $method): ServerRequest
+    {
+        return new ServerRequest($method, '/dataset-family/1', array(
+            'Content-Type' => 'application/json'
+        ));
+    }
+
+    private function addInstance(): Instance
+    {
+        $instance = new Instance('default', 'Default instance');
+        $instance->setClientUrl('http://anis.lam.fr');
+        $this->entityManager->persist($instance);
+        $this->entityManager->flush();
+        return $instance;
+    }
+
+    private function addADatasetFamily(): DatasetFamily
+    {
+        $instance = $this->addInstance();
+
+        $dataset = new DatasetFamily($instance);
+        $dataset->setLabel('Test1');
+        $dataset->setDisplay(10);
+        $this->entityManager->persist($dataset);
+        $this->entityManager->flush();
+        return $dataset;
+    }
+}
-- 
GitLab


From a6c164f21c3d6cddd9ceb80312cec9e86aafbb3f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Agneray?= <francois.agneray@lam.fr>
Date: Thu, 19 Dec 2019 14:51:33 +0100
Subject: [PATCH 17/31] DatasetListAction => done

---
 src/Action/DatasetListAction.php       | 33 +++++++---------------
 tests/Action/DatasetListActionTest.php | 39 ++++++++------------------
 2 files changed, 22 insertions(+), 50 deletions(-)

diff --git a/src/Action/DatasetListAction.php b/src/Action/DatasetListAction.php
index 5f5c7a6..4eb365d 100644
--- a/src/Action/DatasetListAction.php
+++ b/src/Action/DatasetListAction.php
@@ -18,9 +18,7 @@ use Slim\Exception\HttpBadRequestException;
 use Slim\Exception\HttpNotFoundException;
 use Doctrine\ORM\EntityManagerInterface;
 use App\Utils\DBALConnectionFactory;
-use App\Entity\Database;
 use App\Entity\Project;
-use App\Entity\Instance;
 use App\Entity\DatasetFamily;
 use App\Entity\Dataset;
 use App\Entity\Attribute;
@@ -42,7 +40,7 @@ final class DatasetListAction extends AbstractAction
     }
 
     /**
-     * `GET`  Returns a list of all datasets listed in the metamodel database
+     * `GET`  Returns a list of all datasets for a given dataset family
      * `POST` Add a new dataset
      *
      * @param  ServerRequestInterface $request  PSR-7 This object represents the HTTP request
@@ -57,18 +55,18 @@ final class DatasetListAction extends AbstractAction
             return $response->withHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
         }
 
-        $instance = $this->em->find('App\Entity\Instance', $args['name']);
+        $datasetFamily = $this->em->find('App\Entity\DatasetFamily', $args['id']);
 
-        // Returns HTTP 404 if the instance is not found
-        if (is_null($instance)) {
+        // Returns HTTP 404 if the dataset family is not found
+        if (is_null($datasetFamily)) {
             throw new HttpNotFoundException(
                 $request,
-                'Instance with name ' . $args['name'] . ' is not found'
+                'Dataset family with id ' . $args['id'] . ' is not found'
             );
         }
 
         if ($request->getMethod() === GET) {
-            $datasets = $this->em->getRepository('App\Entity\Dataset')->findByInstance($instance);
+            $datasets = $this->em->getRepository('App\Entity\Dataset')->findByDatasetFamily($datasetFamily);
             $payload = json_encode($datasets);
         }
 
@@ -86,8 +84,7 @@ final class DatasetListAction extends AbstractAction
                 'vo',
                 'data_path',
                 'selectable_row',
-                'project_name',
-                'id_dataset_family'
+                'project_name'
             );
             foreach ($fields as $a) {
                 if ($this->isEmptyField($a, $parsedBody)) {
@@ -107,15 +104,7 @@ final class DatasetListAction extends AbstractAction
                 );
             }
 
-            $family = $this->em->find('App\Entity\DatasetFamily', $parsedBody['id_dataset_family']);
-            if (is_null($family)) {
-                throw new HttpBadRequestException(
-                    $request,
-                    'Dataset family with id ' . $parsedBody['id_dataset_family'] . ' is not found'
-                );
-            }
-
-            $dataset = $this->postDataset($parsedBody, $project, $instance, $family);
+            $dataset = $this->postDataset($parsedBody, $project, $datasetFamily);
             $payload = json_encode($dataset);
             $response = $response->withStatus(201);
         }
@@ -136,8 +125,7 @@ final class DatasetListAction extends AbstractAction
     private function postDataset(
         array $parsedBody,
         Project $project,
-        Instance $instance,
-        DatasetFamily $family
+        DatasetFamily $datasetFamily
     ): Dataset {
         $dataset = new Dataset($parsedBody['name']);
         $dataset->setTableRef($parsedBody['table_ref']);
@@ -149,8 +137,7 @@ final class DatasetListAction extends AbstractAction
         $dataset->setDataPath($parsedBody['data_path']);
         $dataset->setSelectableRow($parsedBody['selectable_row']);
         $dataset->setProject($project);
-        $dataset->setInstance($instance);
-        $dataset->setDatasetFamily($family);
+        $dataset->setDatasetFamily($datasetFamily);
 
         $this->em->persist($dataset);
         $this->postAttributes($dataset);
diff --git a/tests/Action/DatasetListActionTest.php b/tests/Action/DatasetListActionTest.php
index 9be0dce..2d481b2 100644
--- a/tests/Action/DatasetListActionTest.php
+++ b/tests/Action/DatasetListActionTest.php
@@ -47,12 +47,12 @@ final class DatasetListActionTest extends TestCase
         $this->assertSame($response->getHeaderLine('Access-Control-Allow-Methods'), 'GET, POST, OPTIONS');
     }
 
-    public function testInstanceNotFound(): void
+    public function testDatasetFamilyNotFound(): void
     {
         $this->expectException(HttpNotFoundException::class);
-        $this->expectExceptionMessage('Instance with name aspic is not found');
+        $this->expectExceptionMessage('Dataset family with id 1 is not found');
         $request = $this->getRequest('GET');
-        $response = ($this->action)($request, new Response(), array('name' => 'aspic'));
+        $response = ($this->action)($request, new Response(), array('id' => 1));
         $this->assertEquals(404, (int) $response->getStatusCode());
     }
 
@@ -60,7 +60,7 @@ final class DatasetListActionTest extends TestCase
     {
         $datasets = $this->addDatasets();
         $request = $this->getRequest('GET');
-        $response = ($this->action)($request, new Response(), array('name' => 'aspic'));
+        $response = ($this->action)($request, new Response(), array('id' => 1));
         $this->assertSame(
             json_encode($datasets),
             (string) $response->getBody()
@@ -69,45 +69,32 @@ final class DatasetListActionTest extends TestCase
 
     public function testAddANewDatasetEmptyNameField(): void
     {
-        $this->addInstance();
+        $this->addDatasetFamily();
         $this->expectException(HttpBadRequestException::class);
         $this->expectExceptionMessage('Param name needed to add a new dataset');
         $request = $this->getRequest('POST')->withParsedBody(array());
-        $response = ($this->action)($request, new Response(), array('name' => 'aspic'));
+        $response = ($this->action)($request, new Response(), array('id' => 1));
         $this->assertEquals(400, (int) $response->getStatusCode());
     }
 
     public function testAddANewDatasetProjectNotFound(): void
     {
-        $this->addInstance();
+        $this->addDatasetFamily();
         $this->expectException(HttpBadRequestException::class);
         $this->expectExceptionMessage('Project with name anis_project is not found');
         $fields = $this->getNewDatasetFields();
         $request = $this->getRequest('POST')->withParsedBody($fields);
-        $response = ($this->action)($request, new Response(), array('name' => 'aspic'));
-        $this->assertEquals(400, (int) $response->getStatusCode());
-    }
-
-    public function testAddANewDatasetFamilyNotFound(): void
-    {
-        $this->addProject();
-        $this->addInstance();
-        $this->expectException(HttpBadRequestException::class);
-        $this->expectExceptionMessage('Dataset family with id 1 is not found');
-        $fields = $this->getNewDatasetFields();
-        $request = $this->getRequest('POST')->withParsedBody($fields);
-        $response = ($this->action)($request, new Response(), array('name' => 'aspic'));
+        $response = ($this->action)($request, new Response(), array('id' => 1));
         $this->assertEquals(400, (int) $response->getStatusCode());
     }
 
     public function testAddANewDataset(): void
     {
         $this->addProject();
-        $this->addInstance();
         $this->addDatasetFamily();
         $fields = $this->getNewDatasetFields();
         $request = $this->getRequest('POST')->withParsedBody($fields);
-        $response = ($this->action)($request, new Response(), array('name' => 'aspic'));
+        $response = ($this->action)($request, new Response(), array('id' => 1));
         $this->assertSame(
             json_encode($fields),
             (string) $response->getBody()
@@ -140,7 +127,6 @@ final class DatasetListActionTest extends TestCase
             'data_path' => '/mnt/dataset1',
             'selectable_row' => false,
             'project_name' => 'anis_project',
-            'instance_name' => 'aspic',
             'id_dataset_family' => 1
         );
     }
@@ -180,7 +166,9 @@ final class DatasetListActionTest extends TestCase
 
     private function addDatasetFamily(): DatasetFamily
     {
-        $family = new DatasetFamily();
+        $instance = $this->addInstance();
+
+        $family = new DatasetFamily($instance);
         $family->setLabel('Default dataset');
         $family->setDisplay(10);
         $this->entityManager->persist($family);
@@ -192,7 +180,6 @@ final class DatasetListActionTest extends TestCase
     private function addDatasets(): array
     {
         $project = $this->addProject();
-        $instance = $this->addInstance();
         $family = $this->addDatasetFamily();
 
         $dataset1 = new Dataset('dataset1');
@@ -205,7 +192,6 @@ final class DatasetListActionTest extends TestCase
         $dataset1->setDataPath('/mnt/dataset1');
         $dataset1->setSelectableRow(false);
         $dataset1->setProject($project);
-        $dataset1->setInstance($instance);
         $dataset1->setDatasetFamily($family);
         $this->entityManager->persist($dataset1);
 
@@ -219,7 +205,6 @@ final class DatasetListActionTest extends TestCase
         $dataset2->setDataPath('/mnt/dataset2');
         $dataset2->setSelectableRow(false);
         $dataset2->setProject($project);
-        $dataset2->setInstance($instance);
         $dataset2->setDatasetFamily($family);
         $this->entityManager->persist($dataset2);
 
-- 
GitLab


From e047459c8971dbad478497541866e258dab4c96c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Agneray?= <francois.agneray@lam.fr>
Date: Thu, 19 Dec 2019 14:56:31 +0100
Subject: [PATCH 18/31] DatasetAction => done

---
 tests/Action/DatasetActionTest.php | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/tests/Action/DatasetActionTest.php b/tests/Action/DatasetActionTest.php
index 15e3ec3..f8877bc 100644
--- a/tests/Action/DatasetActionTest.php
+++ b/tests/Action/DatasetActionTest.php
@@ -93,7 +93,7 @@ final class DatasetActionTest extends TestCase
                 array_merge(
                     ['name' => 'obs_cat', 'table_ref' => 'v_obs_cat'],
                     $fields,
-                    ['project_name' => 'anis_project', 'instance_name' => 'aspic', 'id_dataset_family' => 1]
+                    ['project_name' => 'anis_project', 'id_dataset_family' => 1]
                 ),
                 JSON_UNESCAPED_SLASHES
             ),
@@ -174,7 +174,9 @@ final class DatasetActionTest extends TestCase
 
     private function addDatasetFamily(): DatasetFamily
     {
-        $family = new DatasetFamily();
+        $instance = $this->addInstance();
+
+        $family = new DatasetFamily($instance);
         $family->setLabel('Default dataset');
         $family->setDisplay(10);
         $this->entityManager->persist($family);
@@ -186,7 +188,6 @@ final class DatasetActionTest extends TestCase
     private function addADataset(): Dataset
     {
         $project = $this->addProject();
-        $instance = $this->addInstance();
         $family = $this->addDatasetFamily();
 
         $dataset = new Dataset('obs_cat');
@@ -199,7 +200,6 @@ final class DatasetActionTest extends TestCase
         $dataset->setDataPath('/mnt/obs_cat');
         $dataset->setSelectableRow(false);
         $dataset->setProject($project);
-        $dataset->setInstance($instance);
         $dataset->setDatasetFamily($family);
         $this->entityManager->persist($dataset);
         $this->entityManager->flush();
-- 
GitLab


From 3bb91acc2176a27c2bf0f3a3f17a1ee2fba9fd6f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Agneray?= <francois.agneray@lam.fr>
Date: Thu, 19 Dec 2019 15:14:30 +0100
Subject: [PATCH 19/31] CriteriaFamilyListAction => done

---
 src/Action/CriteriaFamilyListAction.php       |  88 ++++++++
 tests/Action/CriteriaFamilyListActionTest.php | 189 ++++++++++++++++++
 2 files changed, 277 insertions(+)
 create mode 100644 src/Action/CriteriaFamilyListAction.php
 create mode 100644 tests/Action/CriteriaFamilyListActionTest.php

diff --git a/src/Action/CriteriaFamilyListAction.php b/src/Action/CriteriaFamilyListAction.php
new file mode 100644
index 0000000..f9ea236
--- /dev/null
+++ b/src/Action/CriteriaFamilyListAction.php
@@ -0,0 +1,88 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Action;
+
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Psr\Http\Message\ResponseInterface as Response;
+use Slim\Exception\HttpBadRequestException;
+use Slim\Exception\HttpNotFoundException;
+use App\Entity\Dataset;
+use App\Entity\CriteriaFamily;
+
+final class CriteriaFamilyListAction extends AbstractAction
+{
+    /**
+     * `GET`  Returns a list of all criteria family for a given dataset
+     * `POST` Add a new dataset criteria family to a given dataset
+     *
+     * @param  ServerRequestInterface $request  PSR-7 This object represents the HTTP request
+     * @param  ResponseInterface      $response PSR-7 This object represents the HTTP response
+     * @param  string[]               $args     This table contains information transmitted in the URL (see routes.php)
+     *
+     * @return ResponseInterface
+     */
+    public function __invoke(Request $request, Response $response, array $args): Response
+    {
+        if ($request->getMethod() === OPTIONS) {
+            return $response->withHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
+        }
+
+        $dataset = $this->em->find('App\Entity\Dataset', $args['name']);
+
+        // Returns HTTP 404 if the dataset is not found
+        if (is_null($dataset)) {
+            throw new HttpNotFoundException(
+                $request,
+                'Dataset with name ' . $args['name'] . ' is not found'
+            );
+        }
+
+        if ($request->getMethod() === GET) {
+            $families = $this->em->getRepository('App\Entity\CriteriaFamily')->findByDataset($dataset);
+            $payload = json_encode($families);
+        }
+
+        if ($request->getMethod() === POST) {
+            $parsedBody = $request->getParsedBody();
+
+            // To work this action needs information
+            foreach (array('label', 'display') as $a) {
+                if ($this->isEmptyField($a, $parsedBody)) {
+                    throw new HttpBadRequestException(
+                        $request,
+                        'Param ' . $a . ' needed to add a new criteria family'
+                    );
+                }
+            }
+
+            $family = $this->postCriteriaFamily($parsedBody, $dataset);
+            $payload = json_encode($family);
+            $response = $response->withStatus(201);
+        }
+
+        $response->getBody()->write($payload);
+        return $response;
+    }
+
+    private function postCriteriaFamily(array $parsedBody, Dataset $dataset): CriteriaFamily
+    {
+        $family = new CriteriaFamily($dataset);
+        $family->setLabel($parsedBody['label']);
+        $family->setDisplay($parsedBody['display']);
+
+        $this->em->persist($family);
+        $this->em->flush();
+
+        return $family;
+    }
+}
diff --git a/tests/Action/CriteriaFamilyListActionTest.php b/tests/Action/CriteriaFamilyListActionTest.php
new file mode 100644
index 0000000..d42ebf1
--- /dev/null
+++ b/tests/Action/CriteriaFamilyListActionTest.php
@@ -0,0 +1,189 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Tests\Action;
+
+use PHPUnit\Framework\TestCase;
+use Nyholm\Psr7\ServerRequest;
+use Nyholm\Psr7\Response;
+use Slim\Exception\HttpBadRequestException;
+use Slim\Exception\HttpNotFoundException;
+use App\tests\EntityManagerBuilder;
+use App\Entity\Database;
+use App\Entity\Project;
+use App\Entity\Instance;
+use App\Entity\DatasetFamily;
+use App\Entity\Dataset;
+use App\Entity\CriteriaFamily;
+
+final class CriteriaFamilyListActionTest extends TestCase
+{
+    private $action;
+    private $entityManager;
+
+    protected function setUp(): void
+    {
+        $this->entityManager = EntityManagerBuilder::getInstance();
+        $this->action = new \App\Action\CriteriaFamilyListAction($this->entityManager);
+    }
+
+    public function testOptionsHttpMethod(): void
+    {
+        $request = $this->getRequest('OPTIONS');
+        $response = ($this->action)($request, new Response(), array());
+        $this->assertSame($response->getHeaderLine('Access-Control-Allow-Methods'), 'GET, POST, OPTIONS');
+    }
+
+    public function testDatasetIsNotFound(): void
+    {
+        $this->expectException(HttpNotFoundException::class);
+        $this->expectExceptionMessage('Dataset with name obs_cat is not found');
+        $request = $this->getRequest('GET');
+        $response = ($this->action)($request, new Response(), array('name' => 'obs_cat'));
+        $this->assertEquals(404, (int) $response->getStatusCode());
+    }
+
+    public function testGetAllCriteriaFamiliesOfADataset(): void
+    {
+        $families = $this->addFamilies();
+        $request = $this->getRequest('GET');
+        $response = ($this->action)($request, new Response(), array('name' => 'obs_cat'));
+        $this->assertSame(
+            json_encode($families),
+            (string) $response->getBody()
+        );
+    }
+
+    public function testAddANewCriteriaFamilyEmptyLabelField(): void
+    {
+        $this->addADataset();
+        $this->expectException(HttpBadRequestException::class);
+        $this->expectExceptionMessage('Param label needed to add a new criteria family');
+        $request = $this->getRequest('POST')->withParsedBody(array());
+        $response = ($this->action)($request, new Response(), array('name' => 'obs_cat'));
+        $this->assertEquals(400, (int) $response->getStatusCode());
+    }
+
+    public function testAddANewCriteriaFamily(): void
+    {
+        $fields = array(
+            'label' => 'Default criteria family',
+            'display' => 10
+        );
+        $this->addADataset();
+        $request = $this->getRequest('POST')->withParsedBody($fields);
+        $response = ($this->action)($request, new Response(), array('name' => 'obs_cat'));
+        $this->assertSame(
+            json_encode(array_merge(['id' => 1], $fields)),
+            (string) $response->getBody()
+        );
+        $this->assertEquals(201, (int) $response->getStatusCode());
+    }
+
+    protected function tearDown(): void
+    {
+        $this->entityManager->getConnection()->close();
+    }
+
+    private function getRequest(string $method): ServerRequest
+    {
+        return new ServerRequest($method, '/dataset/obs_cat/criteria-family', array(
+            'Content-Type' => 'application/json'
+        ));
+    }
+
+    private function addFamilies(): array
+    {
+        $dataset = $this->addADataset();
+
+        $family = new CriteriaFamily($dataset);
+        $family->setLabel('Default dataset');
+        $family->setDisplay(10);
+        $this->entityManager->persist($family);
+
+        $family2 = new CriteriaFamily($dataset);
+        $family2->setLabel('Another family');
+        $family2->setDisplay(20);
+        $this->entityManager->persist($family2);
+
+        $this->entityManager->flush();
+
+        return array($family, $family2);
+    }
+
+    private function addProject(): Project
+    {
+        $database = new Database();
+        $database->setLabel('Test1');
+        $database->setDbName('test1');
+        $database->setType('pgsql');
+        $database->setHost('db');
+        $database->setPort(5432);
+        $database->setLogin('test');
+        $database->setPassword('test');
+        $this->entityManager->persist($database);
+
+        $project = new Project('anis_project');
+        $project->setLabel('Test project');
+        $project->setDescription('Test description');
+        $project->setLink('http://test.com');
+        $project->setManager('User1');
+        $project->setDatabase($database);
+        $this->entityManager->persist($project);
+
+        $this->entityManager->flush();
+        return $project;
+    }
+
+    private function addInstance(): Instance
+    {
+        $instance = new Instance('aspic', 'Aspic');
+        $instance->setClientUrl('http://cesam.lam.fr/aspic');
+        $this->entityManager->persist($instance);
+        $this->entityManager->flush();
+        return $instance;
+    }
+
+    private function addDatasetFamily(): DatasetFamily
+    {
+        $instance = $this->addInstance();
+
+        $family = new DatasetFamily($instance);
+        $family->setLabel('Default dataset');
+        $family->setDisplay(10);
+        $this->entityManager->persist($family);
+
+        $this->entityManager->flush();
+        return $family;
+    }
+
+    private function addADataset(): Dataset
+    {
+        $project = $this->addProject();
+        $family = $this->addDatasetFamily();
+
+        $dataset = new Dataset('obs_cat');
+        $dataset->setTableRef('v_obs_cat');
+        $dataset->setLabel('Obscat label');
+        $dataset->setDescription('Obscat description');
+        $dataset->setDisplay(10);
+        $dataset->setCount(10000);
+        $dataset->setVo(false);
+        $dataset->setDataPath('/mnt/obs_cat');
+        $dataset->setSelectableRow(false);
+        $dataset->setProject($project);
+        $dataset->setDatasetFamily($family);
+        $this->entityManager->persist($dataset);
+        $this->entityManager->flush();
+        return $dataset;
+    }
+}
-- 
GitLab


From 4841581471bf9fd6bfdb0f586c394e358eb31c45 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Agneray?= <francois.agneray@lam.fr>
Date: Thu, 19 Dec 2019 15:24:07 +0100
Subject: [PATCH 20/31] CriteriaFamilyAction => done

---
 app/dependencies.php                      |   8 +
 src/Action/CriteriaFamilyAction.php       |  80 +++++++++
 tests/Action/CriteriaFamilyActionTest.php | 188 ++++++++++++++++++++++
 3 files changed, 276 insertions(+)
 create mode 100644 src/Action/CriteriaFamilyAction.php
 create mode 100644 tests/Action/CriteriaFamilyActionTest.php

diff --git a/app/dependencies.php b/app/dependencies.php
index 53a5bc3..37c47ad 100644
--- a/app/dependencies.php
+++ b/app/dependencies.php
@@ -111,6 +111,14 @@ $container->set('App\Action\DatasetAction', function (ContainerInterface $c) {
     return new App\Action\DatasetAction($c->get('em'), new App\Utils\DBALConnectionFactory());
 });
 
+$container->set('App\Action\CriteriaFamilyListAction', function (ContainerInterface $c) {
+    return new App\Action\CriteriaFamilyListAction($c->get('em'), new App\Utils\DBALConnectionFactory());
+});
+
+$container->set('App\Action\CriteriaFamilyAction', function (ContainerInterface $c) {
+    return new App\Action\CriteriaFamilyAction($c->get('em'), new App\Utils\DBALConnectionFactory());
+});
+
 $container->set('App\Action\AttributeListAction', function (ContainerInterface $c) {
     return new App\Action\AttributeListAction($c->get('em'));
 });
diff --git a/src/Action/CriteriaFamilyAction.php b/src/Action/CriteriaFamilyAction.php
new file mode 100644
index 0000000..c10c088
--- /dev/null
+++ b/src/Action/CriteriaFamilyAction.php
@@ -0,0 +1,80 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Action;
+
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Psr\Http\Message\ResponseInterface as Response;
+use Slim\Exception\HttpBadRequestException;
+use Slim\Exception\HttpNotFoundException;
+use App\Entity\CriteriaFamily;
+
+final class CriteriaFamilyAction extends AbstractAction
+{
+    public function __invoke(Request $request, Response $response, array $args): Response
+    {
+        if ($request->getMethod() === OPTIONS) {
+            return $response->withHeader('Access-Control-Allow-Methods', 'GET, PUT, DELETE, OPTIONS');
+        }
+
+        // Search the correct criteria family with primary key
+        $criteriaFamily = $this->em->find('App\Entity\CriteriaFamily', $args['id']);
+
+        // If criteria family is not found 404
+        if (is_null($criteriaFamily)) {
+            throw new HttpNotFoundException(
+                $request,
+                'Criteria family with id ' . $args['id'] . ' is not found'
+            );
+        }
+
+        if ($request->getMethod() === GET) {
+            $payload = json_encode($criteriaFamily);
+        }
+
+        if ($request->getMethod() === PUT) {
+            $parsedBody = $request->getParsedBody();
+
+            $fields = array('label', 'display');
+            foreach ($fields as $a) {
+                if ($this->isEmptyField($a, $parsedBody)) {
+                    throw new HttpBadRequestException(
+                        $request,
+                        'Param ' . $a . ' needed to edit the criteria family'
+                    );
+                }
+            }
+
+            $this->editCriteriaFamily($criteriaFamily, $parsedBody);
+            $payload = json_encode($criteriaFamily);
+        }
+
+        if ($request->getMethod() === DELETE) {
+            $id = $criteriaFamily->getId();
+            $this->em->remove($criteriaFamily);
+            $this->em->flush();
+            $payload = json_encode(array(
+                'message' => 'Criteria family with id ' . $id . ' is removed!'
+            ));
+        }
+
+        $response->getBody()->write($payload);
+        return $response;
+    }
+
+    private function editCriteriaFamily(CriteriaFamily $family, array $parsedBody): void
+    {
+        $family->setLabel($parsedBody['label']);
+        $family->setDisplay($parsedBody['display']);
+        $this->em->flush();
+    }
+}
diff --git a/tests/Action/CriteriaFamilyActionTest.php b/tests/Action/CriteriaFamilyActionTest.php
new file mode 100644
index 0000000..0575261
--- /dev/null
+++ b/tests/Action/CriteriaFamilyActionTest.php
@@ -0,0 +1,188 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Tests\Action;
+
+use PHPUnit\Framework\TestCase;
+use Nyholm\Psr7\ServerRequest;
+use Nyholm\Psr7\Response;
+use Slim\Exception\HttpNotFoundException;
+use Slim\Exception\HttpBadRequestException;
+use App\tests\EntityManagerBuilder;
+use App\Entity\Database;
+use App\Entity\Project;
+use App\Entity\Instance;
+use App\Entity\DatasetFamily;
+use App\Entity\Dataset;
+use App\Entity\CriteriaFamily;
+
+final class CriteriaFamilyActionTest extends TestCase
+{
+    private $action;
+    private $entityManager;
+
+    protected function setUp(): void
+    {
+        $this->entityManager = EntityManagerBuilder::getInstance();
+        $this->action = new \App\Action\CriteriaFamilyAction($this->entityManager);
+    }
+
+    public function testOptionsHttpMethod(): void
+    {
+        $request = $this->getRequest('OPTIONS');
+        $response = ($this->action)($request, new Response(), array());
+        $this->assertSame($response->getHeaderLine('Access-Control-Allow-Methods'), 'GET, PUT, DELETE, OPTIONS');
+    }
+
+    public function testCriteriaFamilyIsNotFound(): void
+    {
+        $this->expectException(HttpNotFoundException::class);
+        $this->expectExceptionMessage('Criteria family with id 1 is not found');
+        $request = $this->getRequest('GET');
+        $response = ($this->action)($request, new Response(), array('id' => 1));
+        $this->assertEquals(404, (int) $response->getStatusCode());
+    }
+
+    public function testGetACriteriaFamilyById(): void
+    {
+        $criteriaFamily = $this->addACriteriaFamily();
+        $request = $this->getRequest('GET');
+        $response = ($this->action)($request, new Response(), array('id' => 1));
+        $this->assertSame(json_encode($criteriaFamily), (string) $response->getBody());
+    }
+
+    public function testEditACriteriaFamilyEmptyLabelField(): void
+    {
+        $this->addACriteriaFamily();
+        $this->expectException(HttpBadRequestException::class);
+        $this->expectExceptionMessage('Param label needed to edit the criteria family');
+        $request = $this->getRequest('PUT')->withParsedBody(array());
+        $response = ($this->action)($request, new Response(), array('id' => 1));
+        $this->assertEquals(400, (int) $response->getStatusCode());
+    }
+
+    public function testEditACriteriaFamily(): void
+    {
+        $fields = array(
+            'label' => 'Modfied family',
+            'display' => 20
+        );
+        $this->addACriteriaFamily();
+        $request = $this->getRequest('PUT')->withParsedBody($fields);
+        $response = ($this->action)($request, new Response(), array('id' => 1));
+        $this->assertSame(json_encode(array_merge(['id' => 1], $fields)), (string) $response->getBody());
+    }
+
+    public function testDeleteACriteriaFamily(): void
+    {
+        $this->addACriteriaFamily();
+        $request = $this->getRequest('DELETE');
+        $response = ($this->action)($request, new Response(), array('id' => 1));
+        $this->assertSame(
+            json_encode(array('message' => 'Criteria family with id 1 is removed!')),
+            (string) $response->getBody()
+        );
+    }
+
+    protected function tearDown(): void
+    {
+        $this->entityManager->getConnection()->close();
+    }
+
+    private function getRequest(string $method): ServerRequest
+    {
+        return new ServerRequest($method, '/criteria-family/1', array(
+            'Content-Type' => 'application/json'
+        ));
+    }
+
+    private function addACriteriaFamily(): CriteriaFamily
+    {
+        $dataset = $this->addADataset();
+
+        $family = new CriteriaFamily($dataset);
+        $family->setLabel('Default criteria');
+        $family->setDisplay(10);
+        $this->entityManager->persist($family);
+
+        $this->entityManager->flush();
+
+        return $family;
+    }
+
+    private function addProject(): Project
+    {
+        $database = new Database();
+        $database->setLabel('Test1');
+        $database->setDbName('test1');
+        $database->setType('pgsql');
+        $database->setHost('db');
+        $database->setPort(5432);
+        $database->setLogin('test');
+        $database->setPassword('test');
+        $this->entityManager->persist($database);
+
+        $project = new Project('anis_project');
+        $project->setLabel('Test project');
+        $project->setDescription('Test description');
+        $project->setLink('http://test.com');
+        $project->setManager('User1');
+        $project->setDatabase($database);
+        $this->entityManager->persist($project);
+
+        $this->entityManager->flush();
+        return $project;
+    }
+
+    private function addInstance(): Instance
+    {
+        $instance = new Instance('aspic', 'Aspic');
+        $instance->setClientUrl('http://cesam.lam.fr/aspic');
+        $this->entityManager->persist($instance);
+        $this->entityManager->flush();
+        return $instance;
+    }
+
+    private function addDatasetFamily(): DatasetFamily
+    {
+        $instance = $this->addInstance();
+
+        $family = new DatasetFamily($instance);
+        $family->setLabel('Default dataset');
+        $family->setDisplay(10);
+        $this->entityManager->persist($family);
+
+        $this->entityManager->flush();
+        return $family;
+    }
+
+    private function addADataset(): Dataset
+    {
+        $project = $this->addProject();
+        $family = $this->addDatasetFamily();
+
+        $dataset = new Dataset('obs_cat');
+        $dataset->setTableRef('v_obs_cat');
+        $dataset->setLabel('Obscat label');
+        $dataset->setDescription('Obscat description');
+        $dataset->setDisplay(10);
+        $dataset->setCount(10000);
+        $dataset->setVo(false);
+        $dataset->setDataPath('/mnt/obs_cat');
+        $dataset->setSelectableRow(false);
+        $dataset->setProject($project);
+        $dataset->setDatasetFamily($family);
+        $this->entityManager->persist($dataset);
+        $this->entityManager->flush();
+        return $dataset;
+    }
+}
-- 
GitLab


From 824612460f1eb990d8866623780c1158237646c7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Agneray?= <francois.agneray@lam.fr>
Date: Thu, 19 Dec 2019 15:30:39 +0100
Subject: [PATCH 21/31] OutputFamilyListAction => done

---
 app/dependencies.php                        |   4 +
 src/Action/OutputFamilyListAction.php       |  88 +++++++++
 src/Entity/OutputFamily.php                 |   3 +-
 tests/Action/OutputFamilyListActionTest.php | 189 ++++++++++++++++++++
 4 files changed, 282 insertions(+), 2 deletions(-)
 create mode 100644 src/Action/OutputFamilyListAction.php
 create mode 100644 tests/Action/OutputFamilyListActionTest.php

diff --git a/app/dependencies.php b/app/dependencies.php
index 37c47ad..7f3f4ac 100644
--- a/app/dependencies.php
+++ b/app/dependencies.php
@@ -119,6 +119,10 @@ $container->set('App\Action\CriteriaFamilyAction', function (ContainerInterface
     return new App\Action\CriteriaFamilyAction($c->get('em'), new App\Utils\DBALConnectionFactory());
 });
 
+$container->set('App\Action\OutputFamilyListAction', function (ContainerInterface $c) {
+    return new App\Action\OutputFamilyListAction($c->get('em'), new App\Utils\DBALConnectionFactory());
+});
+
 $container->set('App\Action\AttributeListAction', function (ContainerInterface $c) {
     return new App\Action\AttributeListAction($c->get('em'));
 });
diff --git a/src/Action/OutputFamilyListAction.php b/src/Action/OutputFamilyListAction.php
new file mode 100644
index 0000000..16a4e82
--- /dev/null
+++ b/src/Action/OutputFamilyListAction.php
@@ -0,0 +1,88 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Action;
+
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Psr\Http\Message\ResponseInterface as Response;
+use Slim\Exception\HttpBadRequestException;
+use Slim\Exception\HttpNotFoundException;
+use App\Entity\Dataset;
+use App\Entity\OutputFamily;
+
+final class OutputFamilyListAction extends AbstractAction
+{
+    /**
+     * `GET`  Returns a list of all output family for a given dataset
+     * `POST` Add a new dataset output family to a given dataset
+     *
+     * @param  ServerRequestInterface $request  PSR-7 This object represents the HTTP request
+     * @param  ResponseInterface      $response PSR-7 This object represents the HTTP response
+     * @param  string[]               $args     This table contains information transmitted in the URL (see routes.php)
+     *
+     * @return ResponseInterface
+     */
+    public function __invoke(Request $request, Response $response, array $args): Response
+    {
+        if ($request->getMethod() === OPTIONS) {
+            return $response->withHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
+        }
+
+        $dataset = $this->em->find('App\Entity\Dataset', $args['name']);
+
+        // Returns HTTP 404 if the dataset is not found
+        if (is_null($dataset)) {
+            throw new HttpNotFoundException(
+                $request,
+                'Dataset with name ' . $args['name'] . ' is not found'
+            );
+        }
+
+        if ($request->getMethod() === GET) {
+            $families = $this->em->getRepository('App\Entity\OutputFamily')->findByDataset($dataset);
+            $payload = json_encode($families);
+        }
+
+        if ($request->getMethod() === POST) {
+            $parsedBody = $request->getParsedBody();
+
+            // To work this action needs information
+            foreach (array('label', 'display') as $a) {
+                if ($this->isEmptyField($a, $parsedBody)) {
+                    throw new HttpBadRequestException(
+                        $request,
+                        'Param ' . $a . ' needed to add a new output family'
+                    );
+                }
+            }
+
+            $family = $this->postOutputFamily($parsedBody, $dataset);
+            $payload = json_encode($family);
+            $response = $response->withStatus(201);
+        }
+
+        $response->getBody()->write($payload);
+        return $response;
+    }
+
+    private function postOutputFamily(array $parsedBody, Dataset $dataset): OutputFamily
+    {
+        $family = new OutputFamily($dataset);
+        $family->setLabel($parsedBody['label']);
+        $family->setDisplay($parsedBody['display']);
+
+        $this->em->persist($family);
+        $this->em->flush();
+
+        return $family;
+    }
+}
diff --git a/src/Entity/OutputFamily.php b/src/Entity/OutputFamily.php
index 6f54613..e09e809 100644
--- a/src/Entity/OutputFamily.php
+++ b/src/Entity/OutputFamily.php
@@ -84,8 +84,7 @@ class OutputFamily implements \JsonSerializable
         return [
             'id' => $this->getId(),
             'label' => $this->getLabel(),
-            'display' => $this->getDisplay(),
-            'type' => 'output'
+            'display' => $this->getDisplay()
         ];
     }
 }
diff --git a/tests/Action/OutputFamilyListActionTest.php b/tests/Action/OutputFamilyListActionTest.php
new file mode 100644
index 0000000..e41648a
--- /dev/null
+++ b/tests/Action/OutputFamilyListActionTest.php
@@ -0,0 +1,189 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Tests\Action;
+
+use PHPUnit\Framework\TestCase;
+use Nyholm\Psr7\ServerRequest;
+use Nyholm\Psr7\Response;
+use Slim\Exception\HttpBadRequestException;
+use Slim\Exception\HttpNotFoundException;
+use App\tests\EntityManagerBuilder;
+use App\Entity\Database;
+use App\Entity\Project;
+use App\Entity\Instance;
+use App\Entity\DatasetFamily;
+use App\Entity\Dataset;
+use App\Entity\OutputFamily;
+
+final class OutputFamilyListActionTest extends TestCase
+{
+    private $action;
+    private $entityManager;
+
+    protected function setUp(): void
+    {
+        $this->entityManager = EntityManagerBuilder::getInstance();
+        $this->action = new \App\Action\OutputFamilyListAction($this->entityManager);
+    }
+
+    public function testOptionsHttpMethod(): void
+    {
+        $request = $this->getRequest('OPTIONS');
+        $response = ($this->action)($request, new Response(), array());
+        $this->assertSame($response->getHeaderLine('Access-Control-Allow-Methods'), 'GET, POST, OPTIONS');
+    }
+
+    public function testDatasetIsNotFound(): void
+    {
+        $this->expectException(HttpNotFoundException::class);
+        $this->expectExceptionMessage('Dataset with name obs_cat is not found');
+        $request = $this->getRequest('GET');
+        $response = ($this->action)($request, new Response(), array('name' => 'obs_cat'));
+        $this->assertEquals(404, (int) $response->getStatusCode());
+    }
+
+    public function testGetAllOutputFamiliesOfADataset(): void
+    {
+        $families = $this->addFamilies();
+        $request = $this->getRequest('GET');
+        $response = ($this->action)($request, new Response(), array('name' => 'obs_cat'));
+        $this->assertSame(
+            json_encode($families),
+            (string) $response->getBody()
+        );
+    }
+
+    public function testAddANewOutputFamilyEmptyLabelField(): void
+    {
+        $this->addADataset();
+        $this->expectException(HttpBadRequestException::class);
+        $this->expectExceptionMessage('Param label needed to add a new output family');
+        $request = $this->getRequest('POST')->withParsedBody(array());
+        $response = ($this->action)($request, new Response(), array('name' => 'obs_cat'));
+        $this->assertEquals(400, (int) $response->getStatusCode());
+    }
+
+    public function testAddANewOutputFamily(): void
+    {
+        $fields = array(
+            'label' => 'Default output family',
+            'display' => 10
+        );
+        $this->addADataset();
+        $request = $this->getRequest('POST')->withParsedBody($fields);
+        $response = ($this->action)($request, new Response(), array('name' => 'obs_cat'));
+        $this->assertSame(
+            json_encode(array_merge(['id' => 1], $fields)),
+            (string) $response->getBody()
+        );
+        $this->assertEquals(201, (int) $response->getStatusCode());
+    }
+
+    protected function tearDown(): void
+    {
+        $this->entityManager->getConnection()->close();
+    }
+
+    private function getRequest(string $method): ServerRequest
+    {
+        return new ServerRequest($method, '/dataset/obs_cat/output-family', array(
+            'Content-Type' => 'application/json'
+        ));
+    }
+
+    private function addFamilies(): array
+    {
+        $dataset = $this->addADataset();
+
+        $family = new OutputFamily($dataset);
+        $family->setLabel('Default output');
+        $family->setDisplay(10);
+        $this->entityManager->persist($family);
+
+        $family2 = new OutputFamily($dataset);
+        $family2->setLabel('Another family');
+        $family2->setDisplay(20);
+        $this->entityManager->persist($family2);
+
+        $this->entityManager->flush();
+
+        return array($family, $family2);
+    }
+
+    private function addProject(): Project
+    {
+        $database = new Database();
+        $database->setLabel('Test1');
+        $database->setDbName('test1');
+        $database->setType('pgsql');
+        $database->setHost('db');
+        $database->setPort(5432);
+        $database->setLogin('test');
+        $database->setPassword('test');
+        $this->entityManager->persist($database);
+
+        $project = new Project('anis_project');
+        $project->setLabel('Test project');
+        $project->setDescription('Test description');
+        $project->setLink('http://test.com');
+        $project->setManager('User1');
+        $project->setDatabase($database);
+        $this->entityManager->persist($project);
+
+        $this->entityManager->flush();
+        return $project;
+    }
+
+    private function addInstance(): Instance
+    {
+        $instance = new Instance('aspic', 'Aspic');
+        $instance->setClientUrl('http://cesam.lam.fr/aspic');
+        $this->entityManager->persist($instance);
+        $this->entityManager->flush();
+        return $instance;
+    }
+
+    private function addDatasetFamily(): DatasetFamily
+    {
+        $instance = $this->addInstance();
+
+        $family = new DatasetFamily($instance);
+        $family->setLabel('Default dataset');
+        $family->setDisplay(10);
+        $this->entityManager->persist($family);
+
+        $this->entityManager->flush();
+        return $family;
+    }
+
+    private function addADataset(): Dataset
+    {
+        $project = $this->addProject();
+        $family = $this->addDatasetFamily();
+
+        $dataset = new Dataset('obs_cat');
+        $dataset->setTableRef('v_obs_cat');
+        $dataset->setLabel('Obscat label');
+        $dataset->setDescription('Obscat description');
+        $dataset->setDisplay(10);
+        $dataset->setCount(10000);
+        $dataset->setVo(false);
+        $dataset->setDataPath('/mnt/obs_cat');
+        $dataset->setSelectableRow(false);
+        $dataset->setProject($project);
+        $dataset->setDatasetFamily($family);
+        $this->entityManager->persist($dataset);
+        $this->entityManager->flush();
+        return $dataset;
+    }
+}
-- 
GitLab


From e0967e192d46522d05f715d205938fc5a6623d31 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Agneray?= <francois.agneray@lam.fr>
Date: Thu, 19 Dec 2019 15:37:17 +0100
Subject: [PATCH 22/31] AttributeListAction => done

---
 src/Entity/Attribute.php                 | 44 ++++++++++++------------
 tests/Action/AttributeListActionTest.php |  6 ++--
 2 files changed, 25 insertions(+), 25 deletions(-)

diff --git a/src/Entity/Attribute.php b/src/Entity/Attribute.php
index 4bcfa35..3ca9f4f 100644
--- a/src/Entity/Attribute.php
+++ b/src/Entity/Attribute.php
@@ -314,7 +314,7 @@ class Attribute implements \JsonSerializable
         $this->formLabel = $formLabel;
     }
 
-    public function getDescription(): string
+    public function getDescription()
     {
         return $this->description;
     }
@@ -344,7 +344,7 @@ class Attribute implements \JsonSerializable
         return $this->criteriaDisplay;
     }
 
-    public function getSearchFlag(): string
+    public function getSearchFlag()
     {
         return $this->searchFlag;
     }
@@ -354,7 +354,7 @@ class Attribute implements \JsonSerializable
         $this->searchFlag = $searchFlag;
     }
 
-    public function getSearchType(): string
+    public function getSearchType()
     {
         return $this->searchType;
     }
@@ -364,7 +364,7 @@ class Attribute implements \JsonSerializable
         $this->searchType = $searchType;
     }
 
-    public function getOperator(): string
+    public function getOperator()
     {
         return $this->operator;
     }
@@ -384,7 +384,7 @@ class Attribute implements \JsonSerializable
         $this->type = $type;
     }
 
-    public function getMin(): string
+    public function getMin()
     {
         return $this->min;
     }
@@ -394,7 +394,7 @@ class Attribute implements \JsonSerializable
         $this->min = $min;
     }
 
-    public function getMax(): string
+    public function getMax()
     {
         return $this->max;
     }
@@ -404,7 +404,7 @@ class Attribute implements \JsonSerializable
         $this->max = $max;
     }
 
-    public function getPlaceholderMin(): string
+    public function getPlaceholderMin()
     {
         return $this->placeholderMin;
     }
@@ -414,7 +414,7 @@ class Attribute implements \JsonSerializable
         $this->placeholderMin = $placeholderMin;
     }
 
-    public function getPlaceholderMax(): string
+    public function getPlaceholderMax()
     {
         return $this->placeholderMax;
     }
@@ -424,7 +424,7 @@ class Attribute implements \JsonSerializable
         $this->placeholderMax = $placeholderMax;
     }
 
-    public function getUriAction(): string
+    public function getUriAction()
     {
         return $this->uriAction;
     }
@@ -434,7 +434,7 @@ class Attribute implements \JsonSerializable
         $this->uriAction = $uriAction;
     }
 
-    public function getRenderer(): string
+    public function getRenderer()
     {
         return $this->renderer;
     }
@@ -454,7 +454,7 @@ class Attribute implements \JsonSerializable
         $this->displayDetail = $displayDetail;
     }
 
-    public function getVoUtype(): string
+    public function getVoUtype()
     {
         return $this->voUtype;
     }
@@ -464,7 +464,7 @@ class Attribute implements \JsonSerializable
         $this->voUtype = $voUtype;
     }
 
-    public function getVoUcd(): string
+    public function getVoUcd()
     {
         return $this->voUcd;
     }
@@ -474,7 +474,7 @@ class Attribute implements \JsonSerializable
         $this->voUcd = $voUcd;
     }
 
-    public function getVoUnit(): string
+    public function getVoUnit()
     {
         return $this->voUnit;
     }
@@ -484,7 +484,7 @@ class Attribute implements \JsonSerializable
         $this->voUnit = $voUnit;
     }
 
-    public function getVoDescription(): string
+    public function getVoDescription()
     {
         return $this->voDescription;
     }
@@ -494,7 +494,7 @@ class Attribute implements \JsonSerializable
         $this->voDescription = $voDescription;
     }
 
-    public function getVoDatatype(): string
+    public function getVoDatatype()
     {
         return $this->voDatatype;
     }
@@ -504,7 +504,7 @@ class Attribute implements \JsonSerializable
         $this->voDatatype = $voDatatype;
     }
 
-    public function getVoSize(): int
+    public function getVoSize()
     {
         return $this->voSize;
     }
@@ -524,7 +524,7 @@ class Attribute implements \JsonSerializable
         $this->selected = $selected;
     }
 
-    public function getOrderBy(): bool
+    public function getOrderBy()
     {
         return $this->orderBy;
     }
@@ -544,7 +544,7 @@ class Attribute implements \JsonSerializable
         $this->orderDisplay = $orderDisplay;
     }
 
-    public function getDetail(): bool
+    public function getDetail()
     {
         return $this->detail;
     }
@@ -554,7 +554,7 @@ class Attribute implements \JsonSerializable
         $this->detail = $detail;
     }
 
-    public function getRendererDetail(): string
+    public function getRendererDetail()
     {
         return $this->rendererDetail;
     }
@@ -564,7 +564,7 @@ class Attribute implements \JsonSerializable
         $this->rendererDetail = $rendererDetail;
     }
 
-    public function getOptions(): string
+    public function getOptions()
     {
         return $this->options;
     }
@@ -574,7 +574,7 @@ class Attribute implements \JsonSerializable
         $this->options = $options;
     }
 
-    public function getCriteriaFamily(): CriteriaFamily
+    public function getCriteriaFamily()
     {
         return $this->criteriaFamily;
     }
@@ -584,7 +584,7 @@ class Attribute implements \JsonSerializable
         $this->criteriaFamily = $criteriaFamily;
     }
 
-    public function getOutputCategory(): OutputCategory
+    public function getOutputCategory()
     {
         return $this->outputCategory;
     }
diff --git a/tests/Action/AttributeListActionTest.php b/tests/Action/AttributeListActionTest.php
index 295327c..aee4113 100644
--- a/tests/Action/AttributeListActionTest.php
+++ b/tests/Action/AttributeListActionTest.php
@@ -109,7 +109,9 @@ final class AttributeListActionTest extends TestCase
 
     private function addDatasetFamily(): DatasetFamily
     {
-        $family = new DatasetFamily();
+        $instance = $this->addInstance();
+
+        $family = new DatasetFamily($instance);
         $family->setLabel('Default dataset');
         $family->setDisplay(10);
         $this->entityManager->persist($family);
@@ -120,7 +122,6 @@ final class AttributeListActionTest extends TestCase
     private function addADataset(): Dataset
     {
         $project = $this->addProject();
-        $instance = $this->addInstance();
         $family = $this->addDatasetFamily();
 
         $dataset = new Dataset('obs_cat');
@@ -133,7 +134,6 @@ final class AttributeListActionTest extends TestCase
         $dataset->setDataPath('/mnt/obs_cat');
         $dataset->setSelectableRow(false);
         $dataset->setProject($project);
-        $dataset->setInstance($instance);
         $dataset->setDatasetFamily($family);
         $this->entityManager->persist($dataset);
         $this->entityManager->flush();
-- 
GitLab


From 83ba9e12a72212e431d40849803668812ee96599 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Agneray?= <francois.agneray@lam.fr>
Date: Thu, 19 Dec 2019 15:55:36 +0100
Subject: [PATCH 23/31] AttributeAction => done

---
 src/Entity/Attribute.php             | 86 ++++++++++++++--------------
 tests/Action/AttributeActionTest.php | 23 ++++----
 2 files changed, 55 insertions(+), 54 deletions(-)

diff --git a/src/Entity/Attribute.php b/src/Entity/Attribute.php
index 3ca9f4f..9a2a18d 100644
--- a/src/Entity/Attribute.php
+++ b/src/Entity/Attribute.php
@@ -269,47 +269,47 @@ class Attribute implements \JsonSerializable
         $this->dataset = $dataset;
     }
 
-    public function getId(): int
+    public function getId()
     {
         return $this->id;
     }
 
-    public function getName(): string
+    public function getName()
     {
         return $this->name;
     }
 
-    public function setName(string $name): void
+    public function setName($name)
     {
         $this->name = $name;
     }
 
-    public function getTableName(): string
+    public function getTableName()
     {
         return $this->tableName;
     }
 
-    public function setTableName(string $tableName): void
+    public function setTableName($tableName)
     {
         $this->tableName = $tableName;
     }
 
-    public function getLabel(): string
+    public function getLabel()
     {
         return $this->label;
     }
 
-    public function setLabel(string $label): void
+    public function setLabel($label)
     {
         $this->label = $label;
     }
 
-    public function getFormLabel(): string
+    public function getFormLabel()
     {
         return $this->formLabel;
     }
 
-    public function setFormLabel(string $formLabel): void
+    public function setFormLabel($formLabel)
     {
         $this->formLabel = $formLabel;
     }
@@ -319,27 +319,27 @@ class Attribute implements \JsonSerializable
         return $this->description;
     }
 
-    public function setDescription(string $description): void
+    public function setDescription($description)
     {
         $this->description = $description;
     }
 
-    public function getOutputDisplay(): int
+    public function getOutputDisplay()
     {
         return $this->outputDisplay;
     }
 
-    public function setOutputDisplay(int $outputDisplay): void
+    public function setOutputDisplay($outputDisplay)
     {
         $this->outputDisplay = $outputDisplay;
     }
 
-    public function setCriteriaDisplay(int $criteriaDisplay): void
+    public function setCriteriaDisplay($criteriaDisplay)
     {
         $this->criteriaDisplay = $criteriaDisplay;
     }
 
-    public function getCriteriaDisplay(): int
+    public function getCriteriaDisplay()
     {
         return $this->criteriaDisplay;
     }
@@ -349,7 +349,7 @@ class Attribute implements \JsonSerializable
         return $this->searchFlag;
     }
 
-    public function setSearchFlag(string $searchFlag): void
+    public function setSearchFlag($searchFlag)
     {
         $this->searchFlag = $searchFlag;
     }
@@ -359,7 +359,7 @@ class Attribute implements \JsonSerializable
         return $this->searchType;
     }
 
-    public function setSearchType(string $searchType): void
+    public function setSearchType($searchType)
     {
         $this->searchType = $searchType;
     }
@@ -369,17 +369,17 @@ class Attribute implements \JsonSerializable
         return $this->operator;
     }
 
-    public function setOperator(string $operator): void
+    public function setOperator($operator)
     {
         $this->operator = $operator;
     }
 
-    public function getType(): string
+    public function getType()
     {
         return $this->type;
     }
 
-    public function setType(string $type): void
+    public function setType($type)
     {
         $this->type = $type;
     }
@@ -389,7 +389,7 @@ class Attribute implements \JsonSerializable
         return $this->min;
     }
 
-    public function setMin(string $min): void
+    public function setMin($min)
     {
         $this->min = $min;
     }
@@ -399,7 +399,7 @@ class Attribute implements \JsonSerializable
         return $this->max;
     }
 
-    public function setMax(string $max): void
+    public function setMax($max)
     {
         $this->max = $max;
     }
@@ -409,7 +409,7 @@ class Attribute implements \JsonSerializable
         return $this->placeholderMin;
     }
 
-    public function setPlaceholderMin(string $placeholderMin): void
+    public function setPlaceholderMin($placeholderMin)
     {
         $this->placeholderMin = $placeholderMin;
     }
@@ -419,7 +419,7 @@ class Attribute implements \JsonSerializable
         return $this->placeholderMax;
     }
 
-    public function setPlaceholderMax(string $placeholderMax): void
+    public function setPlaceholderMax($placeholderMax)
     {
         $this->placeholderMax = $placeholderMax;
     }
@@ -429,7 +429,7 @@ class Attribute implements \JsonSerializable
         return $this->uriAction;
     }
 
-    public function setUriAction(string $uriAction): void
+    public function setUriAction($uriAction)
     {
         $this->uriAction = $uriAction;
     }
@@ -439,17 +439,17 @@ class Attribute implements \JsonSerializable
         return $this->renderer;
     }
 
-    public function setRenderer(string $renderer): void
+    public function setRenderer($renderer)
     {
         $this->renderer = $renderer;
     }
 
-    public function getDisplayDetail(): int
+    public function getDisplayDetail()
     {
         return $this->displayDetail;
     }
 
-    public function setDisplayDetail(int $displayDetail): void
+    public function setDisplayDetail($displayDetail)
     {
         $this->displayDetail = $displayDetail;
     }
@@ -459,7 +459,7 @@ class Attribute implements \JsonSerializable
         return $this->voUtype;
     }
 
-    public function setVoUtype(string $voUtype): void
+    public function setVoUtype($voUtype)
     {
         $this->voUtype = $voUtype;
     }
@@ -469,7 +469,7 @@ class Attribute implements \JsonSerializable
         return $this->voUcd;
     }
 
-    public function setVoUcd(string $voUcd): void
+    public function setVoUcd($voUcd)
     {
         $this->voUcd = $voUcd;
     }
@@ -479,7 +479,7 @@ class Attribute implements \JsonSerializable
         return $this->voUnit;
     }
 
-    public function setVoUnit(string $voUnit): void
+    public function setVoUnit($voUnit)
     {
         $this->voUnit = $voUnit;
     }
@@ -489,7 +489,7 @@ class Attribute implements \JsonSerializable
         return $this->voDescription;
     }
 
-    public function setVoDescription(string $voDescription): void
+    public function setVoDescription($voDescription)
     {
         $this->voDescription = $voDescription;
     }
@@ -499,7 +499,7 @@ class Attribute implements \JsonSerializable
         return $this->voDatatype;
     }
 
-    public function setVoDatatype(string $voDatatype): void
+    public function setVoDatatype($voDatatype)
     {
         $this->voDatatype = $voDatatype;
     }
@@ -509,17 +509,17 @@ class Attribute implements \JsonSerializable
         return $this->voSize;
     }
 
-    public function setVoSize(int $voSize): void
+    public function setVoSize($voSize)
     {
         $this->voSize = $voSize;
     }
 
-    public function getSelected(): bool
+    public function getSelected()
     {
         return $this->selected;
     }
 
-    public function setSelected(bool $selected): void
+    public function setSelected($selected)
     {
         $this->selected = $selected;
     }
@@ -529,17 +529,17 @@ class Attribute implements \JsonSerializable
         return $this->orderBy;
     }
 
-    public function setOrderBy(bool $orderBy): void
+    public function setOrderBy($orderBy)
     {
         $this->orderBy = $orderBy;
     }
 
-    public function getOrderDisplay(): int
+    public function getOrderDisplay()
     {
         return $this->orderDisplay;
     }
 
-    public function setOrderDisplay(int $orderDisplay): void
+    public function setOrderDisplay($orderDisplay)
     {
         $this->orderDisplay = $orderDisplay;
     }
@@ -549,7 +549,7 @@ class Attribute implements \JsonSerializable
         return $this->detail;
     }
 
-    public function setDetail(bool $detail): void
+    public function setDetail($detail)
     {
         $this->detail = $detail;
     }
@@ -559,7 +559,7 @@ class Attribute implements \JsonSerializable
         return $this->rendererDetail;
     }
 
-    public function setRendererDetail(string $rendererDetail): void
+    public function setRendererDetail($rendererDetail)
     {
         $this->rendererDetail = $rendererDetail;
     }
@@ -569,7 +569,7 @@ class Attribute implements \JsonSerializable
         return $this->options;
     }
 
-    public function setOptions(string $options): void
+    public function setOptions($options)
     {
         $this->options = $options;
     }
@@ -579,7 +579,7 @@ class Attribute implements \JsonSerializable
         return $this->criteriaFamily;
     }
 
-    public function setCriteriaFamily(CriteriaFamily $criteriaFamily): void
+    public function setCriteriaFamily($criteriaFamily)
     {
         $this->criteriaFamily = $criteriaFamily;
     }
@@ -589,7 +589,7 @@ class Attribute implements \JsonSerializable
         return $this->outputCategory;
     }
 
-    public function setOutputCategory(OutputCategory $outputCategory): void
+    public function setOutputCategory($outputCategory)
     {
         $this->outputCategory = $outputCategory;
     }
diff --git a/tests/Action/AttributeActionTest.php b/tests/Action/AttributeActionTest.php
index 97d423d..97708d6 100644
--- a/tests/Action/AttributeActionTest.php
+++ b/tests/Action/AttributeActionTest.php
@@ -71,8 +71,6 @@ final class AttributeActionTest extends TestCase
     public function testEditADatabase(): void
     {
         $this->addAnAttribute();
-        $this->addCriteriaFamily();
-        $this->addOutputCategory();
         $fields = $this->getEditAttributeFields();
         $request = $this->getRequest('PUT')->withParsedBody($fields);
         $response = ($this->action)($request, new Response(), array('name' => 'obs_cat', 'id' => 1));
@@ -173,7 +171,9 @@ final class AttributeActionTest extends TestCase
 
     private function addDatasetFamily(): DatasetFamily
     {
-        $family = new DatasetFamily();
+        $instance = $this->addInstance();
+
+        $family = new DatasetFamily($instance);
         $family->setLabel('Default dataset');
         $family->setDisplay(10);
         $this->entityManager->persist($family);
@@ -181,9 +181,9 @@ final class AttributeActionTest extends TestCase
         return $family;
     }
 
-    private function addCriteriaFamily(): CriteriaFamily
+    private function addCriteriaFamily(Dataset $dataset): CriteriaFamily
     {
-        $family = new CriteriaFamily();
+        $family = new CriteriaFamily($dataset);
         $family->setLabel('Default criteria');
         $family->setDisplay(10);
         $this->entityManager->persist($family);
@@ -192,9 +192,9 @@ final class AttributeActionTest extends TestCase
         return $family;
     }
 
-    private function addOutputCategory(): OutputCategory
+    private function addOutputCategory(Dataset $dataset): OutputCategory
     {
-        $outputFamily = $this->addOutputFamily();
+        $outputFamily = $this->addOutputFamily($dataset);
 
         $outputCategory = new OutputCategory();
         $outputCategory->setLabel('Default output category');
@@ -205,9 +205,9 @@ final class AttributeActionTest extends TestCase
         return $outputCategory;
     }
 
-    private function addOutputFamily(): OutputFamily
+    private function addOutputFamily(Dataset $dataset): OutputFamily
     {
-        $family = new OutputFamily();
+        $family = new OutputFamily($dataset);
         $family->setLabel('Default output');
         $family->setDisplay(10);
         $this->entityManager->persist($family);
@@ -218,7 +218,6 @@ final class AttributeActionTest extends TestCase
     private function addADataset(): Dataset
     {
         $project = $this->addProject();
-        $instance = $this->addInstance();
         $family = $this->addDatasetFamily();
 
         $dataset = new Dataset('obs_cat');
@@ -231,7 +230,6 @@ final class AttributeActionTest extends TestCase
         $dataset->setDataPath('/mnt/obs_cat');
         $dataset->setSelectableRow(false);
         $dataset->setProject($project);
-        $dataset->setInstance($instance);
         $dataset->setDatasetFamily($family);
         $this->entityManager->persist($dataset);
         $this->entityManager->flush();
@@ -243,6 +241,9 @@ final class AttributeActionTest extends TestCase
     {
         $dataset = $this->addADataset();
 
+        $this->addCriteriaFamily($dataset);
+        $this->addOutputCategory($dataset);
+
         $attribute = new Attribute(1, $dataset);
         $attribute->setName('id');
         $attribute->setTableName($dataset->getTableRef());
-- 
GitLab


From 7635354dab83658ac8697eabea7c4c024c82be37 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Agneray?= <francois.agneray@lam.fr>
Date: Thu, 19 Dec 2019 16:10:56 +0100
Subject: [PATCH 24/31] OutputFamilyAction => done

---
 app/dependencies.php                    |  20 ++-
 src/Action/OutputFamilyAction.php       |  80 ++++++++++
 tests/Action/OutputFamilyActionTest.php | 188 ++++++++++++++++++++++++
 3 files changed, 280 insertions(+), 8 deletions(-)
 create mode 100644 src/Action/OutputFamilyAction.php
 create mode 100644 tests/Action/OutputFamilyActionTest.php

diff --git a/app/dependencies.php b/app/dependencies.php
index 7f3f4ac..f81c995 100644
--- a/app/dependencies.php
+++ b/app/dependencies.php
@@ -75,14 +75,6 @@ $container->set('App\Action\FamilyAction', function (ContainerInterface $c) {
     return new App\Action\FamilyAction($c->get('em'));
 });
 
-$container->set('App\Action\OutputCategoryListAction', function (ContainerInterface $c) {
-    return new App\Action\OutputCategoryListAction($c->get('em'));
-});
-
-$container->set('App\Action\OutputCategoryAction', function (ContainerInterface $c) {
-    return new App\Action\OutputCategoryAction($c->get('em'));
-});
-
 $container->set('App\Action\InstanceListAction', function (ContainerInterface $c) {
     return new App\Action\InstanceListAction($c->get('em'));
 });
@@ -123,6 +115,18 @@ $container->set('App\Action\OutputFamilyListAction', function (ContainerInterfac
     return new App\Action\OutputFamilyListAction($c->get('em'), new App\Utils\DBALConnectionFactory());
 });
 
+$container->set('App\Action\OutputFamilyAction', function (ContainerInterface $c) {
+    return new App\Action\OutputFamilyAction($c->get('em'), new App\Utils\DBALConnectionFactory());
+});
+
+$container->set('App\Action\OutputCategoryListAction', function (ContainerInterface $c) {
+    return new App\Action\OutputCategoryListAction($c->get('em'));
+});
+
+$container->set('App\Action\OutputCategoryAction', function (ContainerInterface $c) {
+    return new App\Action\OutputCategoryAction($c->get('em'));
+});
+
 $container->set('App\Action\AttributeListAction', function (ContainerInterface $c) {
     return new App\Action\AttributeListAction($c->get('em'));
 });
diff --git a/src/Action/OutputFamilyAction.php b/src/Action/OutputFamilyAction.php
new file mode 100644
index 0000000..f6c12d2
--- /dev/null
+++ b/src/Action/OutputFamilyAction.php
@@ -0,0 +1,80 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Action;
+
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Psr\Http\Message\ResponseInterface as Response;
+use Slim\Exception\HttpBadRequestException;
+use Slim\Exception\HttpNotFoundException;
+use App\Entity\OutputFamily;
+
+final class OutputFamilyAction extends AbstractAction
+{
+    public function __invoke(Request $request, Response $response, array $args): Response
+    {
+        if ($request->getMethod() === OPTIONS) {
+            return $response->withHeader('Access-Control-Allow-Methods', 'GET, PUT, DELETE, OPTIONS');
+        }
+
+        // Search the correct output family with primary key
+        $outputFamily = $this->em->find('App\Entity\OutputFamily', $args['id']);
+
+        // If output family is not found 404
+        if (is_null($outputFamily)) {
+            throw new HttpNotFoundException(
+                $request,
+                'Output family with id ' . $args['id'] . ' is not found'
+            );
+        }
+
+        if ($request->getMethod() === GET) {
+            $payload = json_encode($outputFamily);
+        }
+
+        if ($request->getMethod() === PUT) {
+            $parsedBody = $request->getParsedBody();
+
+            $fields = array('label', 'display');
+            foreach ($fields as $a) {
+                if ($this->isEmptyField($a, $parsedBody)) {
+                    throw new HttpBadRequestException(
+                        $request,
+                        'Param ' . $a . ' needed to edit the output family'
+                    );
+                }
+            }
+
+            $this->editOutputFamily($outputFamily, $parsedBody);
+            $payload = json_encode($outputFamily);
+        }
+
+        if ($request->getMethod() === DELETE) {
+            $id = $outputFamily->getId();
+            $this->em->remove($outputFamily);
+            $this->em->flush();
+            $payload = json_encode(array(
+                'message' => 'Output family with id ' . $id . ' is removed!'
+            ));
+        }
+
+        $response->getBody()->write($payload);
+        return $response;
+    }
+
+    private function editOutputFamily(OutputFamily $family, array $parsedBody): void
+    {
+        $family->setLabel($parsedBody['label']);
+        $family->setDisplay($parsedBody['display']);
+        $this->em->flush();
+    }
+}
diff --git a/tests/Action/OutputFamilyActionTest.php b/tests/Action/OutputFamilyActionTest.php
new file mode 100644
index 0000000..06fad23
--- /dev/null
+++ b/tests/Action/OutputFamilyActionTest.php
@@ -0,0 +1,188 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Tests\Action;
+
+use PHPUnit\Framework\TestCase;
+use Nyholm\Psr7\ServerRequest;
+use Nyholm\Psr7\Response;
+use Slim\Exception\HttpNotFoundException;
+use Slim\Exception\HttpBadRequestException;
+use App\tests\EntityManagerBuilder;
+use App\Entity\Database;
+use App\Entity\Project;
+use App\Entity\Instance;
+use App\Entity\DatasetFamily;
+use App\Entity\Dataset;
+use App\Entity\OutputFamily;
+
+final class OutputFamilyActionTest extends TestCase
+{
+    private $action;
+    private $entityManager;
+
+    protected function setUp(): void
+    {
+        $this->entityManager = EntityManagerBuilder::getInstance();
+        $this->action = new \App\Action\OutputFamilyAction($this->entityManager);
+    }
+
+    public function testOptionsHttpMethod(): void
+    {
+        $request = $this->getRequest('OPTIONS');
+        $response = ($this->action)($request, new Response(), array());
+        $this->assertSame($response->getHeaderLine('Access-Control-Allow-Methods'), 'GET, PUT, DELETE, OPTIONS');
+    }
+
+    public function testOutputFamilyIsNotFound(): void
+    {
+        $this->expectException(HttpNotFoundException::class);
+        $this->expectExceptionMessage('Output family with id 1 is not found');
+        $request = $this->getRequest('GET');
+        $response = ($this->action)($request, new Response(), array('id' => 1));
+        $this->assertEquals(404, (int) $response->getStatusCode());
+    }
+
+    public function testGetAnOutputFamilyById(): void
+    {
+        $outputFamily = $this->addAnOutputFamily();
+        $request = $this->getRequest('GET');
+        $response = ($this->action)($request, new Response(), array('id' => 1));
+        $this->assertSame(json_encode($outputFamily), (string) $response->getBody());
+    }
+
+    public function testEditAnOutputFamilyEmptyLabelField(): void
+    {
+        $this->addAnOutputFamily();
+        $this->expectException(HttpBadRequestException::class);
+        $this->expectExceptionMessage('Param label needed to edit the output family');
+        $request = $this->getRequest('PUT')->withParsedBody(array());
+        $response = ($this->action)($request, new Response(), array('id' => 1));
+        $this->assertEquals(400, (int) $response->getStatusCode());
+    }
+
+    public function testEditAnOutputFamily(): void
+    {
+        $fields = array(
+            'label' => 'Modfied family',
+            'display' => 20
+        );
+        $this->addAnOutputFamily();
+        $request = $this->getRequest('PUT')->withParsedBody($fields);
+        $response = ($this->action)($request, new Response(), array('id' => 1));
+        $this->assertSame(json_encode(array_merge(['id' => 1], $fields)), (string) $response->getBody());
+    }
+
+    public function testDeleteAnOutputFamily(): void
+    {
+        $this->addAnOutputFamily();
+        $request = $this->getRequest('DELETE');
+        $response = ($this->action)($request, new Response(), array('id' => 1));
+        $this->assertSame(
+            json_encode(array('message' => 'Output family with id 1 is removed!')),
+            (string) $response->getBody()
+        );
+    }
+
+    protected function tearDown(): void
+    {
+        $this->entityManager->getConnection()->close();
+    }
+
+    private function getRequest(string $method): ServerRequest
+    {
+        return new ServerRequest($method, '/output-family/1', array(
+            'Content-Type' => 'application/json'
+        ));
+    }
+
+    private function addAnOutputFamily(): OutputFamily
+    {
+        $dataset = $this->addADataset();
+
+        $family = new OutputFamily($dataset);
+        $family->setLabel('Default criteria');
+        $family->setDisplay(10);
+        $this->entityManager->persist($family);
+
+        $this->entityManager->flush();
+
+        return $family;
+    }
+
+    private function addProject(): Project
+    {
+        $database = new Database();
+        $database->setLabel('Test1');
+        $database->setDbName('test1');
+        $database->setType('pgsql');
+        $database->setHost('db');
+        $database->setPort(5432);
+        $database->setLogin('test');
+        $database->setPassword('test');
+        $this->entityManager->persist($database);
+
+        $project = new Project('anis_project');
+        $project->setLabel('Test project');
+        $project->setDescription('Test description');
+        $project->setLink('http://test.com');
+        $project->setManager('User1');
+        $project->setDatabase($database);
+        $this->entityManager->persist($project);
+
+        $this->entityManager->flush();
+        return $project;
+    }
+
+    private function addInstance(): Instance
+    {
+        $instance = new Instance('aspic', 'Aspic');
+        $instance->setClientUrl('http://cesam.lam.fr/aspic');
+        $this->entityManager->persist($instance);
+        $this->entityManager->flush();
+        return $instance;
+    }
+
+    private function addDatasetFamily(): DatasetFamily
+    {
+        $instance = $this->addInstance();
+
+        $family = new DatasetFamily($instance);
+        $family->setLabel('Default dataset');
+        $family->setDisplay(10);
+        $this->entityManager->persist($family);
+
+        $this->entityManager->flush();
+        return $family;
+    }
+
+    private function addADataset(): Dataset
+    {
+        $project = $this->addProject();
+        $family = $this->addDatasetFamily();
+
+        $dataset = new Dataset('obs_cat');
+        $dataset->setTableRef('v_obs_cat');
+        $dataset->setLabel('Obscat label');
+        $dataset->setDescription('Obscat description');
+        $dataset->setDisplay(10);
+        $dataset->setCount(10000);
+        $dataset->setVo(false);
+        $dataset->setDataPath('/mnt/obs_cat');
+        $dataset->setSelectableRow(false);
+        $dataset->setProject($project);
+        $dataset->setDatasetFamily($family);
+        $this->entityManager->persist($dataset);
+        $this->entityManager->flush();
+        return $dataset;
+    }
+}
-- 
GitLab


From c34ac693b72d59fd95ca95af43bbd3f39ca976e0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Agneray?= <francois.agneray@lam.fr>
Date: Thu, 19 Dec 2019 16:29:10 +0100
Subject: [PATCH 25/31] OutputCategoryListAction => done

---
 src/Action/OutputCategoryListAction.php       |  26 ++--
 tests/Action/OutputCategoryListActionTest.php | 114 ++++++++++++++----
 2 files changed, 107 insertions(+), 33 deletions(-)

diff --git a/src/Action/OutputCategoryListAction.php b/src/Action/OutputCategoryListAction.php
index d818322..072b208 100644
--- a/src/Action/OutputCategoryListAction.php
+++ b/src/Action/OutputCategoryListAction.php
@@ -14,6 +14,7 @@ namespace App\Action;
 
 use Psr\Http\Message\ServerRequestInterface as Request;
 use Psr\Http\Message\ResponseInterface as Response;
+use Slim\Exception\HttpNotFoundException;
 use Slim\Exception\HttpBadRequestException;
 use App\Entity\OutputCategory;
 use App\Entity\OutputFamily;
@@ -36,8 +37,20 @@ final class OutputCategoryListAction extends AbstractAction
             return $response->withHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
         }
 
+        $outputFamily = $this->em->find('App\Entity\OutputFamily', $args['id']);
+
+        // Returns HTTP 404 if the output family is not found
+        if (is_null($outputFamily)) {
+            throw new HttpNotFoundException(
+                $request,
+                'Output family with id ' . $args['id'] . ' is not found'
+            );
+        }
+
         if ($request->getMethod() === GET) {
-            $outputCategories = $this->em->getRepository('App\Entity\OutputCategory')->findAll();
+            $outputCategories = $this->em->getRepository('App\Entity\OutputCategory')->findByOutputFamily(
+                $outputFamily
+            );
             $payload = json_encode($outputCategories);
         }
 
@@ -45,7 +58,7 @@ final class OutputCategoryListAction extends AbstractAction
             $parsedBody = $request->getParsedBody();
 
             // Verif mandatories fields
-            foreach (array('label', 'display', 'id_output_family') as $a) {
+            foreach (array('label', 'display') as $a) {
                 if ($this->isEmptyField($a, $parsedBody)) {
                     throw new HttpBadRequestException(
                         $request,
@@ -54,15 +67,6 @@ final class OutputCategoryListAction extends AbstractAction
                 }
             }
 
-            // Output family is mandatory
-            $outputFamily = $this->em->find('App\Entity\OutputFamily', $parsedBody['id_output_family']);
-            if (is_null($outputFamily)) {
-                throw new HttpBadRequestException(
-                    $request,
-                    'Output family with id ' . $parsedBody['id_output_family'] . ' is not found'
-                );
-            }
-
             $outputCategory = $this->postOutputCategory($parsedBody, $outputFamily);
             $payload = json_encode($outputCategory);
             $response = $response->withStatus(201);
diff --git a/tests/Action/OutputCategoryListActionTest.php b/tests/Action/OutputCategoryListActionTest.php
index 3c396e9..930499e 100644
--- a/tests/Action/OutputCategoryListActionTest.php
+++ b/tests/Action/OutputCategoryListActionTest.php
@@ -16,7 +16,13 @@ use PHPUnit\Framework\TestCase;
 use Nyholm\Psr7\ServerRequest;
 use Nyholm\Psr7\Response;
 use Slim\Exception\HttpBadRequestException;
+use Slim\Exception\HttpNotFoundException;
 use App\tests\EntityManagerBuilder;
+use App\Entity\Database;
+use App\Entity\Project;
+use App\Entity\Instance;
+use App\Entity\DatasetFamily;
+use App\Entity\Dataset;
 use App\Entity\OutputFamily;
 use App\Entity\OutputCategory;
 
@@ -38,11 +44,20 @@ final class OutputCategoryListActionTest extends TestCase
         $this->assertSame($response->getHeaderLine('Access-Control-Allow-Methods'), 'GET, POST, OPTIONS');
     }
 
+    public function testOutputFamilyIsNotFound(): void
+    {
+        $this->expectException(HttpNotFoundException::class);
+        $this->expectExceptionMessage('Output family with id 1 is not found');
+        $request = $this->getRequest('GET');
+        $response = ($this->action)($request, new Response(), array('id' => 1));
+        $this->assertEquals(404, (int) $response->getStatusCode());
+    }
+
     public function testGetAllOutputCategories(): void
     {
         $outputCategories = $this->addOutputCategories();
         $request = $this->getRequest('GET');
-        $response = ($this->action)($request, new Response(), array());
+        $response = ($this->action)($request, new Response(), array('id' => 1));
         $this->assertSame(
             json_encode($outputCategories),
             (string) $response->getBody()
@@ -51,24 +66,11 @@ final class OutputCategoryListActionTest extends TestCase
 
     public function testAddANewOutputCategoryEmptyLabelField(): void
     {
+        $this->addOutputFamily();
         $this->expectException(HttpBadRequestException::class);
         $this->expectExceptionMessage('Param label needed to add a new output category');
         $request = $this->getRequest('POST')->withParsedBody(array());
-        $response = ($this->action)($request, new Response(), array());
-        $this->assertEquals(400, (int) $response->getStatusCode());
-    }
-
-    public function testAddANewOutputCategoryOutputFamilyIsNotFound(): void
-    {
-        $this->expectException(HttpBadRequestException::class);
-        $this->expectExceptionMessage('Output family with id 1 is not found');
-        $fields = array(
-            'label' => 'Default output category',
-            'display' => 10,
-            'id_output_family' => 1
-        );
-        $request = $this->getRequest('POST')->withParsedBody($fields);
-        $response = ($this->action)($request, new Response(), array());
+        $response = ($this->action)($request, new Response(), array('id' => 1));
         $this->assertEquals(400, (int) $response->getStatusCode());
     }
 
@@ -77,13 +79,12 @@ final class OutputCategoryListActionTest extends TestCase
         $this->addOutputFamily();
         $fields = array(
             'label' => 'Default output category',
-            'display' => 10,
-            'id_output_family' => 1
+            'display' => 10
         );
         $request = $this->getRequest('POST')->withParsedBody($fields);
-        $response = ($this->action)($request, new Response(), array());
+        $response = ($this->action)($request, new Response(), array('id' => 1));
         $this->assertSame(
-            json_encode(array_merge(['id' => 1], $fields)),
+            json_encode(array_merge(['id' => 1], $fields, ['id_output_family' => 1])),
             (string) $response->getBody()
         );
         $this->assertEquals(201, (int) $response->getStatusCode());
@@ -96,14 +97,16 @@ final class OutputCategoryListActionTest extends TestCase
 
     private function getRequest(string $method): ServerRequest
     {
-        return new ServerRequest($method, '/output-category', array(
+        return new ServerRequest($method, '/output-family/1/output-category', array(
             'Content-Type' => 'application/json'
         ));
     }
 
     private function addOutputFamily(): OutputFamily
     {
-        $family = new OutputFamily();
+        $dataset = $this->addADataset();
+
+        $family = new OutputFamily($dataset);
         $family->setLabel('Default output family');
         $family->setDisplay(10);
         $this->entityManager->persist($family);
@@ -111,6 +114,73 @@ final class OutputCategoryListActionTest extends TestCase
         return $family;
     }
 
+    private function addProject(): Project
+    {
+        $database = new Database();
+        $database->setLabel('Test1');
+        $database->setDbName('test1');
+        $database->setType('pgsql');
+        $database->setHost('db');
+        $database->setPort(5432);
+        $database->setLogin('test');
+        $database->setPassword('test');
+        $this->entityManager->persist($database);
+
+        $project = new Project('anis_project');
+        $project->setLabel('Test project');
+        $project->setDescription('Test description');
+        $project->setLink('http://test.com');
+        $project->setManager('User1');
+        $project->setDatabase($database);
+        $this->entityManager->persist($project);
+
+        $this->entityManager->flush();
+        return $project;
+    }
+
+    private function addInstance(): Instance
+    {
+        $instance = new Instance('aspic', 'Aspic');
+        $instance->setClientUrl('http://cesam.lam.fr/aspic');
+        $this->entityManager->persist($instance);
+        $this->entityManager->flush();
+        return $instance;
+    }
+
+    private function addDatasetFamily(): DatasetFamily
+    {
+        $instance = $this->addInstance();
+
+        $family = new DatasetFamily($instance);
+        $family->setLabel('Default dataset');
+        $family->setDisplay(10);
+        $this->entityManager->persist($family);
+
+        $this->entityManager->flush();
+        return $family;
+    }
+
+    private function addADataset(): Dataset
+    {
+        $project = $this->addProject();
+        $family = $this->addDatasetFamily();
+
+        $dataset = new Dataset('obs_cat');
+        $dataset->setTableRef('v_obs_cat');
+        $dataset->setLabel('Obscat label');
+        $dataset->setDescription('Obscat description');
+        $dataset->setDisplay(10);
+        $dataset->setCount(10000);
+        $dataset->setVo(false);
+        $dataset->setDataPath('/mnt/obs_cat');
+        $dataset->setSelectableRow(false);
+        $dataset->setProject($project);
+        $dataset->setDatasetFamily($family);
+        $this->entityManager->persist($dataset);
+        $this->entityManager->flush();
+        return $dataset;
+    }
+
     private function addOutputCategories(): array
     {
         $outputFamily = $this->addOutputFamily();
-- 
GitLab


From 4c5e4ef80e7833262088186daac51624b21191f6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Agneray?= <francois.agneray@lam.fr>
Date: Thu, 19 Dec 2019 16:34:27 +0100
Subject: [PATCH 26/31] OutputCategoryAction => done

---
 src/Action/FamilyAction.php               | 103 ----------------
 src/Action/FamilyListAction.php           | 100 ---------------
 tests/Action/FamilyActionTest.php         | 124 -------------------
 tests/Action/FamilyListActionTest.php     | 141 ----------------------
 tests/Action/OutputCategoryActionTest.php |  78 +++++++++++-
 5 files changed, 76 insertions(+), 470 deletions(-)
 delete mode 100644 src/Action/FamilyAction.php
 delete mode 100644 src/Action/FamilyListAction.php
 delete mode 100644 tests/Action/FamilyActionTest.php
 delete mode 100644 tests/Action/FamilyListActionTest.php

diff --git a/src/Action/FamilyAction.php b/src/Action/FamilyAction.php
deleted file mode 100644
index 6201b2e..0000000
--- a/src/Action/FamilyAction.php
+++ /dev/null
@@ -1,103 +0,0 @@
-<?php
-
-/*
- * This file is part of Anis Server.
- *
- * (c) Laboratoire d'Astrophysique de Marseille / CNRS
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-declare(strict_types=1);
-
-namespace App\Action;
-
-use Psr\Http\Message\ServerRequestInterface as Request;
-use Psr\Http\Message\ResponseInterface as Response;
-use Slim\Exception\HttpBadRequestException;
-use Slim\Exception\HttpNotFoundException;
-use App\Entity\Family;
-
-final class FamilyAction extends AbstractAction
-{
-    public function __invoke(Request $request, Response $response, array $args): Response
-    {
-        if ($request->getMethod() === OPTIONS) {
-            return $response->withHeader('Access-Control-Allow-Methods', 'GET, PUT, DELETE, OPTIONS');
-        }
-
-        $type = $this->verifyType($args['type']);
-        if (empty($type)) {
-            throw new HttpBadRequestException(
-                $request,
-                'Type ' . $args['type'] . ' is not defined'
-            );
-        }
-
-        $entityClass = $this->getEntityClass($type);
-        $family = $this->em->find($entityClass, $args['id']);
-        if (is_null($family)) {
-            throw new HttpNotFoundException(
-                $request,
-                ucfirst($type) . ' family with id ' . $args['id'] . ' is not found'
-            );
-        }
-
-        if ($request->getMethod() === GET) {
-            $payload = json_encode($family);
-        }
-
-        if ($request->getMethod() === PUT) {
-            $parsedBody = $request->getParsedBody();
-
-            // Vérification des champs vides
-            $fields = array('label', 'display');
-            foreach ($fields as $a) {
-                if ($this->isEmptyField($a, $parsedBody)) {
-                    throw new HttpBadRequestException(
-                        $request,
-                        'Param ' . $a . ' needed to edit the ' . $type . ' family'
-                    );
-                }
-            }
-
-            $this->editFamily($family, $parsedBody);
-            $payload = json_encode($family);
-        }
-
-        if ($request->getMethod() === DELETE) {
-            $id = $family->getId();
-            $this->em->remove($family);
-            $this->em->flush();
-            $payload = json_encode(array(
-                'message' => ucfirst($type) . ' family with id ' . $id . ' is removed!'
-            ));
-        }
-
-        $response->getBody()->write($payload);
-        return $response;
-    }
-
-    private function verifyType(string $type): string
-    {
-        $t = strtolower($type);
-
-        if ($t == 'dataset' || $t == 'output' || $t == 'criteria') {
-            return $t;
-        } else {
-            return '';
-        }
-    }
-
-    private function getEntityClass(string $type): string
-    {
-        return 'App\Entity\\' . ucfirst($type) . 'Family';
-    }
-
-    private function editFamily(object $family, array $parsedBody): void
-    {
-        $family->setLabel($parsedBody['label']);
-        $family->setDisplay($parsedBody['display']);
-        $this->em->flush();
-    }
-}
diff --git a/src/Action/FamilyListAction.php b/src/Action/FamilyListAction.php
deleted file mode 100644
index 8a81b59..0000000
--- a/src/Action/FamilyListAction.php
+++ /dev/null
@@ -1,100 +0,0 @@
-<?php
-
-/*
- * This file is part of Anis Server.
- *
- * (c) Laboratoire d'Astrophysique de Marseille / CNRS
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-declare(strict_types=1);
-
-namespace App\Action;
-
-use Psr\Http\Message\ServerRequestInterface as Request;
-use Psr\Http\Message\ResponseInterface as Response;
-use Slim\Exception\HttpBadRequestException;
-use App\Entity\Family;
-
-final class FamilyListAction extends AbstractAction
-{
-    /**
-     * `GET`  Returns a list of all families by type listed in the metamodel database
-     * `POST` Add a new family
-     *
-     * @param  ServerRequestInterface $request  PSR-7 This object represents the HTTP request
-     * @param  ResponseInterface      $response PSR-7 This object represents the HTTP response
-     * @param  string[]               $args     This table contains information transmitted in the URL (see routes.php)
-     *
-     * @return ResponseInterface
-     */
-    public function __invoke(Request $request, Response $response, array $args): Response
-    {
-        if ($request->getMethod() === OPTIONS) {
-            return $response->withHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
-        }
-
-        $type = $this->verifyType($args['type']);
-        if (empty($type)) {
-            throw new HttpBadRequestException(
-                $request,
-                'Type ' . $args['type'] . ' is not defined'
-            );
-        }
-
-        if ($request->getMethod() === GET) {
-            $families = $this->em->getRepository($this->getEntityClass($type))->findAll();
-            $payload = json_encode($families);
-        }
-
-        if ($request->getMethod() === POST) {
-            $parsedBody = $request->getParsedBody();
-
-            // To work this action needs information to update
-            foreach (array('label', 'display') as $a) {
-                if ($this->isEmptyField($a, $parsedBody)) {
-                    throw new HttpBadRequestException(
-                        $request,
-                        'Param ' . $a . ' needed to add a new family'
-                    );
-                }
-            }
-
-            $family = $this->postFamily($parsedBody, $this->getEntityClass($type));
-            $payload = json_encode($family);
-            $response = $response->withStatus(201);
-        }
-
-        $response->getBody()->write($payload);
-        return $response;
-    }
-
-    private function verifyType(string $type): string
-    {
-        $t = strtolower($type);
-
-        if ($t == 'dataset' || $t == 'output' || $t == 'criteria') {
-            return $t;
-        } else {
-            return '';
-        }
-    }
-
-    private function getEntityClass(string $type): string
-    {
-        return 'App\Entity\\' . ucfirst($type) . 'Family';
-    }
-
-    private function postFamily(array $parsedBody, string $class): object
-    {
-        $family = new $class();
-        $family->setLabel($parsedBody['label']);
-        $family->setDisplay($parsedBody['display']);
-
-        $this->em->persist($family);
-        $this->em->flush();
-
-        return $family;
-    }
-}
diff --git a/tests/Action/FamilyActionTest.php b/tests/Action/FamilyActionTest.php
deleted file mode 100644
index 84ba23c..0000000
--- a/tests/Action/FamilyActionTest.php
+++ /dev/null
@@ -1,124 +0,0 @@
-<?php
-
-/*
- * This file is part of Anis Server.
- *
- * (c) Laboratoire d'Astrophysique de Marseille / CNRS
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-declare(strict_types=1);
-
-namespace App\Tests\Action;
-
-use PHPUnit\Framework\TestCase;
-use Nyholm\Psr7\ServerRequest;
-use Nyholm\Psr7\Response;
-use Slim\Exception\HttpNotFoundException;
-use Slim\Exception\HttpBadRequestException;
-use App\tests\EntityManagerBuilder;
-use App\Entity\DatasetFamily;
-
-final class FamilyActionTest extends TestCase
-{
-    private $action;
-    private $entityManager;
-
-    protected function setUp(): void
-    {
-        $this->entityManager = EntityManagerBuilder::getInstance();
-        $this->action = new \App\Action\FamilyAction($this->entityManager);
-    }
-
-    public function testOptionsHttpMethod(): void
-    {
-        $request = $this->getRequest('OPTIONS');
-        $response = ($this->action)($request, new Response(), array());
-        $this->assertSame($response->getHeaderLine('Access-Control-Allow-Methods'), 'GET, PUT, DELETE, OPTIONS');
-    }
-
-    public function testTypeIsNotDefined(): void
-    {
-        $this->expectException(HttpBadRequestException::class);
-        $this->expectExceptionMessage('Type undifined is not defined');
-        $request = $this->getRequest('GET');
-        $response = ($this->action)($request, new Response(), array('type' => 'undifined'));
-        $this->assertEquals(400, (int) $response->getStatusCode());
-    }
-
-    public function testDatasetFamilyIsNotFound(): void
-    {
-        $this->expectException(HttpNotFoundException::class);
-        $this->expectExceptionMessage('Dataset family with id 1 is not found');
-        $request = $this->getRequest('GET');
-        $response = ($this->action)($request, new Response(), array('type' => 'dataset', 'id' => 1));
-        $this->assertEquals(404, (int) $response->getStatusCode());
-    }
-
-    public function testGetADatasetFamilyById(): void
-    {
-        $family = $this->addADatasetFamily();
-        $request = $this->getRequest('GET');
-        $response = ($this->action)($request, new Response(), array('type' => 'dataset', 'id' => 1));
-        $this->assertSame(json_encode($family), (string) $response->getBody());
-    }
-
-    public function testEditADatasetFamilyEmptyLabelField(): void
-    {
-        $this->addADatasetFamily();
-        $this->expectException(HttpBadRequestException::class);
-        $this->expectExceptionMessage('Param label needed to edit the dataset family');
-        $request = $this->getRequest('PUT')->withParsedBody(array());
-        $response = ($this->action)($request, new Response(), array('type' => 'dataset', 'id' => 1));
-        $this->assertEquals(400, (int) $response->getStatusCode());
-    }
-
-    public function testEditADatasetFamily(): void
-    {
-        $fields = array(
-            'label' => 'New_label',
-            'display' => 20
-        );
-        $this->addADatasetFamily();
-        $request = $this->getRequest('PUT')->withParsedBody($fields);
-        $response = ($this->action)($request, new Response(), array('type' => 'dataset', 'id' => 1));
-        $this->assertSame(
-            json_encode(array_merge(['id' => 1], $fields, ['type' => 'dataset'])),
-            (string) $response->getBody()
-        );
-    }
-
-    public function testDeleteADatasetFamily(): void
-    {
-        $this->addADatasetFamily();
-        $request = $this->getRequest('DELETE');
-        $response = ($this->action)($request, new Response(), array('type' => 'dataset', 'id' => 1));
-        $this->assertSame(
-            json_encode(array('message' => 'Dataset family with id 1 is removed!')),
-            (string) $response->getBody()
-        );
-    }
-
-    protected function tearDown(): void
-    {
-        $this->entityManager->getConnection()->close();
-    }
-
-    private function getRequest(string $method): ServerRequest
-    {
-        return new ServerRequest($method, '/family/dataset/1', array(
-            'Content-Type' => 'application/json'
-        ));
-    }
-
-    private function addADatasetFamily(): DatasetFamily
-    {
-        $family = new DatasetFamily();
-        $family->setLabel('Default dataset');
-        $family->setDisplay(10);
-        $this->entityManager->persist($family);
-        $this->entityManager->flush();
-        return $family;
-    }
-}
diff --git a/tests/Action/FamilyListActionTest.php b/tests/Action/FamilyListActionTest.php
deleted file mode 100644
index 8206c58..0000000
--- a/tests/Action/FamilyListActionTest.php
+++ /dev/null
@@ -1,141 +0,0 @@
-<?php
-
-/*
- * This file is part of Anis Server.
- *
- * (c) Laboratoire d'Astrophysique de Marseille / CNRS
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-declare(strict_types=1);
-
-namespace App\Tests\Action;
-
-use PHPUnit\Framework\TestCase;
-use Nyholm\Psr7\ServerRequest;
-use Nyholm\Psr7\Response;
-use Slim\Exception\HttpBadRequestException;
-use App\tests\EntityManagerBuilder;
-use App\Entity\DatasetFamily;
-
-final class FamilyListActionTest extends TestCase
-{
-    private $action;
-    private $entityManager;
-
-    protected function setUp(): void
-    {
-        $this->entityManager = EntityManagerBuilder::getInstance();
-        $this->action = new \App\Action\FamilyListAction($this->entityManager);
-    }
-
-    public function testOptionsHttpMethod(): void
-    {
-        $request = $this->getRequest('OPTIONS');
-        $response = ($this->action)($request, new Response(), array());
-        $this->assertSame($response->getHeaderLine('Access-Control-Allow-Methods'), 'GET, POST, OPTIONS');
-    }
-
-    public function testTypeIsNotDefined(): void
-    {
-        $this->expectException(HttpBadRequestException::class);
-        $this->expectExceptionMessage('Type undifined is not defined');
-        $request = $this->getRequest('GET');
-        $response = ($this->action)($request, new Response(), array('type' => 'undifined'));
-        $this->assertEquals(400, (int) $response->getStatusCode());
-    }
-
-    public function testGetAllDatasetFamilies(): void
-    {
-        $families = $this->addDatasetFamilies();
-        $request = $this->getRequest('GET');
-        $response = ($this->action)($request, new Response(), array('type' => 'dataset'));
-        $this->assertSame(
-            json_encode($families),
-            (string) $response->getBody()
-        );
-    }
-
-    public function testAddANewFamilyEmptyLabelField(): void
-    {
-        $this->expectException(HttpBadRequestException::class);
-        $this->expectExceptionMessage('Param label needed to add a new family');
-        $request = $this->getRequest('POST')->withParsedBody(array());
-        $response = ($this->action)($request, new Response(), array('type' => 'dataset'));
-        $this->assertEquals(400, (int) $response->getStatusCode());
-    }
-
-    public function testAddANewDatasetFamily(): void
-    {
-        $fields = array(
-            'label' => 'Default family',
-            'display' => 10
-        );
-        $request = $this->getRequest('POST')->withParsedBody($fields);
-        $response = ($this->action)($request, new Response(), array('type' => 'dataset'));
-        $this->assertSame(
-            json_encode(array_merge(['id' => 1], $fields, ['type' => 'dataset'])),
-            (string) $response->getBody()
-        );
-        $this->assertEquals(201, (int) $response->getStatusCode());
-    }
-
-    public function testAddANewCriteriaFamily(): void
-    {
-        $fields = array(
-            'label' => 'Default criteria',
-            'display' => 10
-        );
-        $request = $this->getRequest('POST')->withParsedBody($fields);
-        $response = ($this->action)($request, new Response(), array('type' => 'criteria'));
-        $this->assertSame(
-            json_encode(array_merge(['id' => 1], $fields, ['type' => 'criteria'])),
-            (string) $response->getBody()
-        );
-        $this->assertEquals(201, (int) $response->getStatusCode());
-    }
-
-    public function testAddANewOutputFamily(): void
-    {
-        $fields = array(
-            'label' => 'Default output',
-            'display' => 10
-        );
-        $request = $this->getRequest('POST')->withParsedBody($fields);
-        $response = ($this->action)($request, new Response(), array('type' => 'output'));
-        $this->assertSame(
-            json_encode(array_merge(['id' => 1], $fields, ['type' => 'output'])),
-            (string) $response->getBody()
-        );
-        $this->assertEquals(201, (int) $response->getStatusCode());
-    }
-
-    protected function tearDown(): void
-    {
-        $this->entityManager->getConnection()->close();
-    }
-
-    private function getRequest(string $method): ServerRequest
-    {
-        return new ServerRequest($method, '/family/dataset', array(
-            'Content-Type' => 'application/json'
-        ));
-    }
-
-    private function addDatasetFamilies(): array
-    {
-        $family1 = new DatasetFamily();
-        $family1->setLabel('Default dataset');
-        $family1->setDisplay(10);
-        $this->entityManager->persist($family1);
-
-        $family2 = new DatasetFamily();
-        $family2->setLabel('My family dataset');
-        $family2->setDisplay(20);
-        $this->entityManager->persist($family2);
-
-        $this->entityManager->flush();
-        return array($family1, $family2);
-    }
-}
diff --git a/tests/Action/OutputCategoryActionTest.php b/tests/Action/OutputCategoryActionTest.php
index 277962e..ad292d0 100644
--- a/tests/Action/OutputCategoryActionTest.php
+++ b/tests/Action/OutputCategoryActionTest.php
@@ -18,8 +18,13 @@ use Nyholm\Psr7\Response;
 use Slim\Exception\HttpNotFoundException;
 use Slim\Exception\HttpBadRequestException;
 use App\tests\EntityManagerBuilder;
-use App\Entity\OutputCategory;
+use App\Entity\Database;
+use App\Entity\Project;
+use App\Entity\Instance;
+use App\Entity\DatasetFamily;
+use App\Entity\Dataset;
 use App\Entity\OutputFamily;
+use App\Entity\OutputCategory;
 
 final class OutputCategoryActionTest extends TestCase
 {
@@ -123,7 +128,9 @@ final class OutputCategoryActionTest extends TestCase
 
     private function addOutputFamily(): OutputFamily
     {
-        $family = new OutputFamily();
+        $dataset = $this->addADataset();
+
+        $family = new OutputFamily($dataset);
         $family->setLabel('Default output family');
         $family->setDisplay(10);
         $this->entityManager->persist($family);
@@ -131,6 +138,73 @@ final class OutputCategoryActionTest extends TestCase
         return $family;
     }
 
+    private function addProject(): Project
+    {
+        $database = new Database();
+        $database->setLabel('Test1');
+        $database->setDbName('test1');
+        $database->setType('pgsql');
+        $database->setHost('db');
+        $database->setPort(5432);
+        $database->setLogin('test');
+        $database->setPassword('test');
+        $this->entityManager->persist($database);
+
+        $project = new Project('anis_project');
+        $project->setLabel('Test project');
+        $project->setDescription('Test description');
+        $project->setLink('http://test.com');
+        $project->setManager('User1');
+        $project->setDatabase($database);
+        $this->entityManager->persist($project);
+
+        $this->entityManager->flush();
+        return $project;
+    }
+
+    private function addInstance(): Instance
+    {
+        $instance = new Instance('aspic', 'Aspic');
+        $instance->setClientUrl('http://cesam.lam.fr/aspic');
+        $this->entityManager->persist($instance);
+        $this->entityManager->flush();
+        return $instance;
+    }
+
+    private function addDatasetFamily(): DatasetFamily
+    {
+        $instance = $this->addInstance();
+
+        $family = new DatasetFamily($instance);
+        $family->setLabel('Default dataset');
+        $family->setDisplay(10);
+        $this->entityManager->persist($family);
+
+        $this->entityManager->flush();
+        return $family;
+    }
+
+    private function addADataset(): Dataset
+    {
+        $project = $this->addProject();
+        $family = $this->addDatasetFamily();
+
+        $dataset = new Dataset('obs_cat');
+        $dataset->setTableRef('v_obs_cat');
+        $dataset->setLabel('Obscat label');
+        $dataset->setDescription('Obscat description');
+        $dataset->setDisplay(10);
+        $dataset->setCount(10000);
+        $dataset->setVo(false);
+        $dataset->setDataPath('/mnt/obs_cat');
+        $dataset->setSelectableRow(false);
+        $dataset->setProject($project);
+        $dataset->setDatasetFamily($family);
+        $this->entityManager->persist($dataset);
+        $this->entityManager->flush();
+        return $dataset;
+    }
+
     private function addAnOutputCategory(): OutputCategory
     {
         $outputFamily = $this->addOutputFamily();
-- 
GitLab


From b314077ea1f99480a71d0f78465eeae060882a56 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Agneray?= <francois.agneray@lam.fr>
Date: Thu, 19 Dec 2019 16:36:01 +0100
Subject: [PATCH 27/31] Fixed bug: phpcs

---
 src/Action/SearchAction.php             | 7 +++++--
 tests/Middleware/CorsMiddlewareTest.php | 5 ++++-
 2 files changed, 9 insertions(+), 3 deletions(-)

diff --git a/src/Action/SearchAction.php b/src/Action/SearchAction.php
index 3905ec3..342aa07 100644
--- a/src/Action/SearchAction.php
+++ b/src/Action/SearchAction.php
@@ -42,8 +42,11 @@ final class SearchAction extends AbstractAction
      * @param EntityManagerInterface $em Doctrine       Entity Manager Interface
      * @param DBALConnectionFactory  $connectionFactory Factory used to construct connection to business database
      */
-    public function __construct(EntityManagerInterface $em, DBALConnectionFactory $connectionFactory, OperatorFactory $operatorFactory)
-    {
+    public function __construct(
+        EntityManagerInterface $em,
+        DBALConnectionFactory $connectionFactory,
+        OperatorFactory $operatorFactory
+    ) {
         parent::__construct($em);
         $this->connectionFactory = $connectionFactory;
         $this->operatorFactory = $operatorFactory;
diff --git a/tests/Middleware/CorsMiddlewareTest.php b/tests/Middleware/CorsMiddlewareTest.php
index d74c690..7e76f23 100644
--- a/tests/Middleware/CorsMiddlewareTest.php
+++ b/tests/Middleware/CorsMiddlewareTest.php
@@ -35,7 +35,10 @@ final class CorsMiddlewareTest extends TestCase
         $corsMiddleware = new \App\Middleware\CorsMiddleware();
         $response = $corsMiddleware->process($request, $requestHandler);
         $this->assertSame((string) $response->getHeaderLine('Access-Control-Allow-Origin'), '*');
-        $this->assertSame('Content-Type, Authorization', (string) $response->getHeaderLine('Access-Control-Allow-Headers'));
+        $this->assertSame(
+            'Content-Type, Authorization',
+            (string) $response->getHeaderLine('Access-Control-Allow-Headers')
+        );
     }
     
     public function testCorsHeadersForGetMethod()
-- 
GitLab


From 794c2a8c73076bca4630a061b82402ce248adb7e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Agneray?= <francois.agneray@lam.fr>
Date: Thu, 19 Dec 2019 16:48:59 +0100
Subject: [PATCH 28/31] Change url into create-db shell script

---
 conf-dev/create-db.sh | 13 +++++++------
 1 file changed, 7 insertions(+), 6 deletions(-)

diff --git a/conf-dev/create-db.sh b/conf-dev/create-db.sh
index 72f2746..48d6712 100644
--- a/conf-dev/create-db.sh
+++ b/conf-dev/create-db.sh
@@ -7,11 +7,12 @@ set -e
 curl -d '{"label":"Test","dbname":"anis_test","dbtype":"pdo_pgsql","dbhost":"db","dbport":5432,"dblogin":"anis","dbpassword":"anis"}' -H "Content-Type: application/json" -X POST http://localhost/database
 curl -d '{"name":"anis_project","label":"Anis Project Test","description":"Project used for testing","link":"http://project.com","manager":"M. Durand","id_database":1}' -H "Content-Type: application/json" -X POST http://localhost/project
 
-curl -d '{"label":"Default dataset family","display":10}' -H "Content-Type: application/json" -X POST http://localhost/family/dataset
 curl -d '{"name":"default","label":"Default instance","client_url":"http://localhost:4200"}' -H "Content-Type: application/json" -X POST http://localhost/instance
-curl -d '{"name":"obs_cat","table_ref":"obs_cat","label":"ObsCat dataset","description":"ObsCat","display":"10","count":"10000","vo":false,"data_path":"/mnt/mount","selectable_row":true,"project_name":"anis_project","id_dataset_family":1}' -H "Content-Type: application/json" -X POST http://localhost/instance/default/dataset
-curl -d '{"name":"observations","table_ref":"observations_info","label":"Observations dataset","description":"Observations","display":"20","count":"177454","vo":false,"data_path":"/mnt/mount","selectable_row":false,"project_name":"anis_project","id_dataset_family":1}' -H "Content-Type: application/json" -X POST http://localhost/instance/default/dataset
 
-curl -d '{"label":"Default criteria family","display":10}' -H "Content-Type: application/json" -X POST http://localhost/family/criteria
-curl -d '{"label":"Default output family","display":10}' -H "Content-Type: application/json" -X POST http://localhost/family/output
-curl -d '{"label":"Default output category","display":10,"id_output_family":1}' -H "Content-Type: application/json" -X POST http://localhost/output-category
+curl -d '{"label":"Default dataset family","display":10}' -H "Content-Type: application/json" -X POST http://localhost/instance/default/dataset-family
+curl -d '{"name":"obs_cat","table_ref":"obs_cat","label":"ObsCat dataset","description":"ObsCat","display":10,"count":10000,"vo":false,"data_path":"/mnt/mount","selectable_row":true,"project_name":"anis_project"}' -H "Content-Type: application/json" -X POST http://localhost/dataset-family/1/dataset
+curl -d '{"name":"observations","table_ref":"observations_info","label":"Observations dataset","description":"Observations","display":20,"count":177454,"vo":false,"data_path":"/mnt/mount","selectable_row":false,"project_name":"anis_project"}' -H "Content-Type: application/json" -X POST http://localhost/dataset-family/1/dataset
+
+#curl -d '{"label":"Default criteria family","display":10}' -H "Content-Type: application/json" -X POST http://localhost/family/criteria
+#curl -d '{"label":"Default output family","display":10}' -H "Content-Type: application/json" -X POST http://localhost/family/output
+#curl -d '{"label":"Default output category","display":10,"id_output_family":1}' -H "Content-Type: application/json" -X POST http://localhost/output-category
-- 
GitLab


From 5baac85843c0683f4f2a68a13d469930f3fbb71b Mon Sep 17 00:00:00 2001
From: Tifenn GUILLAS <tifenn.guillas@lam.fr>
Date: Fri, 20 Dec 2019 11:51:46 +0100
Subject: [PATCH 29/31] Fix conflict with getAttributes() returned type

---
 docker-compose.yml     | 5 +++++
 src/Entity/Dataset.php | 4 ++--
 2 files changed, 7 insertions(+), 2 deletions(-)

diff --git a/docker-compose.yml b/docker-compose.yml
index 42f384a..67e2516 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -34,6 +34,11 @@ services:
             - ./conf-dev/obs_cat.sql:/sql/obs_cat.sql
             - ./conf-dev/observations_info.sql:/sql/observations_info.sql
             - ./conf-dev/init-postgres.sh:/docker-entrypoint-initdb.d/init-postgres.sh
+    
+    adminer:
+        image: adminer
+        ports:
+            - 8083:8080
 
 volumes:
     pgdata:
\ No newline at end of file
diff --git a/src/Entity/Dataset.php b/src/Entity/Dataset.php
index ff6da07..5780627 100644
--- a/src/Entity/Dataset.php
+++ b/src/Entity/Dataset.php
@@ -12,7 +12,7 @@ declare(strict_types=1);
 
 namespace App\Entity;
 
-use Doctrine\Common\Collections\ArrayCollection;
+// use Doctrine\Common\Collections\Pers;
 
 /**
  * @Entity
@@ -218,7 +218,7 @@ class Dataset implements \JsonSerializable
         $this->datasetFamily = $datasetFamily;
     }
 
-    public function getAttributes(): ArrayCollection
+    public function getAttributes()
     {
         return $this->attributes;
     }
-- 
GitLab


From 2a8a23ef74025665c4f8c794a1c2f035321c5f2a Mon Sep 17 00:00:00 2001
From: Tifenn GUILLAS <tifenn.guillas@lam.fr>
Date: Fri, 20 Dec 2019 11:53:06 +0100
Subject: [PATCH 30/31] Fix typo

---
 src/Entity/Dataset.php | 2 --
 1 file changed, 2 deletions(-)

diff --git a/src/Entity/Dataset.php b/src/Entity/Dataset.php
index 5780627..33c61c1 100644
--- a/src/Entity/Dataset.php
+++ b/src/Entity/Dataset.php
@@ -12,8 +12,6 @@ declare(strict_types=1);
 
 namespace App\Entity;
 
-// use Doctrine\Common\Collections\Pers;
-
 /**
  * @Entity
  * @Table(name="dataset")
-- 
GitLab


From 254be201a39b0f5b36999a703bfc30f9b172b352 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Agneray?= <francois.agneray@lam.fr>
Date: Fri, 20 Dec 2019 14:29:21 +0100
Subject: [PATCH 31/31] OutputCategoryListByDatasetAction => done

---
 app/dependencies.php                          |  14 +-
 app/routes.php                                |   1 +
 .../OutputCategoryListByDatasetAction.php     |  60 ++++++
 src/Entity/Dataset.php                        |   2 +
 .../OutputCategoryListByDatasetActionTest.php | 177 ++++++++++++++++++
 5 files changed, 249 insertions(+), 5 deletions(-)
 create mode 100644 src/Action/OutputCategoryListByDatasetAction.php
 create mode 100644 tests/Action/OutputCategoryListByDatasetActionTest.php

diff --git a/app/dependencies.php b/app/dependencies.php
index f81c995..3a782ce 100644
--- a/app/dependencies.php
+++ b/app/dependencies.php
@@ -100,23 +100,27 @@ $container->set('App\Action\DatasetListAction', function (ContainerInterface $c)
 });
 
 $container->set('App\Action\DatasetAction', function (ContainerInterface $c) {
-    return new App\Action\DatasetAction($c->get('em'), new App\Utils\DBALConnectionFactory());
+    return new App\Action\DatasetAction($c->get('em'));
 });
 
 $container->set('App\Action\CriteriaFamilyListAction', function (ContainerInterface $c) {
-    return new App\Action\CriteriaFamilyListAction($c->get('em'), new App\Utils\DBALConnectionFactory());
+    return new App\Action\CriteriaFamilyListAction($c->get('em'));
 });
 
 $container->set('App\Action\CriteriaFamilyAction', function (ContainerInterface $c) {
-    return new App\Action\CriteriaFamilyAction($c->get('em'), new App\Utils\DBALConnectionFactory());
+    return new App\Action\CriteriaFamilyAction($c->get('em'));
 });
 
 $container->set('App\Action\OutputFamilyListAction', function (ContainerInterface $c) {
-    return new App\Action\OutputFamilyListAction($c->get('em'), new App\Utils\DBALConnectionFactory());
+    return new App\Action\OutputFamilyListAction($c->get('em'));
 });
 
 $container->set('App\Action\OutputFamilyAction', function (ContainerInterface $c) {
-    return new App\Action\OutputFamilyAction($c->get('em'), new App\Utils\DBALConnectionFactory());
+    return new App\Action\OutputFamilyAction($c->get('em'));
+});
+
+$container->set('App\Action\OutputCategoryListByDatasetAction', function (ContainerInterface $c) {
+    return new App\Action\OutputCategoryListByDatasetAction($c->get('em'));
 });
 
 $container->set('App\Action\OutputCategoryListAction', function (ContainerInterface $c) {
diff --git a/app/routes.php b/app/routes.php
index 57a2bed..f21606c 100644
--- a/app/routes.php
+++ b/app/routes.php
@@ -29,6 +29,7 @@ $app->map([OPTIONS, GET, POST], '/dataset/{name}/criteria-family', App\Action\Cr
 $app->map([OPTIONS, GET, PUT, DELETE], '/criteria-family/{id}', App\Action\CriteriaFamilyAction::class);
 $app->map([OPTIONS, GET, POST], '/dataset/{name}/output-family', App\Action\OutputFamilyListAction::class);
 $app->map([OPTIONS, GET, PUT, DELETE], '/output-family/{id}', App\Action\OutputFamilyAction::class);
+$app->map([OPTIONS, GET], '/dataset/{name}/output-category', App\Action\OutputCategoryListByDatasetAction::class);
 $app->map([OPTIONS, GET, POST], '/output-family/{id}/output-category', App\Action\OutputCategoryListAction::class);
 $app->map([OPTIONS, GET, PUT, DELETE], '/output-category/{id}', App\Action\OutputCategoryAction::class);
 $app->map([OPTIONS, GET], '/dataset/{name}/attribute', App\Action\AttributeListAction::class);
diff --git a/src/Action/OutputCategoryListByDatasetAction.php b/src/Action/OutputCategoryListByDatasetAction.php
new file mode 100644
index 0000000..9e0fed0
--- /dev/null
+++ b/src/Action/OutputCategoryListByDatasetAction.php
@@ -0,0 +1,60 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Action;
+
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Psr\Http\Message\ResponseInterface as Response;
+use Slim\Exception\HttpNotFoundException;
+
+final class OutputCategoryListByDatasetAction extends AbstractAction
+{
+    /**
+     * `GET`  Returns a list of all output categories for a given dataset
+     *
+     * @param  ServerRequestInterface $request  PSR-7 This object represents the HTTP request
+     * @param  ResponseInterface      $response PSR-7 This object represents the HTTP response
+     * @param  string[]               $args     This table contains information transmitted in the URL (see routes.php)
+     *
+     * @return ResponseInterface
+     */
+    public function __invoke(Request $request, Response $response, array $args): Response
+    {
+        if ($request->getMethod() === OPTIONS) {
+            return $response->withHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
+        }
+
+        $dataset = $this->em->find('App\Entity\Dataset', $args['name']);
+
+        // Returns HTTP 404 if the dataset is not found
+        if (is_null($dataset)) {
+            throw new HttpNotFoundException(
+                $request,
+                'Dataset with name ' . $args['name'] . ' is not found'
+            );
+        }
+
+        if ($request->getMethod() === GET) {
+            $qb = $this->em->createQueryBuilder();
+            $qb->select('o')
+                ->from('App\Entity\OutputCategory', 'o')
+                ->join('o.outputFamily', 'f')
+                ->where($qb->expr()->eq('IDENTITY(f.dataset)', ':datasetName'))
+                ->setParameter('datasetName', $dataset->getName());
+            $datasets = $qb->getQuery()->getResult();
+            $payload = json_encode($datasets);
+        }
+
+        $response->getBody()->write($payload);
+        return $response;
+    }
+}
diff --git a/src/Entity/Dataset.php b/src/Entity/Dataset.php
index 33c61c1..47d7741 100644
--- a/src/Entity/Dataset.php
+++ b/src/Entity/Dataset.php
@@ -12,6 +12,8 @@ declare(strict_types=1);
 
 namespace App\Entity;
 
+use Doctrine\Common\Collections\ArrayCollection;
+
 /**
  * @Entity
  * @Table(name="dataset")
diff --git a/tests/Action/OutputCategoryListByDatasetActionTest.php b/tests/Action/OutputCategoryListByDatasetActionTest.php
new file mode 100644
index 0000000..afab9ee
--- /dev/null
+++ b/tests/Action/OutputCategoryListByDatasetActionTest.php
@@ -0,0 +1,177 @@
+<?php
+
+/*
+ * This file is part of Anis Server.
+ *
+ * (c) Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace App\Tests\Action;
+
+use PHPUnit\Framework\TestCase;
+use Nyholm\Psr7\ServerRequest;
+use Nyholm\Psr7\Response;
+use Slim\Exception\HttpBadRequestException;
+use Slim\Exception\HttpNotFoundException;
+use App\tests\EntityManagerBuilder;
+use App\Entity\Database;
+use App\Entity\Project;
+use App\Entity\Instance;
+use App\Entity\DatasetFamily;
+use App\Entity\Dataset;
+use App\Entity\OutputFamily;
+use App\Entity\OutputCategory;
+
+final class OutputCategoryListByDatasetActionTest extends TestCase
+{
+    private $action;
+    private $entityManager;
+
+    protected function setUp(): void
+    {
+        $this->entityManager = EntityManagerBuilder::getInstance();
+        $this->action = new \App\Action\OutputCategoryListByDatasetAction($this->entityManager);
+    }
+
+    public function testOptionsHttpMethod(): void
+    {
+        $request = $this->getRequest('OPTIONS');
+        $response = ($this->action)($request, new Response(), array());
+        $this->assertSame($response->getHeaderLine('Access-Control-Allow-Methods'), 'GET, OPTIONS');
+    }
+
+    public function testDatasetIsNotFound(): void
+    {
+        $this->expectException(HttpNotFoundException::class);
+        $this->expectExceptionMessage('Dataset with name obs_cat is not found');
+        $request = $this->getRequest('GET');
+        $response = ($this->action)($request, new Response(), array('name' => 'obs_cat'));
+        $this->assertEquals(404, (int) $response->getStatusCode());
+    }
+
+    public function testGetAllOutputCategoriesForADataset(): void
+    {
+        $outputCategories = $this->addOutputCategories();
+        $request = $this->getRequest('GET');
+        $response = ($this->action)($request, new Response(), array('name' => 'obs_cat'));
+        $this->assertSame(
+            json_encode($outputCategories),
+            (string) $response->getBody()
+        );
+    }
+
+    protected function tearDown(): void
+    {
+        $this->entityManager->getConnection()->close();
+    }
+
+    private function getRequest(string $method): ServerRequest
+    {
+        return new ServerRequest($method, '/dataset/obs_cat/output-category', array(
+            'Content-Type' => 'application/json'
+        ));
+    }
+
+    private function addOutputFamily(): OutputFamily
+    {
+        $dataset = $this->addADataset();
+
+        $family = new OutputFamily($dataset);
+        $family->setLabel('Default output family');
+        $family->setDisplay(10);
+        $this->entityManager->persist($family);
+        $this->entityManager->flush();
+        return $family;
+    }
+
+    private function addProject(): Project
+    {
+        $database = new Database();
+        $database->setLabel('Test1');
+        $database->setDbName('test1');
+        $database->setType('pgsql');
+        $database->setHost('db');
+        $database->setPort(5432);
+        $database->setLogin('test');
+        $database->setPassword('test');
+        $this->entityManager->persist($database);
+
+        $project = new Project('anis_project');
+        $project->setLabel('Test project');
+        $project->setDescription('Test description');
+        $project->setLink('http://test.com');
+        $project->setManager('User1');
+        $project->setDatabase($database);
+        $this->entityManager->persist($project);
+
+        $this->entityManager->flush();
+        return $project;
+    }
+
+    private function addInstance(): Instance
+    {
+        $instance = new Instance('aspic', 'Aspic');
+        $instance->setClientUrl('http://cesam.lam.fr/aspic');
+        $this->entityManager->persist($instance);
+        $this->entityManager->flush();
+        return $instance;
+    }
+
+    private function addDatasetFamily(): DatasetFamily
+    {
+        $instance = $this->addInstance();
+
+        $family = new DatasetFamily($instance);
+        $family->setLabel('Default dataset');
+        $family->setDisplay(10);
+        $this->entityManager->persist($family);
+
+        $this->entityManager->flush();
+        return $family;
+    }
+
+    private function addADataset(): Dataset
+    {
+        $project = $this->addProject();
+        $family = $this->addDatasetFamily();
+
+        $dataset = new Dataset('obs_cat');
+        $dataset->setTableRef('v_obs_cat');
+        $dataset->setLabel('Obscat label');
+        $dataset->setDescription('Obscat description');
+        $dataset->setDisplay(10);
+        $dataset->setCount(10000);
+        $dataset->setVo(false);
+        $dataset->setDataPath('/mnt/obs_cat');
+        $dataset->setSelectableRow(false);
+        $dataset->setProject($project);
+        $dataset->setDatasetFamily($family);
+        $this->entityManager->persist($dataset);
+        $this->entityManager->flush();
+        return $dataset;
+    }
+
+    private function addOutputCategories(): array
+    {
+        $outputFamily = $this->addOutputFamily();
+
+        $outputCategory1 = new OutputCategory();
+        $outputCategory1->setLabel('Default output category');
+        $outputCategory1->setDisplay(10);
+        $outputCategory1->setOutputFamily($outputFamily);
+        $this->entityManager->persist($outputCategory1);
+
+        $outputCategory2 = new OutputCategory();
+        $outputCategory2->setLabel('My output category');
+        $outputCategory2->setDisplay(20);
+        $outputCategory2->setOutputFamily($outputFamily);
+        $this->entityManager->persist($outputCategory2);
+
+        $this->entityManager->flush();
+        return array($outputCategory1, $outputCategory2);
+    }
+}
-- 
GitLab