diff --git a/.env_dist b/.env_dist new file mode 100644 index 0000000000000000000000000000000000000000..77eb68f731eb584245173e5048e7b15df422c72e --- /dev/null +++ b/.env_dist @@ -0,0 +1,14 @@ +# Modele du fichier '.env', definissant variables "d'environnement" utilisees dans divers fichiers de src/ et de db/. +# (Ces variables sont chargees par le package dotenv dans `process.env`, dans les fichiers .js et .ts ou dotenv.config() est appele) +#Copier et renommer .env + +TARGET_ENV=development +PORT=3000 +DB_USER=sigma +DB_PASSWD=sigmapw +DB_HOST=localhost +#DB_HOST=129.201.104.10 # for remote database +#DB_HOST=127.0.0.1 # for devs using a port forwarding +FRONTEND_SERVER_URL=http://localhost:8888 +#LDAP_URI= (loaded from ldap_config.json!) +#TEST_PORT=3001 (used only in tests/, not yet implemented) diff --git a/.eslintrc.json b/.eslintrc.json index eb8ccc675167e07c62c62873ac622b57eb1af79d..705be3165200e9112c02446561cad30da4422c99 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,7 +1,8 @@ { "env": { "es6": true, - "node": true + "node": true, + "mocha": true }, "extends": "eslint:recommended", "parserOptions": { diff --git a/.gitignore b/.gitignore index 3ce63dd5af9b76459ed762c71406e0496434523f..5e522f55230586043cddad095cc97aaa28902fb1 100644 --- a/.gitignore +++ b/.gitignore @@ -29,9 +29,6 @@ bower_components # node-waf configuration .lock-wscript -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release - # Dependency directories node_modules/ jspm_packages/ @@ -69,8 +66,8 @@ typings/ # Generated files doc/ build/ +tsbuild/ # Config files ldap_credentials.json -ldap_connexion_config.json .env \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 8fb849a5d2cac8a5bba278a4d620d1950b7708d2..38ebd7e47cdf243b39528724c8340891ff320d04 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,7 +10,7 @@ COPY package*.json ./ # Install app dependencies RUN npm install --only=production && npm i -g knex -ARG ldap_uri=ldap://frankiz.eleves.polytechnique.fr:389 +ARG ldap_uri=ldap://bloaziadur.eleves.polytechnique.fr:389 ENV LDAP_URI=${ldap_uri} ENV HOST=0.0.0.0 ENV NODE_ENV=production diff --git a/README.md b/README.md index 0252299cfd73570d02d228f8dc7d0bd650921dd5..7d707f19bc376c736c117e141ab575768522cd58 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,10 @@ Ce dépôt contient le _backend_ de Sigma, le successeur de Frankiz, un site ét À terme, ce projet doit tourner sur un serveur du BR et servir d'API à un _frontend_ React *au code séparé et documenté séparément* toute les données nécessaires à son bon fonctionnement (authentification, appartenance à un groupe, droits de visibilité...). Le dépôt pour le serveur front se trouve ici : <https://gitlab.binets.fr/br/sigma-frontend> (on l'appellera indifféremment serveur front, front ou frontend...) +Le but des lignes qui suivent est de permettre au lecteur de rapidement mettre en place et lancer un sigma local et se familiariser avec son administration. Comment obtenir la documentation détaillée du projet est expliqué à la fin de ce document. + +Pour avoir un guide détaillé de l'installation de l'environnement de dev, voir [ce carnet]() + Pour obtenir une copie du projet, cloner le dépôt par : ```bash git clone git@gitlab.binets.fr:br/sigma-backend.git @@ -14,26 +18,111 @@ git clone git@gitlab.binets.fr:br/sigma-backend.git git clone https://gitlab.binets.fr/br/sigma-backend.git ``` -## Démarrer le serveur (en mode développement) +## Installer les dépendances *npm* +On utilise un serveur node.js avec [express.js](https://expressjs.com/). [^server] +Utiliser Node.js permet d'utiliser facilement [*npm*](https://www.npmjs.com/) (Node Package Manager). Une "dépendance" est un package utilisé dans le projet. + +[^server]: il est configuré dans [`app.ts`](./src/app.ts) puis lancé sur le port 3000 dans [`index.ts`](../src/index.ts). + +On trouve la liste des dépendances dans [`package.json`](../package.json). Express est un exemple de dépendance normale, utilisée en production ; nodemon et ESLint (voir infra) sont des dépendances dev (`devDependencies`), utilisées seulement en mode développement. -### Installer les dépendances -Installer les dépendances (spécifiées dans `package.json`) : +Les dépendances s'installent avec `npm install`. Cette commande a deux comportements possibles selon la valeur de la variable `NODE_ENV` (vérifier avec la commande `echo "$NODE_ENV"`): +* si `NODE_ENV` n'est pas configuré : on installe tout +* si `NODE_ENV` == `development` : on installe tout +* si `NODE_ENV` == `production` : on n'installe pas les dépendances développeur + +Pour installer les dépendances spécifiées dans `package.json` il faut donc lancer : ```bash npm install ``` -Certaines dépendances doivent être installées globalement : -```bash -npm install -g knex -npm install -g webpack -npm install -g eslint -``` -Installer PostgreSQL : +Les dépendances principales utilisées sont +- *knex.js*, qui permet de construire des requêtes SQL facilement, +- *GraphQL*, qui fournit une couche d'abstraction pour l'échange de données frontend-backend, +- *ldap.js*, qui permet d'interroger un serveur LDAP, +- *webpack*, qui compile et optimise tout le code source javascript en un `bundle.js`, +- *ESLint* et *TSLint*, pour le développement, outils de vérification syntaxique. + +Et une dépendance supplémentaire, PostgreSQL (linux est supposé) : ```bash sudo apt install postgresql ``` -### Setup la BDD PostgreSQL +## Configuration +L'API est conçue pour êtes modulaire et pour fonctionner dans plusieurs environnements. + +On peut donc le configurer via des fichiers ou des variables d'environnement. En deux mots : + +* [`jsdoc_config.json`](../jsdoc_config.json), [`Dockerfile`](../Dockerfile), [`.dockerignore`](../.dockerignore), [`.gitignore`](../.gitignore), [`.eslintignore`](../.eslintignore), [`webpack.config.js`](../webpack.config.js) : transparents +* [`ldap_config.json`](../ldap_config.json) : noms champs du LDAP +* [`ldap_credentials.json`](../ldap_credentials.json) : paramètres de connexion secrets au LDAP +* [`.estlintrc.json`](../.eslintrc.json) : ESLINT ou à quel point cancériser le développeur +* [`.gitattributes`](../.gitattributes) : terminaison de fichiers +* [`.gitlab-ci.yml`](../.gitlab-ci.yml) : pipeline gitlab +* [`package.json`](../package.json) et [`package-lock.json`](../package-lock.json) : gestion des dépendances usuel +* [`tsconfig.json`](../tsconfig.json) : configure la compilation de fichiers Typescript en Javascript +* [`tslint.json`](../tslint.json) : configure tslint, utilisé plutôt que tsc dans le projet final +* [`.env`](../.env) : définit les variables d'environnement et ports utilisés... + +Certains de ces fichiers de configurations ont une version "distribution" en "_dist" qui permet de les partager (le reste du temps ils sont dans le .gitignore), quitte à les renommer et à les modifier en local. + +### Configuration LDAP +L'API de Sigma nécessite de se connecter au LDAP Frankiz, à la fois pour obtenir des données et pour l'authentification des utilisateurs. Cela est fait à l'aide de la librairie [ldapjs](http://ldapjs.org) pour faire les requêtes au LDAP et [passportJS](http://www.passportjs.org/) pour l'authentification. + +* La configuration LDAP de base se situe dans [ldap_config.json](../ldap_config.json). +* Les identifiants utilisés pour authentifier le serveur au LDAP sont dans [ldap_credentials.json](../ldap_credentials.json). Ils prennent la forme suivante: +```json +{ + "dn": "uid=<username>,ou=eleves,dc=frankiz,dc=net", + "passwd": "<password>" +} +``` +On peut s'inspirer de [ldap_credentials_dist.json](../ldap_credentials_dist.json). +* Elle est importée dans l'application depuis [src/ldap/internal/config.ts](../src/ldap/internal/config.ts). +* Si la variable d'environnement `LDAP_URI` est définie, l'adresse où trouver le LDAP est remplacée. + +Le LDAP de Frankiz est sous OpenLDAP, qui a l'avantage d'être largement utilisée, documentée sur Internet, compatible avec des lecteurs génériques comme [JXplorer](http://jxplorer.org/) et gérant ses propres logs (voir [ce blog](https://www.vincentliefooghe.net/content/openldap-gestion-des-logs)). + +**Exemple** + +Si on développe en dehors du plâtal et qu'on ouvre un proxy SSH avec _port forwarding_ du LDAP de Frankiz (<frankiz.polytechnique.fr:389>) vers <localhost:8389>, on s'y connecte en définissant : `LDAP_URI=ldap://localhost:8389`, soit en faisant `export LDAP_URI=...`, soit en écrivant un fichier `.env`. Le fichier `config.js` s'occupe du reste. + +### Variables d'environnement + +| **Variable** | **Description** | **Défaut** | +| ------ | ------ | ----- | +| NODE_ENV | Type de l'environnement pour node | `development` | +| TARGET_ENV | Type de l'environnement ciblé : `development`, `staging` ou `production` | [.env](../.env) | +| HOST | Addresse sur laquelle le serveur écoute des requêtes. | [index.ts](../src/index.ts) | +| PORT | Port sur lequel le serveur écoute | [.env](../.env) | +| LDAP_URI | URI vers le serveur LDAP. | [ldap_config.json](../ldap_config.json) | +| DB_HOST | Addresse de la base de données. | [.env](../.env) | +| DB_USER | Utilisateur de la BDD | [.env](../.env) | +| DB_PASSWD | Mot de passe de la BDD | [.env](../.env) | + +Certaines variables doivent etre définies dans un fichier `.env`. On peut se contenter de recopier [.env_dist](../.env_dist) avec `cp .env_dist .env`. + +Par ailleurs, on peut définir ces variables d'environnement, **dans l'ordre décroissant de priorité :** + +* dans sa session de terminal (équivalent à `docker run -e KEY=value`) : + ```bash + export KEY=value + ``` +* au moment de lancer l'application : + ```bash + KEY=value npm start + ``` +* dans un fichier [`.env`](https://www.npmjs.com/package/dotenv) : + ```dotenv + KEY1=value1 + KEY2=value2 + ... + ``` + + +## Setup la BDD PostgreSQL +La BDD PostgreSQL est utilisée pour stocker permissions, écoles des utilisateurs, annonces et événements. + Créer un rôle PostgreSQL "sigma" : ```bash sudo -u postgres -s @@ -46,10 +135,13 @@ Créer une base de données PostgreSQL "sigma_dev" : createdb sigma_dev -U sigma -W ``` - Si vous n'arrivez pas à vous connecter (`createdb: could not connect to database template1: FATAL: Peer authentication failed for user`) : mettre à jour votre fichier `pg_hba.conf`. - - voir https://stackoverflow.com/questions/1471571/how-to-configure-postgresql-for-the-first-time -- Si vous souhaitez utiliser d'autres noms que "sigma" et "sigma_dev" : ça ne pose pas de problème, il vous faudra simplement modifier `./db/knexfile.js` et `./db/knex_init.js`. + - `sudo nano /etc/postgresql/<version>/main/pg_hba.conf` (en tant qu'utilisateur normal) + - remplacer tous les `peer` par `md5` + - redémarrer le serveur postgres : `sudo /etc/init.d/postgresql restart` +- Si vous souhaitez utiliser d'autres noms que "sigma" et "sigma_dev" : ça ne pose pas de problème, il vous faudra simplement modifier [.env](../.env). +- Sortir de l'utilisateur `postgres` avec CTRL + d -Exécuter les *migrations* et les *seeds* de knex : +Exécuter les *migrations* et les *seeds* de knex (dans le dossier `db`) : ```bash # construire le schéma de la BDD, i.e. définir les tables et leur structure. knex migrate:latest @@ -58,8 +150,10 @@ knex migrate:latest knex seed:run ``` -### Démarrer le serveur -Dire à webpack de build le projet (build le bundle `./build/bundle.js`) : +Voilà , vous avez une base de données à jour ! + +## Démarrer le serveur +Dire à webpack de build le projet (build le bundle `../build/bundle.js`) : ```bash npm run dev # en mode developpement # ou @@ -72,9 +166,8 @@ npm run start # ou le raccourci: npm start ``` Comme indiqué dans src/index.js, ceci lance un serveur servant l'application express sur le port 3000. -## Déployer dans un conteneur Docker - -L'image Docker est définie dans [`Dockerfile`](./Dockerfile). Il s'agit d'une distro Alpine avec Node.js et libstdc++. Lors du _build_ les dépendances _runtime_ dont dépend le `bundle.js` sont installées. +## Alternative : déployer dans un conteneur Docker +L'image Docker est définie dans [`Dockerfile`](../Dockerfile). Il s'agit d'une distro Alpine avec Node.js et libstdc++. Lors du _build_ les dépendances _runtime_ dont dépend le `bundle.js` sont installées. Compiler l'image : ```bash @@ -123,80 +216,26 @@ Les dépendances principales utilisées sont - *ESlint*, pour le développement, outil de vérification syntaxique. ## Scripts - -Les scripts sont des instructions en ligne de commande que l'on peut faire tourner avec la commande `npm run`. Ce sont des raccourcis pour gagner du temps sur des opérations un peu longues. Ils sont définis dans [`package.json`](./package.json). +Les scripts sont des instructions en ligne de commande que l'on peut faire tourner avec la commande `npm run`. Ce sont des raccourcis pour gagner du temps sur des opérations un peu longues. Ils sont définis dans [`package.json`](../package.json). Les plus importants sont détaillées ci-dessous : - `npm run build` : transpiler avec Webpack, en mode production - `npm run dev` : idem, mais en mode développement - `npm run watch` : idem, mais retranspile automatiquement dès que le code est modifié. - `npm run start` : lancer un serveur Node avec nodemon +- `npm run start_prod` : lancer le serveur avec Node - `npm run doc` : générer la doc JSDoc -- `npm run lint` : vérifier la syntaxe de tous les fichiers .js et .ts du dossier src/ +- `npm run lint` : discontinué +- `npm run eslint` : vérifier la syntaxe de tous les fichiers .js du dossier src/ +- `npm run tslint` : vérifier la syntaxe de tous les fichiers .ts du dossier src/ +- `npm run tsfix` : vérifie et corrige +- `npm run tsc` : compile le code TypeScript +- `npm run test` : démarre les tests unitaires -`npm run start` démarre en fait le serveur buildé [`build/bundle.js`](build/bundle.js) avec [nodemon](https://nodemon.io/), un outil de dév qui le redémarre automatiquement après toute modification *du bundle*. +`npm run start` démarre en fait le serveur buildé [`build/bundle.js`](../build/bundle.js) avec [nodemon](https://nodemon.io/), un outil de dév qui le redémarre automatiquement après toute modification *du bundle*. Donc, lancer `npm run watch` dans un terminal et `npm run start` dans un autre permet de rebuilder **et** relancer automatiquement le serveur, après toute modification *du code source*. -## Configuration - -L'API est conçue pour êtes modulaire et pour fonctionner dans plusieurs environnements. - -On peut donc le configurer via des fichiers ou des variables d'environnement. - -### Configuration LDAP - -L'API de Sigma nécessite de se connecter au LDAP Frankiz, à la fois pour obtenir des données et pour l'authentification des utilisateurs. Cela est fait à l'aide de la librairie [ldapjs](http://ldapjs.org) pour faire les requêtes au LDAP et [passportJS](http://www.passportjs.org/) pour l'authentification. - -* La configuration LDAP de base se situe dans [ldap_config.json](ldap_config.json). -* Les identifiants utilisés que authentifier le serveur au LDAP sont dans [ldap_credentials.json](./ldap_credentials.json). Ils prennent la forme suivante: -```json -{ - "dn": "uid=<username>,ou=eleves,dc=frankiz,dc=net", - "passwd": "<password>" -} -``` -* Elle est importée dans l'application depuis [src/ldap/config.ts](src/ldap/config.ts). -* Si la variable d'environnement `LDAP_URI` est définie, l'adresse où trouver le LDAP est remplacée. - -**Exemple** - -Si on développe en dehors du plâtal et qu'on ouvre un proxy SSH avec _port forwarding_ du LDAP de Frankiz (<frankiz.polytechnique.fr:389>) vers <localhost:8389>, on s'y connecte en définissant - -``` -LDAP_URI=ldap://localhost:8389 -``` - -soit en faisant `export LDAP_URI=...`, soit en écrivant un fichier `.env`. Le fichier `config.js` s'occupe du reste. - -### Variables d'environnement - -| **Variable** | **Description** | **Défaut** (`ldap_config.json`) | -| ------ | ------ | ----- | -| LDAP_URI | URI vers le serveur LDAP. | <ldap://frankiz.eleves.polytechnique.fr:389> | -| TARGET_ENV | Type de l'environnement ciblé : `development`, `staging` ou `production` | `development` | -| HOST | Addresse sur laquelle le serveur écoute des requêtes. | `localhost` en développement, `0.0.0.0` en staging/prod. | -| DB_HOST | Addresse de la base de données. | `localhost` ou `129.104.201.10` en staging/prod. | -| DB_PASSWD | Mot de passe de la BDD | `password` | - -On peut définir ces variables d'environnement, **dans l'ordre décroissant de priorité :** - -* dans sa session de terminal (équivalent à `docker run -e KEY=value`) : - ```bash - export KEY=value - ``` -* au moment de lancer l'application : - ```bash - KEY=value npm start - ``` -* dans un fichier [`.env`](https://www.npmjs.com/package/dotenv) : - ```dotenv - KEY1=value1 - KEY2=value2 - ... - ``` - - ## Panneau d'administration Il est accessible par navigateur au path `/adminview/admin` ; n'importe quel path devrait rediriger dessus. @@ -226,11 +265,10 @@ Il s'agit du même `/graphql` que l'_endpoint_ de l'API, mais le serveur est con ## Documentation +La documentation détaillée du projet est [ici](index.html). Elle a été compilée avec [JSDoc](http://usejsdoc.org/index.html) sous format hmtl selon le fichier de configuration [`configfile_doc.json`](./configfile_doc.json) à la racine du projet. -La documentation détaillée du projet est [ici](./doc/index.html). Elle a été compilée avec [JSDoc](http://usejsdoc.org/index.html) sous format hmtl selon le fichier de configuration [`configfile_doc.json`](./configfile_doc.json) à la racine du projet. - -Le script pour faire tourner [JSDoc](http://usejsdoc.org/index.html) et régénérer la documentation est : `npm run doc` +Le script pour faire tourner [JSDoc](http://usejsdoc.org/index.html) et régénérer la documentation est : `npm run doc`. Les liens ett images sont faits pour fonctionner en local, donc ne vous étonnez pas si vous avez des surprises en regardant ce README depuis git. Les fichiers compilés se situent dans [`doc`](.) avec leurs fichiers image. Par nature de l'outil JSDoc il est facile de documenter en détail des fonctions .js mais plus compliqué de documenter un fichier. -A la fin de ce fichier JSDoc rajoute les commentaires placés dans chacun des fichiers et des hyperliens pour y accéder. +A chaque execution JSDoc rajoute les commentaires placés dans chacun des fichiers dans la doc de façon structurée. Les notes en Markdown placés dans notes/ sont également rajoutées en tant que tutoriels (voir {@tutorial CONTRIBUTING}). diff --git a/assets/LDAPGroup.png b/assets/LDAPGroup.png new file mode 100644 index 0000000000000000000000000000000000000000..539fb1b637a38e70f129af814dba580734e52fc7 Binary files /dev/null and b/assets/LDAPGroup.png differ diff --git a/assets/LDAPUseCase.PNG b/assets/LDAPUseCase.PNG new file mode 100644 index 0000000000000000000000000000000000000000..6aae00183835ea38cf8255669f30f7450fd0ef5b Binary files /dev/null and b/assets/LDAPUseCase.PNG differ diff --git a/assets/LDAPUser.png b/assets/LDAPUser.png new file mode 100644 index 0000000000000000000000000000000000000000..69296169c96a45c9edd7c76be9d625fb341e14e6 Binary files /dev/null and b/assets/LDAPUser.png differ diff --git a/assets/LDAP_API.png b/assets/LDAP_API.png new file mode 100644 index 0000000000000000000000000000000000000000..af9bd2dccb790afa275c2d0d36f544c75974d940 Binary files /dev/null and b/assets/LDAP_API.png differ diff --git a/assets/auth.png b/assets/auth.png new file mode 100644 index 0000000000000000000000000000000000000000..fb358fa894a0fa0ba32e4dccc49ddab2df52f061 Binary files /dev/null and b/assets/auth.png differ diff --git a/assets/grapheDroits.jpg b/assets/grapheDroits.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b24b0d52bfa422eed3841d081aa83cb4db4b4f4e Binary files /dev/null and b/assets/grapheDroits.jpg differ diff --git a/assets/struct.png b/assets/struct.png new file mode 100644 index 0000000000000000000000000000000000000000..15d6d97d6964f37fb845fcc921a70346776011db Binary files /dev/null and b/assets/struct.png differ diff --git a/db/knex_router.js b/db/knex_router.ts similarity index 61% rename from db/knex_router.js rename to db/knex_router.ts index 8911c2eae70bb922c2c31252cc2c91ce6c6f7711..3ce9bf2b228a4d96042af36d9033e3e280147a1e 100644 --- a/db/knex_router.js +++ b/db/knex_router.ts @@ -1,11 +1,11 @@ /** * @file Charge la configuration knexjs adaptée à l'environnement (production ou développement) puis exporte un objet knex permettant les requêtes SQL -*/ + */ require('dotenv').config(); -const environment = process.env.TARGET_ENV || 'development'; -const config = require('./knexfile')[environment]; +const environment = process.env.TARGET_ENV; +export const config = require('./knexfile')[environment]; console.log("Running Knex configuration '%s'", environment); -module.exports = require('knex')(config); +export default require('knex')(config); diff --git a/db/knexfile.js b/db/knexfile.js index dc6a3c7b5f3bf27855823251f975be9c346e545e..8b190e33fb928c76aba5262e0d191844825f7e0d 100644 --- a/db/knexfile.js +++ b/db/knexfile.js @@ -1,5 +1,5 @@ /** - * * @file Fichier de configuration Knex. Il spécifie la base de donnée à laquelle se connecter, son adresse, le nom + * @file Fichier de configuration Knex. Il spécifie la base de donnée à laquelle se connecter, son adresse, le nom * d'un utilisateur sur le serveur qui a les droits appropriés, et son mot de passe. * * Le fichier précise également où stocker les fichiers de migrations Knex ainsi que les _seeds_. @@ -8,8 +8,8 @@ * et c'est knex_router.js qui donne la configuration de knex.js à proprement parler (i.e. les méthodes knex.insert(...) dans le code javascript) * @author manifold */ -require('dotenv').config(); const path = require('path'); +require('dotenv').config({ path: path.resolve('..', './.env') }); const knexConfig = { migrations: { @@ -26,8 +26,8 @@ module.exports = { client: 'pg', connection: { host: 'localhost', - user: process.env.DB_USER || 'sigma', - password: process.env.DB_PASSWD || 'sigmapw', + user: process.env.DB_USER, + password: process.env.DB_PASSWD, database: 'sigma_dev', charset: 'utf8' }, @@ -36,9 +36,9 @@ module.exports = { staging: { client: 'pg', connection: { - host: process.env.DB_HOST || '129.201.104.10', - user: process.env.DB_USER || 'sigma', - password: process.env.DB_PASSWD || 'password', + host: process.env.DB_HOST, + user: process.env.DB_USER, + password: process.env.DB_PASSWD, database: 'sigma_staging', charset: 'utf8' }, diff --git a/db/migrations_v3/20181110191614_create_groups.js b/db/migrations/20181110191614_create_groups.js similarity index 100% rename from db/migrations_v3/20181110191614_create_groups.js rename to db/migrations/20181110191614_create_groups.js diff --git a/db/migrations_v3/20181110191757_create_associativetable_metagroup_memberships.js b/db/migrations/20181110191757_create_associativetable_metagroup_memberships.js similarity index 100% rename from db/migrations_v3/20181110191757_create_associativetable_metagroup_memberships.js rename to db/migrations/20181110191757_create_associativetable_metagroup_memberships.js diff --git a/db/migrations_v3/20181110192012_create_messages_announcements.js b/db/migrations/20181110192012_create_messages_announcements.js similarity index 100% rename from db/migrations_v3/20181110192012_create_messages_announcements.js rename to db/migrations/20181110192012_create_messages_announcements.js diff --git a/db/migrations_v3/20181110192431_create_messages_events.js b/db/migrations/20181110192431_create_messages_events.js similarity index 100% rename from db/migrations_v3/20181110192431_create_messages_events.js rename to db/migrations/20181110192431_create_messages_events.js diff --git a/db/migrations_v3/20181110192917_create_messages_private_posts.js b/db/migrations/20181110192917_create_messages_private_posts.js similarity index 100% rename from db/migrations_v3/20181110192917_create_messages_private_posts.js rename to db/migrations/20181110192917_create_messages_private_posts.js diff --git a/db/migrations_v3/20181110193153_create_messages_questions.js b/db/migrations/20181110193153_create_messages_questions.js similarity index 100% rename from db/migrations_v3/20181110193153_create_messages_questions.js rename to db/migrations/20181110193153_create_messages_questions.js diff --git a/db/migrations_v3/20181110193226_create_messages_answers.js b/db/migrations/20181110193226_create_messages_answers.js similarity index 100% rename from db/migrations_v3/20181110193226_create_messages_answers.js rename to db/migrations/20181110193226_create_messages_answers.js diff --git a/db/migrations_v3/20181110193517_create_requests_user_join_group.js b/db/migrations/20181110193517_create_requests_user_join_group.js similarity index 100% rename from db/migrations_v3/20181110193517_create_requests_user_join_group.js rename to db/migrations/20181110193517_create_requests_user_join_group.js diff --git a/db/migrations_v3/20181110193612_create_requests_group_join_metagroup.js b/db/migrations/20181110193612_create_requests_group_join_metagroup.js similarity index 100% rename from db/migrations_v3/20181110193612_create_requests_group_join_metagroup.js rename to db/migrations/20181110193612_create_requests_group_join_metagroup.js diff --git a/db/migrations_v3/20181110193656_create_requests_group_coauthor_event.js b/db/migrations/20181110193656_create_requests_group_coauthor_event.js similarity index 100% rename from db/migrations_v3/20181110193656_create_requests_group_coauthor_event.js rename to db/migrations/20181110193656_create_requests_group_coauthor_event.js diff --git a/db/migrations/20190202114345_create_announcements_authors.js b/db/migrations/20190202114345_create_announcements_authors.js new file mode 100644 index 0000000000000000000000000000000000000000..e3c8ee1b3c6a23a39d15b9bc59da00a0be2b23d3 --- /dev/null +++ b/db/migrations/20190202114345_create_announcements_authors.js @@ -0,0 +1,18 @@ + +exports.up = function (knex, Promise) { + return knex.schema.createTable('announcements_authors', function (table) { + table.increments('id'); //autoincrementing (non-nullable) primary key + + table.integer('mid').notNullable() + .references('mid').inTable('messages_announcements') + .onDelete('CASCADE'); //if announcement is deleted, also delete authors + + table.string('gid', 128).notNullable() + .references('gid').inTable('groups') + .onDelete('CASCADE'); //if group is deleted, also delete authors + }); +}; + +exports.down = function (knex, Promise) { + return knex.schema.dropTable('announcements_authors'); +}; diff --git a/db/migrations/20190202114346_create_announcements_recipients.js b/db/migrations/20190202114346_create_announcements_recipients.js new file mode 100644 index 0000000000000000000000000000000000000000..dc30eab645076009cc7e49a780307b4925a01f72 --- /dev/null +++ b/db/migrations/20190202114346_create_announcements_recipients.js @@ -0,0 +1,18 @@ + +exports.up = function (knex, Promise) { + return knex.schema.createTable('announcements_recipients', function (table) { + table.increments('id'); //autoincrementing (non-nullable) primary key + + table.integer('mid').notNullable() + .references('mid').inTable('messages_announcements') + .onDelete('CASCADE'); //if announcement is deleted, also delete recipients + + table.string('gid', 128).notNullable() + .references('gid').inTable('groups') + .onDelete('CASCADE'); //if group is deleted, also delete recipient + }); +}; + +exports.down = function (knex, Promise) { + return knex.schema.dropTable('announcements_recipients'); +}; diff --git a/db/migrations/20190202114347_create_events_authors.js b/db/migrations/20190202114347_create_events_authors.js new file mode 100644 index 0000000000000000000000000000000000000000..1619ccacf96cabe2725d4d92cce5bddc0099267c --- /dev/null +++ b/db/migrations/20190202114347_create_events_authors.js @@ -0,0 +1,18 @@ + +exports.up = function (knex, Promise) { + return knex.schema.createTable('events_authors', function (table) { + table.increments('id'); //autoincrementing (non-nullable) primary key + + table.integer('mid').notNullable() + .references('mid').inTable('messages_events') + .onDelete('CASCADE'); //if event is deleted, also delete authors + + table.string('gid', 128).notNullable() + .references('gid').inTable('groups') + .onDelete('CASCADE'); //if group is deleted, also delete authors + }); +}; + +exports.down = function (knex, Promise) { + return knex.schema.dropTable('events_authors'); +}; diff --git a/db/migrations/20190202114348_create_events_recipients.js b/db/migrations/20190202114348_create_events_recipients.js new file mode 100644 index 0000000000000000000000000000000000000000..fc8a157b18e50f7ac68d6a2334581e356d5c1b89 --- /dev/null +++ b/db/migrations/20190202114348_create_events_recipients.js @@ -0,0 +1,18 @@ + +exports.up = function (knex, Promise) { + return knex.schema.createTable('events_recipients', function (table) { + table.increments('id'); //autoincrementing (non-nullable) primary key + + table.integer('mid').notNullable() + .references('mid').inTable('messages_events') + .onDelete('CASCADE'); //if event is deleted, also delete recipients + + table.string('gid', 128).notNullable() + .references('gid').inTable('groups') + .onDelete('CASCADE'); //if group is deleted, also delete recipient + }); +}; + +exports.down = function (knex, Promise) { + return knex.schema.dropTable('events_recipients'); +}; diff --git a/db/migrations_v1/20180225001319_create_groups.js b/db/migrations_v1/20180225001319_create_groups.js deleted file mode 100644 index 952684150583e80f1b957c534025f2c0f661ff07..0000000000000000000000000000000000000000 --- a/db/migrations_v1/20180225001319_create_groups.js +++ /dev/null @@ -1,17 +0,0 @@ - -exports.up = function (knex, Promise) { - return knex.schema.createTable('groups', function (table) { - table.timestamp(true, true); - table.string('name').notNullable(); - table.string('uid',128).primary().notNullable(); - table.string('parent_uid',128); - table.foreign('parent_uid').references('groups.uid'); - table.string('website').defaultTo(''); - table.text('description').defaultTo(''); - table.enum('school', ['polytechnique', 'ensta', 'supoptique']).notNullable(); - }); -}; - -exports.down = function (knex, Promise) { - return knex.schema.dropTable('groups'); -}; diff --git a/db/migrations_v1/20180302130024_create_messages.js b/db/migrations_v1/20180302130024_create_messages.js deleted file mode 100644 index dead1ac7428fb9d313553611c737d0da84b93e47..0000000000000000000000000000000000000000 --- a/db/migrations_v1/20180302130024_create_messages.js +++ /dev/null @@ -1,14 +0,0 @@ - -exports.up = function(knex, Promise) { - return knex.schema.createTable('messages', function (table) { - table.timestamps(true,true); - table.increments('id'); - - table.string('title').notNullable(); - table.text('content'); - }); -}; - -exports.down = function(knex, Promise) { - return knex.schema.dropTable('messages'); -}; diff --git a/db/migrations_v1/20180302152113_requests.js b/db/migrations_v1/20180302152113_requests.js deleted file mode 100644 index 7fa068828355a24885361b81ff8f301954213e7b..0000000000000000000000000000000000000000 --- a/db/migrations_v1/20180302152113_requests.js +++ /dev/null @@ -1,37 +0,0 @@ - -exports.up = function(knex, Promise) { - return knex.schema.createTable('requests', function (table){ - table.timestamps(true, true); - table.increments('id'); - table.string('recipient').notNullable(); - table.text('message'); - }).then(()=>{ - return knex.schema.createTable('user_join_group', function (table) { - table.inherits('requests'); - table.string('useruid').notNullable(); - }).then(()=>{ - return knex.schema.createTable('group_join_event', function (table){ - table.inherits('requests'); - table.string('eventuid').notNullable(); - table.string('senderuid').notNullable(); - }).then(() => { - return knex.schema.createTable('your_group_host_event', function(table){ - table.inherits('requests'); - table.string('eventuid').notNullable(); - table.string('senderuid').notNullable(); - }); - }); - }); - }); -}; - -exports.down = function(knex, Promise) { - return knex.schema.dropTable('your_group_host_event').then(() => { - return knex.schema.dropTable('group_join_event').then(() => { - return knex.schema.dropTable('user_join_group').then(() => { - return knex.schema.dropTable('requests').then(() => { - }); - }); - }); - }); -}; diff --git a/db/migrations_v1/20180303192411_create_metaGroups.js b/db/migrations_v1/20180303192411_create_metaGroups.js deleted file mode 100644 index fc416ee3a7c68fa53b96a1365b65f255fd06f266..0000000000000000000000000000000000000000 --- a/db/migrations_v1/20180303192411_create_metaGroups.js +++ /dev/null @@ -1,46 +0,0 @@ - -/* -Une migrations pour rajouter dans la BDD les tables simple_group et meta_group -(cf la documentation de l'API GraphQL) -Les objets qui étaient jusqu'à présent stoqués dans la table groups -sont déplacés dans la table simple_groups -En cas de rollback, le déplacement se fait dans l'autre sens -(et les meta groupes sont perdus) - -Après cette migration, plus rien n'est sensé être mis directement dans la table groups -*/ - -exports.up = function(knex, Promise) { - return knex('groups').select().then( groups_content => { - return knex('groups').del().then(function () { - return knex.schema.table('groups', function (table){ - table.dropColumn('school'); - }).then(()=> { - return knex.schema.createTable('simple_groups', function (table){ - table.inherits('groups'); - table.enum('school', ['polytechnique', 'ensta', 'supoptique']).notNullable(); - }).then(() => { - return knex.schema.createTable('meta_groups', function (table){ - table.inherits('groups'); - }).then(function(){ - return knex('simple_groups').insert(groups_content); - }); - }); - }); - }); - }); -}; - -exports.down = function(knex, Promise) { - return knex('simple_groups').select().then(simple_groups_content => { - return knex.schema.dropTable('simple_groups').then(function (){ - return knex.schema.dropTable('meta_groups').then(function (){ - return knex.schema.table('groups', function (table){ - table.enum('school', ['polytechnique', 'ensta', 'supoptique']).notNullable(); - }).then(function (){ - return knex('groups').insert(simple_groups_content); - }); - }); - }); - }); -}; diff --git a/db/migrations_v1/20180304154225_group_type_column.js b/db/migrations_v1/20180304154225_group_type_column.js deleted file mode 100644 index f178a0a9ddf63ec1de5e2e6e050eb5ab4ea6af1c..0000000000000000000000000000000000000000 --- a/db/migrations_v1/20180304154225_group_type_column.js +++ /dev/null @@ -1,17 +0,0 @@ - -exports.up = function(knex, Promise) { - return knex.schema.table('groups', function(table) { - table.enum('type', ['simple', 'meta', 'error']).notNullable().defaultTo('error'); - }).then( () => { - return knex('simple_groups').update({type : "simple"}).then(() => { - return knex('meta_groups').update({type : "meta"}); - }); - }); - -}; - -exports.down = function(knex, Promise) { - return knex.schema.table('groups', function(table) { - table.dropColumn('type'); - }); -}; diff --git a/db/migrations_v1/20180305111321_metaGroup_member_table.js b/db/migrations_v1/20180305111321_metaGroup_member_table.js deleted file mode 100644 index b69354e738b906e9cea4d91ee0bc83b30dda2920..0000000000000000000000000000000000000000 --- a/db/migrations_v1/20180305111321_metaGroup_member_table.js +++ /dev/null @@ -1,13 +0,0 @@ - -exports.up = function(knex, Promise) { - return knex.schema.createTable('meta_group_membership', function (table){ - table.timestamp(true, true); - table.string('member_uid').notNullable(); - table.string('union_uid').notNullable(); - table.enum('status', ['admin', 'speaker', 'basic']).notNullable(); - }); -}; - -exports.down = function(knex, Promise) { - return knex.schema.dropTable('meta_group_membership'); -}; diff --git a/db/migrations_v1/20180307213043_fixes_and_messages.js b/db/migrations_v1/20180307213043_fixes_and_messages.js deleted file mode 100644 index e06d514c2cefa5d37bc4ea91516aa68b33dd5744..0000000000000000000000000000000000000000 --- a/db/migrations_v1/20180307213043_fixes_and_messages.js +++ /dev/null @@ -1,35 +0,0 @@ - -exports.up = async function(knex, Promise) { - await knex.schema.createTable('announcements', function (table){ - table.inherits('messages'); - table.specificType('authors', knex.raw('varchar(128)[]')); - table.integer('views').defaultTo(0); - }); - await knex.schema.createTable('events', function (table){ - table.inherits('messages'); - table.string('location'); - table.dateTime('start_time'); - table.dateTime('end_time'); - table.boolean('is_announcement'); - table.specificType('authors', knex.raw('varchar(128)[]')); - }); - await knex.schema.createTable('group_message_relationships', function(table){ - table.integer('message').notNullable(); - table.string('group').notNullable(); - table.enum('status', ['join', 'host', 'publish']).notNullable(); - }); - await knex.schema.createTable('user_participation', function(table){ - table.integer('event').notNullable(); - table.string('user_uid').notNullable(); - table.enum('user_db', ['ldap']).notNullable().defaultTo('ldap'); - }); - return; -}; - -exports.down = async function(knex, Promise) { - await knex.schema.dropTable('group_message_relationships'); - await knex.schema.dropTable('user_participation'); - await knex.schema.dropTable('events'); - await knex.schema.dropTable('announcements'); - return; -}; diff --git a/db/migrations_v1/20180309145434_supervision.js b/db/migrations_v1/20180309145434_supervision.js deleted file mode 100644 index 554e48a5497dfb3f73dfb509338eac738de057b2..0000000000000000000000000000000000000000 --- a/db/migrations_v1/20180309145434_supervision.js +++ /dev/null @@ -1,14 +0,0 @@ - -exports.up = function(knex, Promise) { - return knex.schema.createTable('taken_rights', function(table){ - table.timestamp(true, true); - table.string('user_uid').notNullable(); - table.string('group_uid').notNullable(); - table.string('justification'); - }); - -}; - -exports.down = function(knex, Promise) { - return knex.schema.dropTable('taken_rights'); -}; diff --git a/db/migrations_v1/20180316003119_create_posts.js b/db/migrations_v1/20180316003119_create_posts.js deleted file mode 100644 index 1207a75925c0c0bfaa8fadd8fe53e9f5b78b49dd..0000000000000000000000000000000000000000 --- a/db/migrations_v1/20180316003119_create_posts.js +++ /dev/null @@ -1,11 +0,0 @@ - -exports.up = async function(knex, Promise) { - await knex.schema.createTable('posts', function(table) { - table.inherits('messages'); - }); - return; -}; - -exports.down = async function(knex, Promise) { - return knex.schema.dropTable('posts'); -}; diff --git a/db/migrations_v1/20180413024152_new_group_message_relations.js b/db/migrations_v1/20180413024152_new_group_message_relations.js deleted file mode 100644 index 28c50374cd898bda5dd45ec196224e02510712bf..0000000000000000000000000000000000000000 --- a/db/migrations_v1/20180413024152_new_group_message_relations.js +++ /dev/null @@ -1,25 +0,0 @@ - -exports.up = async function(knex, Promise) { - let relations = await knex.select().from('group_message_relationships'); - await knex('group_message_relationships').del(); - await knex.schema.table('group_message_relationships', table => { - table.dropColumn('status'); - }); - await knex.schema.table('group_message_relationships', table => { - table.enum('status', ['join', 'host', 'publish', 'recieve', 'internal']); - }); - return knex('group_message_relationships').insert(relations); -}; - -exports.down = async function(knex, Promise) { - let relations = await knex.select().from('group_message_relationships') - .whereIn('status', ['join', 'host', 'publish']); - await knex('group_message_relationships').del(); - await knex.schema.table('group_message_relationships', table => { - table.dropColumn('status'); - }); - await knex.schema.table('group_message_relationships', table => { - table.enum('status', ['join', 'host', 'publish']); - }); - return knex('group_message_relationships').insert(relations); -}; diff --git a/db/migrations_v1/20180415160405_message_types.js b/db/migrations_v1/20180415160405_message_types.js deleted file mode 100644 index ff31bb0b75bae49f6c18c13349b1c0b017ef1670..0000000000000000000000000000000000000000 --- a/db/migrations_v1/20180415160405_message_types.js +++ /dev/null @@ -1,29 +0,0 @@ - -exports.up = async function(knex, Promise) { - await knex.schema.createTable('private_posts', function(table){ - table.inherits('messages'); - table.string('author_uid', 128).notNullable(); - table.enum('author_db', ['ldap']).notNullable().defaultTo('ldap'); - table.string('recipient_uid', 128).notNullable(); - }); - await knex.schema.createTable('questions', function(table){ - table.inherits('messages'); - table.string('author_uid', 128).notNullable(); - table.enum('author_db', ['ldap']).notNullable().defaultTo('ldap'); - table.string('recipient_uid', 128).notNullable(); - }); - await knex.schema.createTable('answers', function(table){ - table.inherits('messages'); - table.string('author_uid', 128).notNullable(); - table.string('recipient_uid', 128).notNullable(); - table.string('for_question', 128).notNullable(); - }); - return; -}; - -exports.down = async function(knex, Promise) { - await knex.schema.dropTable('answers'); - await knex.schema.dropTable('questions'); - await knex.schema.dropTable('private_posts'); - return; -}; diff --git a/db/migrations_v1/20180530182824_drop_reciepient_column.js b/db/migrations_v1/20180530182824_drop_reciepient_column.js deleted file mode 100644 index 90a48e9ae803a33ee89180fb4aa98b3b9b9f1f90..0000000000000000000000000000000000000000 --- a/db/migrations_v1/20180530182824_drop_reciepient_column.js +++ /dev/null @@ -1,52 +0,0 @@ - -exports.up = async function(knex, Promise) { - let recipients = await knex('private_posts').select('id', 'recipient_uid'); - for(entry of recipients){ - knex('group_message_relationships').insert({ - message : entry.id, - group : entry.recipient_uid, - status : "recieve" - }); - } - await knex.schema.table('private_posts', (table) => { - table.dropColumn('recipient_uid'); - }); - - recipients = await knex('questions').select('id', 'recipient_uid'); - for(entry of recipients){ - knex('group_message_relationships').insert({ - message : entry.id, - group : entry.recipient_uid, - status : "recieve" - }); - } - await knex.schema.table('questions', (table) => { - table.dropColumn('recipient_uid'); - }); - - recipients = await knex('answers').select('id', 'recipient_uid'); - for(entry of recipients){ - knex('group_message_relationships').insert({ - message : entry.id, - group : entry.recipient_uid, - status : "recieve" - }); - } - await knex.schema.table('answers', (table) => { - table.dropColumn('recipient_uid'); - }); - -}; - -exports.down = async function(knex, Promise) { - await knex.schema.table('private_posts', (table) => { - table.string("recipient_uid"); - }); - await knex.schema.table('questions', (table) => { - table.string("recipient_uid"); - }); - await knex.schema.table('answers', (table) => { - table.string("recipient_uid"); - }); - -}; diff --git a/db/migrations_v1/20180530191430_fix_spelling_recieve.js b/db/migrations_v1/20180530191430_fix_spelling_recieve.js deleted file mode 100644 index e816c31d7826661d4eedd1a8f9d52d3872c4e5b2..0000000000000000000000000000000000000000 --- a/db/migrations_v1/20180530191430_fix_spelling_recieve.js +++ /dev/null @@ -1,36 +0,0 @@ - -exports.up = async function(knex, Promise) { - let relations = await knex.select().from('group_message_relationships'); - await knex('group_message_relationships').del(); - await knex.schema.table('group_message_relationships', table => { - table.dropColumn('status'); - }); - await knex.schema.table('group_message_relationships', table => { - table.enum('status', ['join', 'host', 'publish', 'receive', 'internal']); - }); - let entry; - for(entry of relations){ - if(entry.status == "recieve"){ - entry.status = "receive"; - } - } - return knex('group_message_relationships').insert(relations); -}; - -exports.down = async function(knex, Promise) { - let relations = await knex.select().from('group_message_relationships'); - await knex('group_message_relationships').del(); - await knex.schema.table('group_message_relationships', table => { - table.dropColumn('status'); - }); - await knex.schema.table('group_message_relationships', table => { - table.enum('status', ['join', 'host', 'publish', 'recieve', 'internal']); - }); - let entry; - for(entry of relations){ - if(entry.status == "receive"){ - entry.status = "recieve"; - } - } - return knex('group_message_relationships').insert(relations); -}; diff --git a/db/migrations_v1/20180831165621_question_for_answer.js b/db/migrations_v1/20180831165621_question_for_answer.js deleted file mode 100644 index 6464eabb459359f66e2b9c1e57df8c0be7e30da1..0000000000000000000000000000000000000000 --- a/db/migrations_v1/20180831165621_question_for_answer.js +++ /dev/null @@ -1,12 +0,0 @@ - -exports.up = async function(knex, Promise) { - await knex.schema.table('questions', table => { - table.string('for_answer', 128); - }); -}; - -exports.down = async function(knex, Promise) { - await knex.schema.table('questions', table => { - table.dropColumn('for_answer'); - }); -}; diff --git a/db/migrations_v2/20181110100101_create_groups.js b/db/migrations_v2/20181110100101_create_groups.js deleted file mode 100644 index 7f56bb994110ae4777981444a50dbfd8ec481dd7..0000000000000000000000000000000000000000 --- a/db/migrations_v2/20181110100101_create_groups.js +++ /dev/null @@ -1,24 +0,0 @@ - -// Special groups (implemented by the seeds, specified as a note here) : -// 'root' group, ancestor of all SimpleGroups. (simple group) -// 'noone' group, direct child of 'root' group. Serves as recipient of all unpublished Messages -// (e.g. drafts, messages with deleted recipient...) (simple group) - -exports.up = function (knex, Promise) { - return knex.schema.createTable('groups', function (table) { - table.string('gid',128).primary().notNullable(); //primary key *human readable*! should, but does not need to, match exactly column 'name' - table.timestamps(true, true); //adds timestamptz-type (a PostgresQL data type) updated_at and created_at columns - table.enum('type', ['simple', 'meta', 'error']).notNullable().defaultTo('error'); - - table.string('parent_gid',128).notNullable().defaultTo('root') - .references('gid').inTable('groups') - .onDelete('SET DEFAULT'); //if parent group is deleted, become child of root group - table.string('name').notNullable(); - table.string('website'); //.defaultTo(''); <-- default to null instead - table.text('description').defaultTo('Hello world! <Insert group description here>'); - }); -}; - -exports.down = function (knex, Promise) { - return knex.schema.dropTable('groups'); -}; diff --git a/db/migrations_v2/20181110100202_create_childtable_simple_groups.js b/db/migrations_v2/20181110100202_create_childtable_simple_groups.js deleted file mode 100644 index ead227bff989ece79ccb560ca093c8173333e8c7..0000000000000000000000000000000000000000 --- a/db/migrations_v2/20181110100202_create_childtable_simple_groups.js +++ /dev/null @@ -1,23 +0,0 @@ -//Après cette migration, plus rien n'est sensé être mis directement dans la table groups - -let schoolEnum = [ - 'polytechnique', - 'ensta', - 'supoptique', - 'ensae', - 'centrale', - 'enscachan', - 'otherschool', - 'notaschool' -]; - -exports.up = function (knex, Promise) { - return knex.schema.createTable('simple_groups', function (table) { - table.inherits('groups'); - table.enum('school', schoolEnum).notNullable(); - }); -}; - -exports.down = function (knex, Promise) { - return knex.schema.dropTable('simple_groups'); -}; diff --git a/db/migrations_v2/20181110100303_create_childtable_meta_groups.js b/db/migrations_v2/20181110100303_create_childtable_meta_groups.js deleted file mode 100644 index 44108910206db39ad71c6bc3af7ea4689c6cdb0f..0000000000000000000000000000000000000000 --- a/db/migrations_v2/20181110100303_create_childtable_meta_groups.js +++ /dev/null @@ -1,10 +0,0 @@ - -exports.up = function (knex, Promise) { - return knex.schema.createTable('meta_groups', function (table) { - table.inherits('groups'); - }); -}; - -exports.down = function (knex, Promise) { - return knex.schema.dropTable('meta_groups'); -}; diff --git a/db/migrations_v2/20181110100404_create_associativetable_metagroup_memberships.js b/db/migrations_v2/20181110100404_create_associativetable_metagroup_memberships.js deleted file mode 100644 index d803d552d0f9edaf1d52a86e990f56344e0fb33a..0000000000000000000000000000000000000000 --- a/db/migrations_v2/20181110100404_create_associativetable_metagroup_memberships.js +++ /dev/null @@ -1,34 +0,0 @@ -// Using associative tables is the recommended way of representing many-to-many relationships. -//https://en.wikipedia.org/wiki/Many-to-many_%28data_model%29 -//http://web.csulb.edu/colleges/coe/cecs/dbdesign/dbdesign.php?page=manymany.php - -let rightsEnum = [ - 'admin', - 'speaker', - 'member' -]; - -exports.up = function (knex, Promise) { - return knex.schema.createTable('metagroup_memberships', function (table) { - table.timestamps(true, true); - table.string('simple_group_gid',128).notNullable() - .references('gid').inTable('groups' /*'simple_groups'*/) - .onDelete('CASCADE'); //delete row from group_memberships if member is deleted - table.string('meta_group_gid',128).notNullable() - .references('gid').inTable('groups' /*'meta_groups'*/) - .onDelete('CASCADE'); - /* - ^ as stated in https://www.postgresql.org/docs/10/static/ddl-inherit.html#DDL-INHERIT-CAVEATS, - "A serious limitation of the inheritance feature is that indexes (including unique constraints) and foreign key constraints only apply to single tables, not to their inheritance children." - it is not possible (with PostgresQL v10) to use both inheritance and foreign keys at once. - until it is, or a workaround is found, we simply don't maintain child-level referential integrity - and we hope all the rest will work, e.g. simple_group_gids will indeed refer to simple groups, and idem for meta_group_gids. - TODO: maybe, check if PostgresQL's latest version enables this feature? - */ - table.enum('rights', rightsEnum).notNullable(); //TODO: this is not used by GraphQL schema! - }); -}; - -exports.down = function (knex, Promise) { - return knex.schema.dropTable('metagroup_memberships'); -}; diff --git a/db/migrations_v2/20181110110101_create_messages.js b/db/migrations_v2/20181110110101_create_messages.js deleted file mode 100644 index 2efdc03360ea32debdbb21a3ecc76ba796338035..0000000000000000000000000000000000000000 --- a/db/migrations_v2/20181110110101_create_messages.js +++ /dev/null @@ -1,18 +0,0 @@ - -exports.up = function (knex, Promise) { - return knex.schema.createTable('messages', function (table) { - table.increments('mid'); //autoincrementing (non-nullable) primary key - table.timestamps(true, true); //adds timestamptz-type (a PostgresQL data type) updated_at and created_at columns - - table.string('recipient',128).notNullable().defaultTo('noone') - .references('gid').inTable('groups') - .onDelete('SET DEFAULT'); //if recipient is deleted, direct to the special "no-one" group - //'authors' can be a Group or a User, so specified in childtables. - table.string('title').notNullable(); - table.text('content'); - }); -}; - -exports.down = function (knex, Promise) { - return knex.schema.dropTable('messages'); -}; diff --git a/db/migrations_v2/20181110110202_create_childtable_announcements.js b/db/migrations_v2/20181110110202_create_childtable_announcements.js deleted file mode 100644 index 8c9e8c95cea9adc33272fa9589a46b3734978e2e..0000000000000000000000000000000000000000 --- a/db/migrations_v2/20181110110202_create_childtable_announcements.js +++ /dev/null @@ -1,22 +0,0 @@ - -exports.up = function(knex, Promise) { - return knex.schema.createTable('announcements', function (table) { - table.inherits('messages'); - //TODO: for now, we support only 1 author (where as graphql schema indicates support for [Group] authors) - table.string('author',128).notNullable() - .references('gid').inTable('groups') - .onDelete('CASCADE'); //delete message if author is deleted - table.integer('views').defaultTo(0); - /* - we cannot declare this column yet, as table 'events' is not yet created. - it will be declared in the migration for the 'events' childtable. - table.integer('for_event') - .references('mid').inTable('events') - .onDelete('SET NULL'); - */ - }); -}; - -exports.down = function(knex, Promise) { - return knex.schema.dropTable('announcements'); -}; diff --git a/db/migrations_v2/20181110110303_create_childtable_events.js b/db/migrations_v2/20181110110303_create_childtable_events.js deleted file mode 100644 index 7d9e11e5391af4678b0b7b9d468c16b4b5c532b7..0000000000000000000000000000000000000000 --- a/db/migrations_v2/20181110110303_create_childtable_events.js +++ /dev/null @@ -1,47 +0,0 @@ - -exports.up = function (knex, Promise) { - return knex.schema.createTable('events', function (table) { - table.inherits('messages'); - table.string('location').notNullable(); - table.dateTime('start_time').notNullable(); - table.dateTime('end_time').notNullable(); //TODO: add a CHECK (https://www.postgresql.org/docs/current/ddl-constraints.html) that start_time < end_time - - //TODO: for now, we support only 1 author (where as graphql schema indicates support for [Group] authors) - table.string('author', 128).notNullable() - .references('gid').inTable('groups') - .onDelete('CASCADE'); //delete message if author is deleted - table.integer('for_announcement') - .references('mid').inTable('messages' /*'announcements'*/) - .onDelete('SET NULL'); - /* - ^ as stated in https://www.postgresql.org/docs/10/static/ddl-inherit.html#DDL-INHERIT-CAVEATS, - "A serious limitation of the inheritance feature is that indexes (including unique constraints) and foreign key constraints only apply to single tables, not to their inheritance children." - it is not possible (with PostgresQL v10) to use both inheritance and foreign keys at once. - until it is, or a workaround is found, we simply don't maintain child-level referential integrity - and we hope all the rest will work, e.g. simple_group_gids will indeed refer to simple groups, and idem for meta_group_gids. - TODO: maybe, check if PostgresQL's latest version enables this feature? - */ - - //TODO: add support for participatingGroups and participatingUsers from the graphql schema - - }).then( () => { - //update 'announcements' table by adding the 'for_event' column - knex.schema.table('announcements', function (table) { - table.integer('for_event') - .references('mid').inTable('messages' /*'events'*/) - .onDelete('SET NULL'); - }); - /* - ^ as stated in https://www.postgresql.org/docs/10/static/ddl-inherit.html#DDL-INHERIT-CAVEATS, - "A serious limitation of the inheritance feature is that indexes (including unique constraints) and foreign key constraints only apply to single tables, not to their inheritance children." - it is not possible (with PostgresQL v10) to use both inheritance and foreign keys at once. - until it is, or a workaround is found, we simply don't maintain child-level referential integrity - and we hope all the rest will work, e.g. simple_group_gids will indeed refer to simple groups, and idem for meta_group_gids. - TODO: maybe, check if PostgresQL's latest version enables this feature? - */ - }); -}; - -exports.down = function (knex, Promise) { - return knex.schema.dropTable('events'); -}; diff --git a/db/migrations_v2/20181110110404_create_childtable_private_posts.js b/db/migrations_v2/20181110110404_create_childtable_private_posts.js deleted file mode 100644 index b92bd25f9c6dd5e78650908287f136dca3c6f2a1..0000000000000000000000000000000000000000 --- a/db/migrations_v2/20181110110404_create_childtable_private_posts.js +++ /dev/null @@ -1,11 +0,0 @@ - -exports.up = function (knex, Promise) { - return knex.schema.createTable('private_posts', function (table) { - table.inherits('messages'); - table.string('author').notNullable(); //must refer to a User's uid. - }); -}; - -exports.down = function (knex, Promise) { - return knex.schema.dropTable('private_posts'); -}; diff --git a/db/migrations_v2/20181110110505_create_childtable_questions.js b/db/migrations_v2/20181110110505_create_childtable_questions.js deleted file mode 100644 index 3cfcfbc72956ca71dcd5e0e326dc53a7b6eea9fb..0000000000000000000000000000000000000000 --- a/db/migrations_v2/20181110110505_create_childtable_questions.js +++ /dev/null @@ -1,18 +0,0 @@ - -exports.up = function (knex, Promise) { - return knex.schema.createTable('questions', function (table) { - table.inherits('messages'); - table.string('author').notNullable(); //must refer to a User's uid. - /* - we cannot declare this column yet, as table 'answers' is not yet created. - it will be declared in the migration for the 'answers' childtable. - table.integer('for_answer') - .references('mid').inTable('answers') - .onDelete('SET NULL'); //if answer is deleted, set for_answer to null - */ - }); -}; - -exports.down = function (knex, Promise) { - return knex.schema.dropTable('questions'); -}; diff --git a/db/migrations_v2/20181110110606_create_childtable_answers.js b/db/migrations_v2/20181110110606_create_childtable_answers.js deleted file mode 100644 index 2407c4961bad2914baab676dbf73cbc8321d1736..0000000000000000000000000000000000000000 --- a/db/migrations_v2/20181110110606_create_childtable_answers.js +++ /dev/null @@ -1,46 +0,0 @@ - -exports.up = function (knex, Promise) { - return knex.schema.createTable('answers', function (table) { - table.inherits('messages'); - /* - we require that a GraphQL Answer's author field be the same as its recipient field. - since recipient is already known (defined in the 'messages' parent-table), - then we simply don't store answers' authors in database. - (another way would be to add equality constraints, but uselessly complex.) - table.string('author', 128).notNullable() - .references('gid').inTable('groups') - .onDelete('CASCADE'); - */ - table.integer('for_question').notNullable() - .references('mid').inTable('messages' /*'questions'*/) - .onDelete('CASCADE'); //if question is deleted, also delete the answer - /* - ^ as stated in https://www.postgresql.org/docs/10/static/ddl-inherit.html#DDL-INHERIT-CAVEATS, - "A serious limitation of the inheritance feature is that indexes (including unique constraints) and foreign key constraints only apply to single tables, not to their inheritance children." - it is not possible (with PostgresQL v10) to use both inheritance and foreign keys at once. - until it is, or a workaround is found, we simply don't maintain child-level referential integrity - and we hope all the rest will work, e.g. simple_group_gids will indeed refer to simple groups, and idem for meta_group_gids. - TODO: maybe, check if PostgresQL's latest version enables this feature? - */ - - }).then(() => { - //update 'questions' table by adding the 'for_answer' column - knex.schema.table('questions', function (table) { - table.integer('for_answer') - .references('mid').inTable('messages' /*'answers'*/) - .onDelete('SET NULL'); //if answer is deleted, set for_answer to null - }); - /* - ^ as stated in https://www.postgresql.org/docs/10/static/ddl-inherit.html#DDL-INHERIT-CAVEATS, - "A serious limitation of the inheritance feature is that indexes (including unique constraints) and foreign key constraints only apply to single tables, not to their inheritance children." - it is not possible (with PostgresQL v10) to use both inheritance and foreign keys at once. - until it is, or a workaround is found, we simply don't maintain child-level referential integrity - and we hope all the rest will work, e.g. simple_group_gids will indeed refer to simple groups, and idem for meta_group_gids. - TODO: maybe, check if PostgresQL's latest version enables this feature? - */ - }); -}; - -exports.down = function (knex, Promise) { - return knex.schema.dropTable('answers'); -}; diff --git a/db/migrations_v2/20181110120101_create_requests.js b/db/migrations_v2/20181110120101_create_requests.js deleted file mode 100644 index b89276fa6688bef7aab14ac24579253c1fe06489..0000000000000000000000000000000000000000 --- a/db/migrations_v2/20181110120101_create_requests.js +++ /dev/null @@ -1,38 +0,0 @@ - -exports.up = function (knex, Promise) { - return knex.schema.createTable('requests', function (table) { - table.increments('rid'); //autoincrementing (non-nullable) primary key - table.timestamps(true, true); //adds timestamptz-type (a PostgresQL data type) updated_at and created_at columns - - table.string('request_to',128).notNullable() - .references('gid').inTable('groups') - .onDelete('CASCADE'); //delete request from database if recipient group is deleted - //'request_from' can be a Group or a User, so specified in childtables. - table.text('request_comment'); - }); - - /* TODO: make a new migration to create these child tables - .then(()=>{ - return knex.schema.createTable('user_join_group', function (table) { - table.inherits('requests'); - table.string('useruid').notNullable(); - }).then(()=>{ - return knex.schema.createTable('group_join_event', function (table){ - table.inherits('requests'); - table.string('eventuid').notNullable(); - table.string('senderuid').notNullable(); - }).then(() => { - return knex.schema.createTable('your_group_host_event', function(table){ - table.inherits('requests'); - table.string('eventuid').notNullable(); - table.string('senderuid').notNullable(); - }); - }); - }); - }); - */ -}; - -exports.down = function (knex, Promise) { - return knex.schema.dropTable('requests'); -}; diff --git a/db/migrations_v2/20181110120202_create_childtable_user_join_group.js b/db/migrations_v2/20181110120202_create_childtable_user_join_group.js deleted file mode 100644 index 2ea3b56dd66a9541afa3d190c768f05c60f226ab..0000000000000000000000000000000000000000 --- a/db/migrations_v2/20181110120202_create_childtable_user_join_group.js +++ /dev/null @@ -1,11 +0,0 @@ - -exports.up = function(knex, Promise) { - return knex.schema.createTable('user_join_group', function (table) { - table.inherits('requests'); - table.string('request_from').notNullable(); //must refer to a User's uid. - }); -}; - -exports.down = function(knex, Promise) { - return knex.schema.dropTable('user_join_group'); -}; diff --git a/db/migrations_v2/20181110120303_create_childtable_group_join_metagroup.js b/db/migrations_v2/20181110120303_create_childtable_group_join_metagroup.js deleted file mode 100644 index 9aa23e822682c22053f08d12df37d131fe0a04cd..0000000000000000000000000000000000000000 --- a/db/migrations_v2/20181110120303_create_childtable_group_join_metagroup.js +++ /dev/null @@ -1,22 +0,0 @@ - -exports.up = function (knex, Promise) { - return knex.schema.createTable('group_join_metagroup', function (table) { - table.inherits('requests'); - table.string('request_from',128).notNullable() - .references('gid').inTable('groups' /*'simple_groups'*/) - .onDelete('CASCADE'); //if requesting group is deleted, also delete request - /* - ^ as stated in https://www.postgresql.org/docs/10/static/ddl-inherit.html#DDL-INHERIT-CAVEATS, - "A serious limitation of the inheritance feature is that indexes (including unique constraints) and foreign key constraints only apply to single tables, not to their inheritance children." - it is not possible (with PostgresQL v10) to use both inheritance and foreign keys at once. - until it is, or a workaround is found, we simply don't maintain child-level referential integrity - and we hope all the rest will work, e.g. simple_group_gids will indeed refer to simple groups, and idem for meta_group_gids. - TODO: maybe, check if PostgresQL's latest version enables this feature? - */ - }); -}; - -exports.down = function (knex, Promise) { - return knex.schema.dropTable('group_join_metagroup'); -}; - diff --git a/db/migrations_v2/20181110120404_create_childtable_group_coauthor_event.js b/db/migrations_v2/20181110120404_create_childtable_group_coauthor_event.js deleted file mode 100644 index a2097480bf4fa8237eadea26e9f476cc7e78ba6c..0000000000000000000000000000000000000000 --- a/db/migrations_v2/20181110120404_create_childtable_group_coauthor_event.js +++ /dev/null @@ -1,24 +0,0 @@ - -exports.up = function (knex, Promise) { - return knex.schema.createTable('group_coauthor_event', function (table) { - table.inherits('requests'); - table.string('request_from',128).notNullable() - .references('gid').inTable('groups' /*'simple_groups'*/) - .onDelete('CASCADE'); //if requesting group is deleted, also delete request - /* - ^ as stated in https://www.postgresql.org/docs/10/static/ddl-inherit.html#DDL-INHERIT-CAVEATS, - "A serious limitation of the inheritance feature is that indexes (including unique constraints) and foreign key constraints only apply to single tables, not to their inheritance children." - it is not possible (with PostgresQL v10) to use both inheritance and foreign keys at once. - until it is, or a workaround is found, we simply don't maintain child-level referential integrity - and we hope all the rest will work, e.g. simple_group_gids will indeed refer to simple groups, and idem for meta_group_gids. - TODO: maybe, check if PostgresQL's latest version enables this feature? - */ - table.integer('for_event').notNullable() - .references('mid').inTable('messages' /*'events'*/) - .onDelete('CASCADE'); //if event is deleted, also delete request - }); -}; - -exports.down = function (knex, Promise) { - return knex.schema.dropTable('group_coauthor_event'); -}; diff --git a/db/migrations_v2/readme.txt b/db/migrations_v2/readme.txt deleted file mode 100644 index f70d6788d84fc106d9dcb44be9353f8688524985..0000000000000000000000000000000000000000 --- a/db/migrations_v2/readme.txt +++ /dev/null @@ -1,13 +0,0 @@ -Cette version du schéma de la BDD a été abandonnée lorsqu'on s'est rendus compte que, -du moins pour la version actuelle de PostgresQL (v11 en 2018-11), on ne peut pas utiliser -simultanément le inheritance et des foreign keys. Plus précisément, - -https://stackoverflow.com/questions/24360312/foreign-keys-table-inheritance-in-postgresql -> The short version: you can use foreign keys, or table inheritance, but not both. This isn't inherently impossible, it's just that it's technically quite difficult to implement unique indexes that span inherited tables in PostgreSQL in a fast, reliable manner. Without that, you can't have a useful foreign key. Nobody's successfully implemented it well enough for a patch adding support to be accepted into PostgreSQL yet. -> A foreign key can point to a table that is part of an inheritance hierarchy, but it'll only find rows in that table exactly. Not in any parent or child tables. To see which rows the foreign key sees, do a `SELECT * FROM ONLY` thetable. The ONLY keyword means "ignoring inheritance" and that's what the foreign key lookup will do. - -https://www.postgresql.org/docs/current/ddl-inherit.html#DDL-INHERIT-CAVEATS -A serious limitation of the inheritance feature is that indexes (including unique constraints) and foreign key constraints only apply to single tables, not to their inheritance children. This is true on both the referencing and referenced sides of a foreign key constraint. - -Il peut toutefois être utile de garder cette version du schéma de la BDD dans un coin, au cas où -la fonctionnalité inheritance + foreign keys arrive dans une prochaine version de PostgresQL... diff --git a/db/seeds_v3/00_special_groups.js b/db/seeds/00_special_groups.js similarity index 100% rename from db/seeds_v3/00_special_groups.js rename to db/seeds/00_special_groups.js diff --git a/db/seeds_v3/01_dummy_groups.js b/db/seeds/01_dummy_groups.js similarity index 100% rename from db/seeds_v3/01_dummy_groups.js rename to db/seeds/01_dummy_groups.js diff --git a/db/seeds_v3/02_dummy_metagroups.js b/db/seeds/02_dummy_metagroups.js similarity index 100% rename from db/seeds_v3/02_dummy_metagroups.js rename to db/seeds/02_dummy_metagroups.js diff --git a/db/seeds_v3/03_dummy_metagroup_memberships.js b/db/seeds/03_dummy_metagroup_memberships.js similarity index 100% rename from db/seeds_v3/03_dummy_metagroup_memberships.js rename to db/seeds/03_dummy_metagroup_memberships.js diff --git a/db/seeds_v3/04_dummy_announcements.js b/db/seeds/04_dummy_announcements.js similarity index 100% rename from db/seeds_v3/04_dummy_announcements.js rename to db/seeds/04_dummy_announcements.js diff --git a/db/seeds_v3/05_dummy_events.js b/db/seeds/05_dummy_events.js similarity index 100% rename from db/seeds_v3/05_dummy_events.js rename to db/seeds/05_dummy_events.js diff --git a/db/seeds_v3/06_dummy_private_posts.js b/db/seeds/06_dummy_private_posts.js similarity index 100% rename from db/seeds_v3/06_dummy_private_posts.js rename to db/seeds/06_dummy_private_posts.js diff --git a/db/seeds_v3/07_dummy_requests.js b/db/seeds/07_dummy_requests.js similarity index 100% rename from db/seeds_v3/07_dummy_requests.js rename to db/seeds/07_dummy_requests.js diff --git a/db/seeds_v1/01_create_groups.js b/db/seeds_v1/01_create_groups.js deleted file mode 100644 index a66046cc274a3f0498818d31d70bfc35d5039afd..0000000000000000000000000000000000000000 --- a/db/seeds_v1/01_create_groups.js +++ /dev/null @@ -1,129 +0,0 @@ - -exports.seed = async function(knex, Promise) { - // Deletes ALL existing entries - await knex('groups').del(); - await knex('simple_groups').del(); - // Inserts seed entries - const simple_groups = [{ - name: 'BR', - uid: 'br', - website: 'br.binets.fr', - description: 'Le Binet Réseau est responsable du réseau internet des élèves sur le campus de l\'Ecole polytechnique.', - school: 'polytechnique', - parent_uid: 'kes', - type : 'simple' - },{ - name: 'JTX', - uid: 'jtx', - description: "Le Journal Télévisé de l'X te fait revivre les évènements promo en images et expose régulièrement ses créations.", - website: 'binet-jtx.com', - school: 'polytechnique', - parent_uid: 'kes', - type : 'simple' - },{ - name: 'Faërix', - uid: 'faerix', - website: 'faerix.net', - description: "Faërix est le binet de jeux de l'école : jeux de cartes, de plateau, de rôles... Le binet organise chaque année, sur un week-end, une convention de jeux de rôles, les Rencontres Rôlistes de l'X.", - school: 'polytechnique', - parent_uid: 'kes', - type : 'simple' - },{ - name: 'Bôbar', - uid: 'bob', - description: "Le bar de l'École polytechnique, tenu par et pour les X. Le BôBar t'enkhûle avec affection", - website: 'http://tdb.bobar.pro/', - school: 'polytechnique', - parent_uid: 'kes', - type : 'simple' - },{ - name: 'Kès', - uid: 'kes', - description: "La Kès est le bureau des élèves de l'école, constitué de 16 kessiers élus par la promotion. Son local est ouvert de 12h30 à 22h30 tous les jours de la semaine (vendredi jusqu'à 18h15), et se trouve à l'entrée Est du Bataclan. Pour plus d'infos : consultez le Wikix ou téléphonez au 2726 pour faire un gros bisou à Mina et Zaza", - website: 'kes.binets.fr', - school: 'polytechnique', - type : 'simple' - },{ - name: 'DaTA', - uid: 'data', - website: 'data-ensta.fr', - school: 'ensta', - parent_uid: 'bdeensta', - type : 'simple' - },{ - name: 'Laser Wave', - uid: 'laserwave', - website: 'laserwave.fr', - school: 'supoptique', - type : 'simple' - },{ - name: 'WikiX', - uid: 'wikix', - website: 'https://wikix.polytechnique.org/', - school: "polytechnique", - description: "Le recueil de la mémoire des élèves", - type: "simple", - },{ - name: 'BDE Ensta', - uid: 'bdeensta', - website: 'http://bde.ensta-paristech.fr/', - school: 'ensta', - type : 'simple' - },{ - name: "X-Chine", - uid: "x-chine", - school: "polytechnique", - description: `X-Chine est le binet qui rassemble les amoureux et les curieux de l'Empire du Milieu, qu'ils soient chinois ou non !`, - type: "simple" - },{ - name: 'Subaïsse', - uid: 'subaisse', - description: 'Le Binet de ceux qui subissent', - school: 'polytechnique', - parent_uid: 'kes', - type : 'simple' - },{ - name: 'X-Broadway', - uid: 'xbroadway', - website: 'https://xbroadway.binets.fr/', - description: "Créé par la promotion 2012, le binet X-Broadway prépare pendant une année une comédie musicale qu'il joue durant le tronc commun de la promo suivante. Chaque année, le bureau est composé de multiples pôles : comédie, danse, manute, musique, et prez'. Une fois le bureau constitué, il recrute sa troupe parmi les étudiants du plateau de Saclay.", - school: "polytechnique", - parent_uid: 'kes', - type: 'simple' - },{ - name: 'Œnologie', - uid: 'oenologie', - website: 'oenologie.binets.fr', - description: "Le binet de la Finesse et du Bon Goût par excellence. Cours d'initiation, dégustations, soirées Vins & Fromages, voyages chez les producteurs : le Binet Œnologie est là pour partager la passion du vin avec tous, néophytes ou connaisseurs.", - school: "polytechnique", - parent_uid: "kes", - type: "simple" - },{ - name: "Tribunes de l'X", - uid: 'politix', - website: 'https://www.facebook.com/Tribunes-de-lX-250533552110256/', - description: "Le binet Tribunes de l'X a pour but d'intéresser les élèves à la \"vie de la cité\" en organisant des conférences ou débats, autour de grandes sujets d'actualité et d'enjeux fondamentaux pour le débat public, avec des personnalités diverses, hommes politiques ou intellectuels. Cette association est la tribune étudiante de l'X.", - school: "polytechnique", - parent_uid: "kes", - type: 'simple' - },{ - name: "X-Finance", - uid: 'x-finance', - description: "X Finance gère un fonds d'investissement étudiant au profit de la FX en partenariat avec Edmond de Rothschild. Nous développons également des modèles algorithmiques pour gérer le risque. Le binet organise également des événements de networking en partenariat avec des écoles françaises et européeennes.", - school: "polytechnique", - parent_uid: "kes", - type: "simple" - },{ - name: "ASK", - uid: "ask", - description: "L'Action Sociale de la Kès regroupe les activités à caractère social et solidaire du platâl, comme le soutien scolaire, la bibliothèque de rue, les maraudes, le don du sang, les journées handicap ou encore le parrainage d'enfants. Chacun y trouvera son compte et pourra s'y investir à sa guise! Si tu as l'âme de l'Abbé Pierre ou le sex appeal d'Adriana Karembeu, chauffe toi et viens donner un peu de ton temps aux autres! Promis, on est gentils. Des bisous bisous!", - school: "polytechnique", - parent_uid: "kes", - type: "simple" - } - - ]; - - return knex('simple_groups').insert(simple_groups); - -}; diff --git a/db/seeds_v1/02_make_metagroups.js b/db/seeds_v1/02_make_metagroups.js deleted file mode 100644 index 432bb33d1b96b6f4fda099d16cbf35d471193c6a..0000000000000000000000000000000000000000 --- a/db/seeds_v1/02_make_metagroups.js +++ /dev/null @@ -1,24 +0,0 @@ - -exports.seed = function(knex, Promise) { - // Deletes ALL existing entries - return knex('meta_groups').del() - .then(function () { - // Inserts seed entries - return knex('meta_groups').insert([ - { - name: 'Fédérez', - uid: 'federez', - website: 'federez.io', - description: "L'association de toutes les associations de réseau des écoles", - type : 'meta' - }, - { - name: 'BSCkBl', - uid: 'bsckbl', - website: 'bsckbl.binets.fr', - type : 'meta' - } - ]); - }); - }; - \ No newline at end of file diff --git a/db/seeds_v1/03_make_requests.js b/db/seeds_v1/03_make_requests.js deleted file mode 100644 index aceaf4721893cb157cd6d5197384e49ff99b0e36..0000000000000000000000000000000000000000 --- a/db/seeds_v1/03_make_requests.js +++ /dev/null @@ -1,38 +0,0 @@ - -exports.seed = function(knex, Promise) { - // Deletes ALL existing entries - return knex('user_join_group').del() - .then(function () { - // Inserts seed entries - return knex('user_join_group').insert([ - { id: 1, - recipient: 'br', - message: "C'est ici pour développer sigma ?", - useruid: "anatole.romon" - }, - { id: 2, - recipient: 'br', - message: "Bonjour, je cherche le binet subaisse", - useruid: "quentin.gendre" - }, - {id : 3, - recipient: 'jtx', - message: "Quand je serais grand je serais cinéaste !", - useruid: "anatole.romon" - } - ]).then(() => { - return knex('group_join_event').del() - .then(function (){ - return knex('group_join_event').insert([ - { - id : 4, - recipient : "br", - message : "nous aussi on veut coder sigma", - eventuid : 42, - senderuid : "subaisse" - } - ]); - }); - }); - }); -}; diff --git a/db/seeds_v1/04_make_posts.js b/db/seeds_v1/04_make_posts.js deleted file mode 100644 index 6ac602b8c9439e729996c21fc3c6653cb0fcdab6..0000000000000000000000000000000000000000 --- a/db/seeds_v1/04_make_posts.js +++ /dev/null @@ -1,110 +0,0 @@ - -exports.seed = async function(knex, Promise) { - await knex('announcements').del(); - await knex('announcements').insert([{ - id: 0, - title: "Fissurer c'est bien", - content: "Les nouveaux ordis du JTX sont arrivés ! Le BR aide à les installer ;)" - },{ - id: 1, - title: "Proj'et Promotion", - content: "La nouvelle proj' du JTX arrive !" - },{ - id: 2, - title: "Fête de la Lune", - content: "C'est bientôt la fête de la Lune ! Inscrivez-vous pour un dîner-spectacle dans le Grand Hall !" - },{ - id: 3, - title: "Formation Web", - content: "Envie d'apprendre à faire un site Web en Django ? Alors viens en amphi Sauvy ce jeudi à 20h !" - },{ - id: 4, - title: "Journées FedeRez", - content: "Cette année, nous parlerons de vie privée, protection des données et sécurité", - authors: ['federez'] - }]); - - await knex('events').del(); - await knex('events').insert([ - { - title : "coder sigma", - content : "Tous ensemble pour faire du Dev. Que demander de plus ? (a part du sommeil)", - start_time : knex.fn.now(), - end_time : knex.fn.now(), - is_announcement : false - } - ]); - - await knex('private_posts').del(); - await knex('private_posts').insert([ - { - title : "Anatole met du temps à faire avancer resolver_dev", - content : "C'est scandaleux qu'anatole mette autant de temps à corriger les bugs et merge sa branche. Comment on fait pour présenter le projet aux 17 ?", - author_uid : "guillaume.wang", - author_db : "ldap" - }, - { - title : "Sushi tradi", - content : "Le prochain sushi tradi aura lieux ce mardi. Tout le monde est invité.", - author_uid : "martin.guillot", - author_db : "ldap" - }, - ]); - - await knex('group_message_relationships').del(); - - await knex('group_message_relationships').insert([ - { - message : 42, - group : 'br', - status : 'host' - }, - { - message : 42, - group : 'kes', - status : 'receive' - } - - ]); - - await knex('group_message_relationships').insert([{ - message: 0, - group: 'br', - status: 'host' - }, - { - message: 0, - group: 'jtx', - status: 'host' - }, - { - message: 1, - group: 'br', - status: 'host' - }, - { - message: 2, - group: 'xchine', - status: 'host' - }, - { - message: 3, - group: 'br', - status: 'host' - }]); - - await knex('group_message_relationships').insert([ - { - message : 4, - group : "br", - status : "receive" - }, - { - message : 5, - group : "faerix", - status : "receive" - } - ]); - - return; -}; diff --git a/db/seeds_v1/05_metagroup_membership.js b/db/seeds_v1/05_metagroup_membership.js deleted file mode 100644 index cd1587503a14bf8f86f50d8f3964f34113bae412..0000000000000000000000000000000000000000 --- a/db/seeds_v1/05_metagroup_membership.js +++ /dev/null @@ -1,23 +0,0 @@ - -exports.seed = async function(knex, Promise) { - // Deletes ALL existing entries - await knex('meta_groups').del(); - await knex('meta_group_membership').insert([ - { - member_uid : "br", - union_uid : "federez", - status : "admin" - }, - { - member_uid : "data", - union_uid : "federez", - status : "admin" - }, - { - member_uid : "bob", - union_uid : "bsckbl", - status : "admin" - } - ]); - return; -}; diff --git a/db/seeds_v1/06_taken_rights.js b/db/seeds_v1/06_taken_rights.js deleted file mode 100644 index 9915ef9ae4d45a7f55c737c185ed4589313b9258..0000000000000000000000000000000000000000 --- a/db/seeds_v1/06_taken_rights.js +++ /dev/null @@ -1,14 +0,0 @@ - -exports.seed = function(knex, Promise) { - // Deletes ALL existing entries - return knex('taken_rights').del() - .then(function () { - // Inserts seed entries - return knex('taken_rights').insert([ - { - user_uid : "anatole.romon", - group_uid : "kes", - } - ]); - }); -}; diff --git a/db/seeds_v1/07_make_event.js b/db/seeds_v1/07_make_event.js deleted file mode 100644 index 31668a7df58d839711c869c3c488614722517b48..0000000000000000000000000000000000000000 --- a/db/seeds_v1/07_make_event.js +++ /dev/null @@ -1,38 +0,0 @@ - -exports.seed = async function(knex, Promise) { - // Deletes ALL existing entries - await knex('events').del(); - return await knex('events').insert([ - { - title : "Fête de la lune", - content : "La fête de la lune, c'est bientôt dans le grand hall !", - start_time : knex.fn.now(), - end_time : knex.fn.now(), - authors : ["x-chine"], - is_announcement : false - },{ - title: "Perm BR du mardi soir", - content: "La perm' BR c'est maintenant!", - location: "Amphi Sauvy", - start_time: knex.fn.now(), - end_time: knex.fn.now(), - authors : ["br"], - is_announcement : true - },{ - title: "Formation Git", - content: "Aujourd'hui, on va parler du système de contrôle de versions Git, qui est particulièrement utile pour travailler à plusieurs sur des projets informatiques: PSC, code de PI ou de projet de MAP, site binet, quoi que ce soit!", - start_time: knex.fn.now(), - end_time: knex.fn.now(), - location: "Amphi Painlevé", - authors: ["br"], - is_announcement: false - },{ - title: "Formation Web", - content: "Envie d'apprendre à faire un site Web en Django ? Alors viens en amphi Sauvy ce jeudi à 20h !", - authors: ['br'], - start_time: knex.fn.now(), - end_time: knex.fn.now(), - is_announcement: false - } - ]); -}; diff --git a/configfile_doc.json b/jsdoc_config.json similarity index 78% rename from configfile_doc.json rename to jsdoc_config.json index 3c4db54b4e44b38898249152e450588b681da74e..e668b8cd04e0622fd08cea637139f7dbeff03647 100644 --- a/configfile_doc.json +++ b/jsdoc_config.json @@ -3,17 +3,18 @@ "plugins": ["plugins/markdown", "plugins/summarize", "node_modules/jsdoc-babel"], "recurseDepth": 5, "source": { - "include": ["./src","./db","./README.md"], - "exclude": ["./db/migrations","./db/seeds"], - "includePattern": ".+\\.(js|ts)(doc|x)?$", - "excludePattern": "(^|\\/|\\\\)_" + "include": ["./README.md","./src","./test","./db"], + "exclude": [], + "includePattern": "(.+\\.(js|ts)(doc|x)?$)|(.md)", + "excludePattern": "((^|\\/|\\\\)_)|(20|0)" }, "sourceType": "module", "opts": { "template": "templates/default", "encoding": "utf8", "destination": "./doc", - "recurse": true + "recurse": true, + "tutorials": "./notes" }, "tags": { "allowUnknownTags": true, diff --git a/ldap_config.json b/ldap_config.json index 9d85190870bbe9056c123d7964dabfea4c3e7234..ba56ad7d85030d2c3f9098bdd84dd5e40f25b067 100644 --- a/ldap_config.json +++ b/ldap_config.json @@ -1,62 +1,58 @@ { "comment_1": "Tout ce fichier sert à protéger les vrais champs du LDAP dans les scripts dans src/ldap. Les champs ci-dessous contiennent le nécessaire à une première connexion par exemple.", - "server": "ldap://ldap.eleves.polytechnique.fr:389", - + "comment_tmp": "Passer en LDAPS ASAP.", + "comment_1bis": "Ce fichier est importé par src/ldap/internal/config.ts ; comme indiqué dans config.ts, la présence d'une variable d'environnement correspondante peut override les paramètres donnés ici.", + "server_prod_old": "ldap://frankiz.eleves.polytechnique.fr:389", + "server_dev_old": "ldap://localhost:389", + "server_prod": "ldap://ldap.eleves.polytechnique.fr:389", + "server_dev": "ldap://ldap.eleves.polytechnique.fr:389", + "comment_2": "Noms de domaines dans LDAP ; le niv d'après est en uid=, voir Wikipedia", - "dn_groups":"ou=groups,dc=frankiz,dc=net", - "dn_users": "ou=eleves,dc=frankiz,dc=net", - "key_id": "uid", + "dn":{ + "group":"ou=groups,dc=frankiz,dc=net", + "user": "ou=eleves,dc=frankiz,dc=net" + }, "comment_3": "Placeholders et indications de contenu de certains champs du LDAP généré par frankiz pour les utilisateurs", "user": { - "single": { - "photo": "jpegPhoto", - "givenName": "givenName", - "lastName": "sn", - "fullName": "cn", - "cleanFullName": "gecos", - "nickname": "displayName", - "birthdate": "brBirthdate", - "nationality": "country", - "promotion": "brPromo", - "phone": "telephoneNumber", - "adress": "brRoom", - "id": "uidNumber", - "sport": "brMemberOf", - "password": "userPassword", - "idNum": "gidNumber", - "directory": "homeDirectory", - "login": "loginShell", - "readPerm": "brNewsReadAccess", - "writePerm": "brNewsPostAccess" - }, - "multiple": { - "mail": "mail", - "ip": "brIP", - "forlifes": "brAlias", - "groups": "brMemberOf", - "school": "brMemberOf", - "course": "brMemberOf", - "class": "objectClass" - } + "uid": "uid", + "password": "userPassword", + "givenName": "givenName", + "lastName": "sn", + "nickname": "pseudonym", + "gender": "gender", + "photo": "urlPhoto", + "phone": "telephoneNumber", + "adress": "roomNumber", + "mail": "email", + "birthdate": "birthdate", + "nationality": "nationality", + "login": "loginShell", + "directory": "homeDirectory", + "classes": "objectClass", + "id": "uidNumber", + "cleanFullName": "gecos", + "admins": "adminOf", + "speakers": "speakerOf", + "members": "memberOf", + "followers": "followerOf" }, - "comment_4": "Placeholders et indications de contenu de certains champs du LDAP généré par frankiz pour les groupes", + "comment_4": "Placeholders et indications de contenu de certains champs du LDAP généré par frankiz pour les groupes (childs TBA)", "group": { - "single": { - "name": "cn", - "nickname": "brAlias", - "type": "brNS", - "idNumber": "uidNumber", - "idNumber2": "gidNumber", - "login": "loginShell", - "password": "userPassword", - "directory": "homeDirectory", - "cleanFullName": "gecos" - }, - "multiple": { - "member": "restrictedMemberUid", - "admin": "memberUid", - "class": "objectClass" - } + "gid": "cn", + "name": "sn", + "site": "website", + "description": "description", + "category": "groupCategory", + "admins": "admin", + "speakers": "speaker", + "members": "member", + "followers": "follower", + "adress":"cn", + "idNumber": "gidNumber", + "password": "userPassword", + "logo": "urlPhoto", + "classes": "objectClass", + "parents": "parent" } } \ No newline at end of file diff --git a/ldap_credentials_dist.json b/ldap_credentials_dist.json new file mode 100644 index 0000000000000000000000000000000000000000..46253a9513b3bbbdf5bee96aea662eb9bba46c7e --- /dev/null +++ b/ldap_credentials_dist.json @@ -0,0 +1,6 @@ +{ + "README": "Modele du fichier 'ldap_credentials.json', requis par src/ldap/. Copier ce fichier en le renommant 'ldap_credentials.json' (le placer a la racine du repo). A copier et renommer en local.", + + "dn": "uid=sigma,ou=services,dc=frankiz,dc=net", + "passwd": "&vH}x%7;FK3j53eX" +} diff --git a/notes/CONTRIBUTING.json b/notes/CONTRIBUTING.json new file mode 100644 index 0000000000000000000000000000000000000000..03bd9b0246ab124c4b87c3c0d876d2df764ad95d --- /dev/null +++ b/notes/CONTRIBUTING.json @@ -0,0 +1,4 @@ +{ + "title": "Contribuer au projet", + "children": [] +} \ No newline at end of file diff --git a/CONTRIBUTING.md b/notes/CONTRIBUTING.md similarity index 67% rename from CONTRIBUTING.md rename to notes/CONTRIBUTING.md index 19104428c170ed62adfafd16e94b50bb36b4d15d..a75a55eba3e3bb6b8f83414805e114ab988ababd 100644 --- a/CONTRIBUTING.md +++ b/notes/CONTRIBUTING.md @@ -1,35 +1,70 @@ -Contribuer au projet -=== +Ceci est la documentation haut-niveau de sigma, décrivant le projet du point de vue développeur de façon globale, sans trop rentrer dans le détail du code. -Documentation haut-niveau, décrivant le projet du point de vue développeur de façon globale, sans rentrer dans le détail du code. +## Pourquoi Sigma ? -Il y a aussi une documentation plus précise, générée par JSDoc à partir des commentaires dans le code-même. Pour la générer, exécuter `npm run doc`. Elle sera alors [disponible dans ./doc](./doc/index.html). +### Deux constats + +Frankiz est obsolète et plus maintenable. Le jour où Frankiz tombe, il emportera le BR, et peut-être tout le platâl, avec lui. Il est plus que temps de développer un outil nouveau, documenté, clair, moderne et qu'il sera beaucoup plus facile de faire évoluer. En outre, la centralisation des pouvoirs des admins et l'interface vieillie font qu'il s'agit d'un service largement sous-utilisé. + +L'organisation actuelle des associations à l'école n'est également pas satisfaisante ; tout passe par Facebook, qui trie des données dans le dos de la plupart des utilisateurs, fait remonter les messages aléatoirement et complexifie beaucoup la constitution d'un emploi du temps compréhensible pour tous les acteurs. Ne jamais oublier la maxime du grand maître : « Facebook est centré sur l’individu, Sigma est centré sur le groupe. » – Quentin Gendre, X2015. + +En revanche, le LDAP généré par Frankiz tous les quarts d'heure est indispensable au fonctionnement de nombreux sites binets. En conséquence il n'est pas envisageable sans passer beaucoup de temps à modifier tous ces sites de laisser tomber le LDAP Frankiz. + +Ainsi, l'idée est de conserver le LDAP Frankiz mais pas sa BDD, inutilisable car non documentée, et utiliser exclusivement le LDAP Frankiz pour la gestion des utilisateurs. + +### L'idée + +Il s'agit de reprendre un nouveau Frankiz plus moderne et clairement documenté, mais pas seulement. Plusieurs fonctionnalités intéressantes sont envisageables : +- fil d'actualité avec filtre personnalisable +- gestion des inscriptions (notamment avec nombre de places limité) +- inscription par trigramme +- minimodules ou greffons à étudier +- intégration des événements d'autres écoles ## Description et actualité des branches -#### Migrations Knex +### *fix_rlc* + +- C'est actuellement la branche qui compile et qui est considérée comme stable, meme si beaucoup de fonctionnalités ne sont pas implémentées. + +### *change_GraphQL* -Pour `./db/migrations`, il faut utiliser migrations_v3 (la dernière) (c'est logique). Ca s'appelle `migrations_v3` et pas `migrations` parce que je ne voulais pas supprimer les deux autres versions sans confirmer auprès des autres devs que c'était ok. -- kadabra +- Cette branche comporte les dernieres modifications du schéma GraphQL et de la BDD Knex, qui sont en pleine mutation. -### _stable_ +### *LDAP_changes* + +- Branche dédiée a la finalisation de l'API LDAP, sur la nouvelle structure de LDAP sur `bloaziadur`. + +### Branches pas a jour + +#### _stable_ - déploiement de l'API pour les développeurs du front -### _master_ +#### _master_ - une branche où on fait des petits commits pour merge dans stable -### redo-connectors +#### *reconverge_ldaprefacto_redoconnectors* -- branche sur laquelle akka vodol fait tout son travail : implémentation des resolvers pour le nouveau schéma propre (le schéma "_wish_list"). Comme ça, il peut faire d'éventuelles modifications du schéma sans faire chier les autres, et ensuite on valide ensemble pour merge. +- faire reconverger toutes les branches, afin de s'éviter de mauvaises surprises au moment de remettre en commun (e.g. éviter que la branche implémentant les resolvers n'ait pas pris en compte une modification dans le schéma GraphQL désiré) : + - master, sur lequel ont eu lieu des modifs de l'authentification, de la doc et du schéma GraphQL + - *LDAP_refacto*, sur lequel hawkspar a écrit le code pour les fonctions d'interaction avec le LDAP (dans `src/ldap`) + - *redo-connectors*, où akka vodol était en train d'écrire des resolvers pour le nouveau schéma propre "_wish_list" (de kadabra). -### LDAP-refacto +Actuellement cette branche comporte un certain nombre d'incohérences : des resolvers qui n'ont pas pris en compte des changements dans les fonctions de manipulation du LDAP, ou qui n'ont pas pris en compte des modifications du schéma GraphQL. -- TODO (@hawkspar tu peux expliquer ici a quoi sert cette branche ? et le cas échéant, la merge dans master ?) +### Anciennes branches + +Certaines anciennes branches, fraîchement mergées, sont décrites ci-dessous. Cette section pourra être retirée quand le merge sera proprifié. + +#### redo-connectors + +- branche sur laquelle akka vodol fait tout son travail : implémentation des resolvers pour le nouveau schéma propre (le schéma "_wish_list"). Comme ça, il peut faire d'éventuelles modifications du schéma sans faire chier les autres, et ensuite on valide ensemble pour merge. -### unit-tests +#### unit-tests -- TODO (@akka vodol tu peux expliquer ici a quoi sert cette branche ? et le cas échéant, la merge dans master ?) +- branche de test-unitaires ## Structure du dossier @@ -73,14 +108,17 @@ Les dossiers à la racine du projet : - `resolvers/` : (@akka vodol tu peux expliquer ici ?) - [`ldap`](./src/ldap) : gestion des requêtes au LDAP. Fournit une couche d'abstraction permettant aux resolvers GraphQL de ne pas se soucier des spécifités et de la complexité syntaxique des requêtes LDAP +La structure générale du projet peut être résumé comme suit : -## Base de données + + +## Bases de données Sigma s'appuie sur deux bases de données (toutes deux hébergées par le BR) : - Le LDAP frankiz, qui contient identifiants, mots de passe, et toutes les informations sur les utilisateurs et groupes spécifiques X. - - On y accède par des requêtes LDAP, d'où la nécessité de ldap.js (une dépendance npm faisant office de traducteur javascript/LDAP). + - Plus de détail dans {@tutorial memo_ldap} - La BDD propre à Sigma, en [PostgreSQL](https://www.postgresql.fr/), contient les groupes et leurs niveaux de visibilité mais peu d'information sur les utilisateurs (seulement son école). - - Les requêtes à cette BDD sont gérées par [Knex.js](http://knexjs.org), qui permet de construire facilement des requêtes PostgreSQL. + - Les requêtes à cette BDD sont gérées par [Knex.js](http://knexjs.org), qui permet de construire facilement des requêtes PostgreSQL (voir {@tutorial memo_postgresql} et {@tutorial memo_knexjs}). Cette structure peut sembler lourde et redondante mais s'explique par plusieurs raisons : - Volonté de pouvoir intégrer d'autres écoles simplement @@ -92,14 +130,12 @@ Cette structure peut sembler lourde et redondante mais s'explique par plusieurs - Le LDAP contient des informations plus sensibles (c'est là que toutes les données sur les utilisateurs sont stockées) ### Utiliser la BDD sigma avec *Knex.js* - -cf. [le memo knexjs](./memo\ knexjs.md). +cf. {@tutorial memo_knexjs} pour plus de détails sur la techno. Le knexfile.js et le knex_router.js sont dans `./db`. La localisation des dossiers [seeds](./db/seeds) et [migrations](./db/migrations), est spécifiée dans le knexfile.js, en l'occurrence également dans `./db`. ### Interagir directement avec la BDD sigma en *PostgreSQL* - -cf. [le memo postgresql](./memo\ postgresql.md). +cf. {@tutorial memo_postgresql} pour plus de détails sur la techno. Pour accéder à la "vraie" BDD, sur roued (le serveur qui héberge sigma), il faut @@ -109,17 +145,21 @@ Pour accéder à la "vraie" BDD, sur roued (le serveur qui héberge sigma), il f - faire les requêtes en SQL (version PostgreSQL) par l'interface ainsi `psql` ### Fonctions LDAP +cf. {@tutorial memo_ldap} pour plus de détails sur la techno. -On peut facilement explorer le LDAP si on est sur le réseau de l'X, avec [JXplorer](http://jxplorer.org/) en entrant `frankiz` dans nouvelle connexion et en allant dans `net`. - -### Ne pas se tromper de LDAP... +L'idée est de fournir une API minimaliste pour le LDAP de façon à faciliter l'intégration de nouvelles écoles qui privilégierait un autre mode d'authentification - et ce qui permet aussi de leur laisser le contrôle sur leurs propres données. +#### Ne pas se tromper de LDAP... Il y a deux LDAP à l'X : le LDAP de la DSI et le LDAP de Frankiz (du BR). - Le premier, utilisé pour l'authentification sur les services de l'Ecole (mail polytechnique.edu, le Moodle, Synapses...) ne concerne pas du tout sigma. - C'est le second qu'on utilise, et "le LDAP" dans le contexte de sigma désignera, sauf indication contraire, le LDAP frankiz. -## Authentification +Une documentation vieillissante est disponible sur le [`wikix`](https://wikibr.binets.fr/Admin:LDAP) pour en savoir plus sur ce sujet. + +## Gestion des utilisateurs + +### Authentification On utilise [passportjs](https://www.npmjs.com/package/passport) pour l'authentification. La [stratégie "ldapauth"](https://www.npmjs.com/package/passport-ldapauth) de passport permet de s'authentifier contre le LDAP frankiz. @@ -127,6 +167,20 @@ Lorsqu'une authentification est réussie, on crée une session et on l'associe La gestion des sessions et de la distribution de cookies est gérée par passport, et est décrite dans `app.ts`. + + +### Vérification des droits + +Il semble intéressant de proposer pour Sigma une structure plus souple que Frankiz, avec plusieurs niveaux dans un groupe : +- Admin, qui gère le groupe +- Speaker, qui parle au nom du groupe +- Member, qui est notifié et qui apparait comme membre +- Follower, qui est aussi notifié parfois + +L'idée est aussi d'explorer ce qu'on peut faire pour opérer une cascade de privilèges et éviter les super-admins de Frankiz. Dans cette logique, on peut définir un graphe évolutif qui définit les interactions entre groupes et établit des relations de visibilités, d'appartenance et d'administrations. Cela permettrait de définir de façon plus intelligente les droits d'un utilisateur de façon presque graphique, voir : + + + ## Panneau d'administration ("adminview") D'habitude, toutes les requêtes arrivent en HTTP POST : les requêtes GraphQL, et les requêtes de connexion (à /login). @@ -159,6 +213,8 @@ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/awai ## Outils de développement +Le détail des outils les plus techniques est dans {@tutorial memos}. + ### ESLint On utilise ESLint pour standardiser le style du code. @@ -192,7 +248,12 @@ npm run start ``` dans une autre pour avoir un environnement de développement agréable. +### VM + +Le BR dispose de plusieurs Virtual Machines (VM) pour faire tourner les outils en développement. Pour l'instant, Sigma est sur roued (129.104.201.10). + ## Contact Le BR 2016, plus particulièrement Wilson Jallet, Guillaume Wang, Quentin Chevalier et Anatole Romon -Le BR 2017, plus particulièrement Olivér Facklam + +Le BR 2017, plus particulièrement Olivér Facklam, Grégoire Grzeckowicz et Hadrien Renaud \ No newline at end of file diff --git a/notes/hist.json b/notes/hist.json new file mode 100644 index 0000000000000000000000000000000000000000..57723b6c4ed07d8642134189816ad96249cb9540 --- /dev/null +++ b/notes/hist.json @@ -0,0 +1,8 @@ +{ + "title": "Historiques", + "children": [ + "hist_graphql", + "hist_ldap", + "hist_rights" + ] +} \ No newline at end of file diff --git a/notes/hist.md b/notes/hist.md new file mode 100644 index 0000000000000000000000000000000000000000..84ddc0088c8613dda92fcdb0776e8546cc6f5956 --- /dev/null +++ b/notes/hist.md @@ -0,0 +1,4 @@ +Historiques +=== + +Le projet sigma a un passif. C'est le travail accumulé de plusieurs promotions qui ont été obligé de faire un certain nombre de choix techniques ou purement esthétiques. Avant de pourrir leur travail conscrit, lis ces quelques lignes. \ No newline at end of file diff --git a/notes/hist_graphql.json b/notes/hist_graphql.json new file mode 100644 index 0000000000000000000000000000000000000000..17834f602fbd2873a6c2c8b66454aaacee9cbb5d --- /dev/null +++ b/notes/hist_graphql.json @@ -0,0 +1,4 @@ +{ + "title": "Historique GraphQL, ou comment gérer les relations de couple entre front et back-end", + "children": [] +} \ No newline at end of file diff --git a/notes/hist_graphql.md b/notes/hist_graphql.md new file mode 100644 index 0000000000000000000000000000000000000000..f23b5ba184fe1ab4b4f54a1a85f211df05deac9b --- /dev/null +++ b/notes/hist_graphql.md @@ -0,0 +1,106 @@ +Remarques sur *l'implémentation*, explication de choix de design... + +Pour des commentaires sur la définition du schéma, se reporter plutôt aux commentaires en en-tête des fichiers `actions.graphql` et `objects.graphql`. + +Pour des explications plus high-level sur la gestion des droits et les authorizations sur Sigma, voir le memo idoine {@tutorial hist_rights}. + +## Structure du dossier `src/graphql` + +```bash +graphql +├── models +│ ├── authorization.ts +│ ├── groupModel.ts +│ ├── messageModel.ts +│ ├── requestModel.ts +│ ├── tools.ts +│ └── userModel.ts +├── object_resolvers +│ ├── groups.ts +│ ├── messages.ts +│ ├── requests.ts +│ └── users.ts +├── resolvers.ts +├── schema.js +└── typeDefs + ├── actions.graphql + ├── objects.graphql + └── queries.d.ts +``` + +Les sections suivantes expliqueront la logique des models et object_resolvers ; pour l'instant,expliquons quelques points simples. + +### "Points d'entrée" +`graphql/schema.js` exporte tout le schéma GraphQL, utilisé pour configurer ApolloServer dans `app.ts`. + +A la réception d'une requête GraphQL, avant de la resolver, une instance de chaque object model de `graphql/models/` est créée, et passée dans le context. Ceci est aussi effectué dans `app.ts` (`const context = ...`). + +### La définition du schéma : `src/graphql/typeDefs` + +C'est assez straightforward quand on connaît les bases de GraphQL... + +`queries.d.ts` définit l'interface Typescript `Context`. + +### Export des resolvers `graphql/resolvers.ts` + +Du point de vue de `graphql/schema.js`, qui les importe, le fichier `graphql/resolvers.ts` exporte bien tous les resolvers de tous les types du schéma GraphQL. +Pourtant, on ne définit dans `graphql/resolvers.ts` que les resolvers pour les types Query et Mutations. +En effet on exploite les default resolvers fournis par Apollo Server (le deuxième point) : + +> Explicit resolvers are not needed for every type, since Apollo Server provides a default that can perform two actions depending on the contents of parent: +> - Return the property from parent with the relevant field name +> - Calls a function on parent with the relevant field name and provide the remaining resolver parameters as arguments +https://www.apollographql.com/docs/apollo-server/v2/essentials/data.html#default + +Dans notre cas, le type de retour de chaque resolver est un objet JS (Group, Message, Request ou User) défini dans un des fichiers de `graphql/object_resolvers/` ; donc les méthodes des classes Group, Message, Request et User dont le prototype est (parent, args, context: Context, info) sont utilisées comme resolvers de l'objet GraphQL respectif. + +(Les sections suivantes donnent plus de précisions sur la logique derrière ce dernier paragraphe.) + + +## Resolvers/Models/Connectors + +Les notions de models et de connectors sont assez communes en développement web. Une définition informelle appliquée au contexte de GraphQL est donnée ici : + +https://blog.apollographql.com/how-to-build-graphql-servers-87587591ded5 +> Model: a set of functions to read/write data of a certain GraphQL type by using various connectors. Models contain additional business logic, such as permission checks, and are usually application-specific. + +> Connector: a layer on top of a database/backend driver that has GraphQL-specific error handling, logging, caching and batching. Only needs to be implemented once for each backend, and can be reused in many apps + +> By using models and connectors, my resolvers turned into simple switches that map inputs and arguments to a function call in the underlying model layer, where the actual business logic resides. + +> Note: To be clear, models and connectors aren’t abstractions that are built into GraphQL, but they emerge as a natural way to structure code in a GraphQL server. + +### Connectors + +Dans notre cas, on utilise à la fois une base de données propre à Sigma (PostgreSQL, manipulée avec Knexjs) et un LDAP (manipulé avec les fonctions de `src/ldap/export/`, développées par Quentin Chevalier, qu'il appelle son "API LDAP"). + +Knexjs et l'"API LDAP" sont les équivalents des Connectors pour notre projet. Pas besoin de se forcer à faire une couche supplémentaire, ça ne serait pas utile. + + +### Les resolvers des actions : `graphql/resolvers.ts` + +### Les resolvers des objets : `graphql/object_resolvers/` + +Les classes JS définies dans les fichiers de `graphql/object_resolvers/` jouent à la fois le rôle de resolvers (plus précisément, ont des méthodes qui sont utilisées comme resolvers), et de return type pour les resolvers. +C'est ceci qui permet d'exploiter à fond les defaults resolvers d'Apollo Server. + +Une bonne façon de le voir est de dire que : +- du point de vue de l'export du schéma (donc de `schema.js` et `resolvers.ts`), ces fichiers servent à définir les resolvers, tout simplement +- du point de vue des autres fichiers qui les importent, ces fichiers définissent des return types de resolvers. + +### Models : `graphql/models` + +Comme indiqué plus haut, une instance de chaque object model est créée et passée dans le context, à chaque réception d'une requête GraphQL. En fait, puisque les constructeurs ne prennent en paramètre que l'uid de l'utilisateur qui fait la requête, on peut dire qu'un set de models est créé pour chaque utilisateur. + +Autrement dit, chaque utilisateur a ses propres "data accessors", sa propre "vue" de la BDD, personnalisée pour prendre en compte ses memberOfs. + + + +## Implémentation de la gestion des authorizations + +En anglais, authori*s*ation est une orthographe valide, mais pour plus d'homogénéité et pour reprendre l'usage habituel sur internet, on utilisera l'orthographe américaine authori*z*ation. + +Comme déjà indiqué plus haut, pour des explications plus high-level sur la gestion des droits et les authorizations sur Sigma, voir le memo idoine (`memo_sigma_rights.md`). + +### authorization.ts + diff --git a/notes/hist_ldap.json b/notes/hist_ldap.json new file mode 100644 index 0000000000000000000000000000000000000000..cbe5b98108928441e56bb2810c66c0813a4c1a2a --- /dev/null +++ b/notes/hist_ldap.json @@ -0,0 +1,4 @@ +{ + "title": "Historique de l'API LDAP", + "children": [] +} \ No newline at end of file diff --git a/notes/hist_ldap.md b/notes/hist_ldap.md new file mode 100644 index 0000000000000000000000000000000000000000..52c0f8bbbc8bbb8c937216b428f08e3ecf1e4ce6 --- /dev/null +++ b/notes/hist_ldap.md @@ -0,0 +1,39 @@ +## Sur le LDAP Frankiz + +A l'origine Frankiz, le prédécesseur de sigma, avait sa propre base de données qu'il importait toutes les 3h dans un LDAP énorme et sale, qui contenait le strict minimum. Ce LDAP s'est révélé très utile à différents sites binets qui ont fait un usage très libre de cette ressource. + +De sorte que quand il fallut remplacer Frankiz, la destruction du LDAP n'était pas possible sans revoir plusieurs sites binets (à voir, le CAS était peut-être une solution suffisante). On a donc choisi de tuer la BDD Frankiz, mais seulement après avoir considérablement enrichi le LDAP pour en faire la BDD utilisateurs et groupes principale de sigma. + +Une documentation vieillissante est disponible sur le [`wikix`](https://wikibr.binets.fr/Admin:LDAP) par rapport au LDAP de Frankiz. + +Le BR2017 a mené un gros travail de réorganisation du LDAP donc beaucoup d'informations sur le wikix ne seront plus valides. + +### Nouvelle organisation du LDAP + +Le LDAP a été significativement enrichi depuis Frankiz, puisqu'il contient la majeure partie des données utilisateurs et groupes de sigma. Les champs parent et child permettent aux resolvers afférents de faire descendre les admins et remonter les membres, mais n'a pas d'influence à l'intérieur de l'API LDAP. + + + + + + + +#### Configuration + +L'équivalence pratique entre champs du LDAP et champs du code est dans le fichier de configuration [`ldap_config`](../ldap_config.json). Idéalement, ce .json serait ensuite porté en une calsse dynamique dans le code. En atendant une structure pareille, on se contentera des structures de données décrites dans [`config`](../src/ldap/internal/config.ts). + +#### Une structure particulière + +Les admins et membres sont de deux types ; les admins et membres stricts, acceptés par un administrateur dans le groupe, et les admins et membres hérités, qui ont rejoint ce groupe en remontant ou en descendant la structure de parenté ; voir {@tutorial hist_rights}. + +### Etat de l'API + +Cette API est voulue comme la plus simple et la plus minimaliste possible ; en effet, elle sera à réimplémenter sur toutes les instances différentes de sigma pour s'adapter aux structures de données des différentes écoles qui choisissent d'adopter sigma. On peut administrer des groupes depuis sigma, mais pour s'interfacer d'instances à instances seules deux fonctions sont indispensables ; peek et search. + +Cette API, bien que relativement naïve, doit être capable de garder la syn,chronisation entre les deux arbres du LDAP et ainsi que les problèmes de récursion posées par le choix de la structure en admin descendant et membres montant. + + + +Le détail de chaque fonction est disponible dans la documentation JSDOC : ([`User`](../doc/LDAP.User.html) et [`Group`](../doc/LDAP.Group.html)). + +La lutte contre les injections est faite au plus bas niveau possible en utilisant [`ldapEscape`](https://www.npmjs.com/package/ldap-escape) directement dans la classe [`Basics`](../doc/LDAP.Basics.html). En effet, les injections LDAP sont relativement voyantes et ne peuvent pas se faire avec des caractères usuels. \ No newline at end of file diff --git a/notes/hist_rights.json b/notes/hist_rights.json new file mode 100644 index 0000000000000000000000000000000000000000..0dd11135e2bb0f6720478e71058fbd07da9996b9 --- /dev/null +++ b/notes/hist_rights.json @@ -0,0 +1,4 @@ +{ + "title": "Bilan des droits sur sigma", + "children": [] +} \ No newline at end of file diff --git a/notes/hist_rights.md b/notes/hist_rights.md new file mode 100644 index 0000000000000000000000000000000000000000..cab6b0fde8f85f30a991025645658d6862190b3a --- /dev/null +++ b/notes/hist_rights.md @@ -0,0 +1,113 @@ +## Overview + +Chaque utilisateur a un certain niveau de droit sur chaque groupe. Ce niveau de droit indique ce qu'il a le droit de savoir et de faire. + +### Les differents niveaux de droit [specification de actions.graphql] +*Par rapport à un groupe donné*, un user peut avoir différents niveaux de droits : +- none : ne sait meme pas que le groupe existe, aucun autre droit (typiquement, une connection où l'utilisateur ne s'est pas authentifié) +- authenticated : sait que le groupe existe, aucun autre droit (une connection on-platal sans auth, ou une connection authentifiée) +- viewer : le user a aussi accès à l'activité publique du groupe : frontpage, Q&A, liste des membres, speakers et admins, Message dont le groupe est destinataire +- member : le user a aussi acces à l'activité interne du groupe : les PrivatePost, ainsi que les Message dont le groupe est auteur +- speaker : le user peut aussi parler au nom du groupe. Il a le droit de publier des annonces et d'organiser des évènements +- admin : le user a tous les droits sur le groupe + +### Le graphe organique des groupes + +Les champs `parent` et `children` de l'objet `Group` dans le schéma GraphQL définissent un graphe des groupes, appelé *graphe organique des groupes* car il définit les héritages de droits d'admin (l'admin d'un groupe peut administrer les groupes enfants) (de la même facon qu'une hiérarchie organique militaire). Ce graphe ne doit pas contenir de cycles, i.e. doit être un arbre. + +### Conditions pour les niveaux de droit + +Un des rôles du *graphe organique des groupes* est de définir le niveau de droit des users pour chaque groupe. + +D'abord, petit détail de terminologie : les cinq niveaux de droits sont inclus les uns dans les autres (un speaker est aussi un viewer, par ex.) (sauf pour les permissions héritées) + +Plus précisément, on a les inclusions suivantes : admin strict > speaker > membre strict > membre hérité > viewer > authenticated. +Il n'y a que admin hérité qui n'est pas nécessairement inclus dans les autres niveaux de droits. + +Dans la BDD sous-jacente, il faut que pour chaque groupe, on ait admin > speaker > member. + +Détaillons ici les conditions exactes pour avoir un niveau de droit donné. + +#### Pour les groupes simples +- Member : + - Un user est membre strict du groupe G s'il est member de G selon la BDD sous-jacente. + - Un user est membre hérité du groupe G s'il est membre strict d'un de ses descendants. +- Speaker : un user est speaker du groupe G s'il est speaker de G selon la BDD. Pas de notion d'héritage de speaker. +- Admin : + - Un user est admin strict du groupe G s'il est admin de G selon la BDD. + - Un user est admin hérité du groupe G s'il est admin strict d'un de ses ascendants. +- Viewer : un user est viewer du groupe G + - s'il est membre hérité de G. + - s'il est membre hérité d'un groupe immédiatement parent de G (one-edge-down visibility), ou + - s'il est membre hérité d'un groupe faisant partie du champ "visibilityEdge" de G + - s'il est membre d'un métagroupe dont G est membre (implicit visibility-edges). +- Dans tous les autres cas, le user a le niveau de droits "none" ou "authenticated", selon le cas de figure. + +#### Pour les méta-groupes +- Un user est membre d'un méta-groupe G s'il est membre (hérité) d'un groupe simple dans G. +- Un user est speaker d'un méta-groupe G s'il est speaker d'un groupe simple dans G. +- Un user est admin d'un méta-groupe G s'il est admin (hérité) d'un groupe simple dans G. +- Un user est viewer d'un méta-groupe G s'il est viewer d'un groupe simple dans G. + +L'autre rôle du *graphe organique des groupes* est de permettre l'administration "en cascade" des groupes enfants. + +Si un groupe est le parent d'un autre, alors les admins du groupe parent sont admins (hérités) du groupe enfant. Exemples : +- BR est parent de Chocapix. L'admin de Chocapix est parti en vacances. L'admin de BR peut, en tant qu'admin (hérité) de Chocapix, faire ce qu'il a à faire, sans attendre le retour du respo Chocapix. +- Cotisants-Kès est parent de Troll'X. Troll'X fait n'importe quoi (en floodant de Messages par ex). Les admins de Cotisants-Kès (les kessiers) peuvent, comme ils sont admins hérités de Troll'X, stopper les dégâts. + +Remarque sur speaker : +Il s'agit d'un nouveau niveau de droit par rapport à Frankiz 3.0 ; qui est a présent implémenté sur le LDAP bloaziadur. + +## Dans l'implémentation + +Le système GraphQL est pensé comme l'interface par laquelle les utilisateurs interagissent avec sigma, les graphismes en moins. Le client peut envoyer tout type de requête. C'est au niveau des resolvers que les permissions sont gérées. D'où le @rights + +### Dans les resolvers GraphQL : le tag @rights +Utilise par JSDoc +Le tag @rights est la gestion des autorisations. +Utilise dans resolvers.ts + +Certaines fonctions de connectors effectuent des vérifications d'authorizations avant de renvoyer une réponse, d'autres non. Pour être sur qu'on ne renvoie jamais de réponse sans avoir au préalable effectué les bonnes vérifications, chaque fonction possède dans sa description un attribut droit, qui décrit les droits que requiert cette fonction. + +La valeur de @rights peut être : +- `super` - la fonction n'effectue aucune vérification, et renvoie le resultat demandé +- `admin( groupID )` - la fonction ne fait que ce qu'un admin du groupe indiqué aurait le droit de faire +- `speaker( groupUID )`, `member( groupUID )`, `viewer( groupUID )` - même chose +- `user` - la fonction ne fait que ce que l'utiliateur a le droit de faire (vérifications via l'argument user) + +La procédure a suivre est la suivante : quand une fonction possède un certain niveau de droit, elle ne peut appeler une fonction possédant un niveau de droit plus large que si : +- 1- on a au préalable vérifié que l'utilisateur possédait effectivement ces droits. +*ou* +- 2- on s'est assuré que l'opération effectuée par cet appel particulier de la fonction était dans les droits de l'utilisateur + +D'un point de vue pratique : +- les **resolvers** doivent **vérifier** que le user possede les droits énoncés dans le tag @rights, avant de renvoyer un résultat / modifier la BDD. Ils vérifient aussi que le user possede les droits nécessaires pour appeler des fonctions dans les models. +- les fonctions dans les **models** peuvent **supposer** que les droits du tag @rights sont acquis, ie. qu'ils ont été vérifiés par la fonction appelante au préalable. + +#### Quel @rights donner a quels resolvers ? + +Les resolvers de base de mutation et query ont des droits user. + +Les fonctions qui ne modifient pas la BDD et ne renvoient pas de données sur la BDD n'ont pas de rights. + +### Le cas des Requests +cf `objects.graphql` + +Les Mutations concernant les *Requests* suivent à peu près toutes le schéma suivant : +- \<typeAuteur>Request\<NatureDeLaRequest>(auteur, destinataire, comment): Request +- accept\<NatureDeLaRequest>Request(request: ID!, comment: String): Boolean +- refuse\<NatureDeLaRequest>Request(request: ID!, comment: String): Boolean + - le paramètre est le rid de la Request à accepter ou refuser + - **seul les admins du groupe destinataire peuvent accepter ou refuser une Request** + +Les Mutations concernant les *Messages* suivent à peu près toutes le schéma suivant : +- create\<TypeDeMessage>(auteur, destinataire, title, content, etc.): \<TypeDeMessage> +- edit\<TypeDeMessage>(\<typeDeMessage>ToEdit: ID!): \<TypeDeMessage> +- remove\<TypeDeMessage>(\<typeDeMessage>ToRemove: ID!): Boolean + - = l'auteur supprime le message + - **pour les Messages où l'auteur est un utilisateur, seul l'auteur a le droit de remove son Message** + - **pour les Messages où l'auteur est un groupe, n'importe quel admin du groupe (ou speaker selon le cas) a le droit de remove le Message** +- censor\<TypeDeMessage>(\<typeDeMessage>ToCensor: ID!): Boolean + - = le groupe destinataire supprime le message + - **n'importe quel admin du groupe a le droit de censurer un Message qui lui est adressé** + - (le destinataire est un Group pour tous les Messages) diff --git a/notes/memo_jsdoc.json b/notes/memo_jsdoc.json new file mode 100644 index 0000000000000000000000000000000000000000..0aefcd8dc1bcd6a07381c95944b4ac886d6b95fa --- /dev/null +++ b/notes/memo_jsdoc.json @@ -0,0 +1,4 @@ +{ + "title": "Memo JSDoc, le meilleur ami du newbie", + "children": [] +} \ No newline at end of file diff --git a/notes/memo_jsdoc.md b/notes/memo_jsdoc.md new file mode 100644 index 0000000000000000000000000000000000000000..e9ac628c289d2a2faa567fb9a017025a3e5e7ae8 --- /dev/null +++ b/notes/memo_jsdoc.md @@ -0,0 +1,46 @@ +JSDoc, c'est l'outil de génération automatique de documentation. Il transforme des commentaires et des fichiers Markdown comme celui-ci en une masse de pages web lisibles même par un autiste semi-aveugle à qui il manquerait un module de sémantique. + +La doc de JSDoc, paradoxalement un peu aride : [`Use JSDoc`](http://usejsdoc.org/) + +## Les bases + +### Générer la documentation + +Pour lancer jsdoc la commande est : `jsdoc --configure jsdoc_config.json`, ou plus simplement : `npm run doc` pour un [`package.json`](..\package.json) à jour. + +Nous avons fait le choix de ne pas mettre la documentation de sigma sur le git car les nombreux fichiers html alourdissaient inutilement le répertoire. Tout nouveau développeur du projet est donc invité à lanncer JSDoc pour régénérer la doc en local ! + +### Ecrire la documentation + +JSDoc fonctionne sur la base de token de la forme @truc dans des commentaires avec seulement 2 *. C'est en lisant ces tokens qu'il génère sa documentation. JSDoc comprend le javascript, mais ne génère pas automatiquement les tags à partir du code. Ex : + +``` +/** + * @function renv Fonction bidon + * @arg a - Nombre à renvoyer + */ +function renv(a:number) { return a; } +``` +Là on lui donne une description d'une fonction et une documentation de son argument, mais pas le type de a, qu'il n'infère pas seul. Ca peut mener à des différences entre le vrai type et le type documenté, surtout quand le code évolue vite, mais ça peut aussi permettre de configurer complètement la doc à la fonction près, ce qui est pas mal non plus. + +## Les détails + +### Fichier de configuration + +JSDoc est configuré via le fichier [`jsdoc_config.json`](..\jsdoc_config.json) à la racine du projet. Ce fichier précise à JSDoc quels fichiers traiter, où ranger ses résultats, quels extensions utiliser, quels fichiers ignorer, etc... + +### JSDoc et VSCode + +JSDoc et VSCode marchent très bien ensemble ; VSCode générera automatiquement les tags afférents à une fonction déjà rédigée si vous commencez un commentaire juste avant cete fonction. + +### L'arborescence + +JSDoc n'est pas bon pour faire appraître une arborescence de fichiers. Il va afficher toutes les fonctions qu'il parse sur la page d'accueil, indépendamment de leur nombre ou de leur emplacement dans le projet. Pour rendre la documentation lisible et faire apparître une vraie arborescence, il convient d'utiliser des namespace ou des classes, quitte à ce qu'elles soient purement statiques, pour factoriser le code par bloc de sens. + +La factorisation de code dans des classes étant en soit une bonne pratique et permettant aussi au développeur de réfléchir à l'organisation de son programme, cette pratique devrait être encouragée de manière globale dans tout le projet. + +### Les fichiers. md + +JSDoc a deux façons simples d'intégrer des fichiers Mardown dans la doc ; un README en page d'accueil, ou un tutoriel comme celui-ci. Tous les tutoriels doivent être mis dans le même dossier avec des fichiers de config .json pour définir une arborescence de tutoriels propres. + +N'hésitez pas à enrichir la documentation à votre tour, par des fichiers .md ou directement dans votre code ! \ No newline at end of file diff --git a/notes/memo_knexjs.json b/notes/memo_knexjs.json new file mode 100644 index 0000000000000000000000000000000000000000..e375581b497e4a0f1d9e1d528fc69d714ead8991 --- /dev/null +++ b/notes/memo_knexjs.json @@ -0,0 +1,4 @@ +{ + "title": "Memo knew.js ou quand on a la felmme de faire du SQL", + "children": [] +} \ No newline at end of file diff --git a/memo knexjs.md b/notes/memo_knexjs.md similarity index 95% rename from memo knexjs.md rename to notes/memo_knexjs.md index 842de165a70e7862dffcfdb44ac0978eb855753c..cff213567e7bfb11d394a331402f9c69a68eb8b4 100644 --- a/memo knexjs.md +++ b/notes/memo_knexjs.md @@ -1,10 +1,3 @@ -Mémo d'introduction à Knex.js pour les nuls -=== - -```info -Note : ce mémo a été rédigé à l'origine pour le CONTRIBUTING.md de shitpost-backend, donc fait parfois référence à des fichiers de ce projet. C'est indiqué à chaque fois lorsque c'est le cas. -``` - > [Knex.js](https://knexjs.org/) is a "batteries included" SQL query builder for Postgres, MSSQL, MySQL, MariaDB, SQLite3, Oracle, and Amazon Redshift designed to be flexible, portable, and fun to use. J'utilise Knex.js avec PostgreSQL (comme dans sigma). @@ -79,4 +72,4 @@ Solution : spécifier dans le schéma (fichiers migrations) ce qu'il faut faire On a défini les migrations (`./db/migrations/*`) en se basant sur le schéma GraphQL (`typeDefs.js`) (et pas l'inverse), ce qui se voit bien puisqu'on suit presque exactement ce dernier. **[spécifique à shitpost :]** -Un point particulier cependant : comme expliqué en commentaires du migration-file `./db/migrations/[timestamp]_create_comments.js`, on n'a pas déclaré le champ "forMessage" de la table "comments" comme ayant "messages.id" comme foreign key, car ce n'est pas possible avec PostgreSQL v10. +Un point particulier cependant : comme expliqué en commentaires du migration-file `./db/migrations/[timestamp]_create_comments.js`, on n'a pas déclaré le champ "forMessage" de la table "comments" comme ayant "messages.id" comme foreign key, car ce n'est pas possible avec PostgreSQL v10. \ No newline at end of file diff --git a/notes/memo_ldap.json b/notes/memo_ldap.json new file mode 100644 index 0000000000000000000000000000000000000000..a0627ac10cb6d0878bbc835eaaccbc1780875725 --- /dev/null +++ b/notes/memo_ldap.json @@ -0,0 +1,4 @@ +{ + "title": "Memo LDAP, ni BDD ni annuaire", + "children": [] +} \ No newline at end of file diff --git a/notes/memo_ldap.md b/notes/memo_ldap.md new file mode 100644 index 0000000000000000000000000000000000000000..8d5179bebed5c8fe3af2965b948f90e4ae93d31b --- /dev/null +++ b/notes/memo_ldap.md @@ -0,0 +1,21 @@ +## Qu'est ce qu'un LDAP ? + +LDAP signifie Lightweight Directory Access Protocol ; c'est donc un protocole, comme http, et pas une structure de données à proprement parler. Comme http, il fonctionne par chaîne de caractères appelés URI. La structure d'un LDAP est cependant forcément celle d'un arbre, avec une racine à la fin de l'URI et le noeud recherché à la fin. Par exemple "uid=louis.vaneau,ou=eleves,dc=frankiz,dc=net", qui permet d'accéder à la feuille associée à Louis Vaneau dans le LDAP de frankiz. + +Il s'agit d'une structure particulièrement rigide mais efficace en lecture. Chaque noeud contient un certain nombre de champs qui ne sont pas nécessairement tous remplis, et qui n'ont pas forcément une seule variable par champ. Un LDAP est une structure parfaitement adapté à la réalisation d'un annuaire car pensé pour cela dès l'origine ; très efficace en lecture, il brille moins en écriture. + +La technologie est ancienne et bien connue. Le LDAP de Frankiz, OpenLDAP, contient beaucoup de fonctionnalités sympathique comme le hashage inclus des mots de passe avec salt conformément à l'état de l'art. + +### Un wrapper sympathique... + +On accède sur un LDAP par des requêtes LDAP, d'où l'intérêt d'une bibliothèque comme [`ldap.js`](http://ldapjs.org/) qui fait office de traducteur javascript/LDAP, un peu comme knex et SQL. On s'intéressera particulièrement à la partie [`client`](http://ldapjs.org/client.html) qui est celle qui permet d'agir sur le LDAP. + +Cette bibliothèque est reprise et re-wrappée dans la classe [`Basics`](../src/ldap/internal/basics.ts). + +### Pas complètement dépourvu de dangers ! + +Comme tous les protocoles, LDAP a ses failles. En particulier, il est possible d'injecter des commandes via un nom de domaine (Domain Name ou DN) ou un filtre (filter). Ces dangers sont adressés par sigma en utilisant la bibliothèque [`ldap-escape`](https://www.npmjs.com/package/ldap-escape). + +### Une interface graphique agréable + +Pour explorer un LDAP, vous pouvez utiliser [`JXplorer`](http://jxplorer.org/). Il s'agit d'un logiciel OpenSource en Java très imparfait mais qui permet de rapidement parcourir les champs d'un LDAP et de mener des petites recherches dessus. \ No newline at end of file diff --git a/notes/memo_postgresql.json b/notes/memo_postgresql.json new file mode 100644 index 0000000000000000000000000000000000000000..7634a09c2fdd835ffeabd0d6677adeb6b3ae653b --- /dev/null +++ b/notes/memo_postgresql.json @@ -0,0 +1,4 @@ +{ + "title": "Memo PostgreSQL, ou une technologie de base de données simple", + "children": [] +} \ No newline at end of file diff --git a/memo postgresql.md b/notes/memo_postgresql.md similarity index 98% rename from memo postgresql.md rename to notes/memo_postgresql.md index 4c0863a7622797e0f37d692158bdb4db556850f5..80c5d539c22ac0a2f0315a4f6d3574fd1f1244c3 100644 --- a/memo postgresql.md +++ b/notes/memo_postgresql.md @@ -1,9 +1,6 @@ -Mémo d'introduction à PostgreSQL pour les nuls -=== - Dans un premier temps, on part du principe que l'utilisateur travaille en local, i.e. gère des bases de données (BDDs) locales sur une machine où il a tout contrôle. Les points à adapter dans le cas d'une base de donnée distante, ou d'une utilisation sans droits d'admin, sont traités dans la section correspondante. -La doc de PostgreSQL, admirablement bien faite, lisible et accessible : https://www.postgresql.org/docs/current/static/tutorial.html +La doc de PostgreSQL, admirablement bien faite, lisible et accessible : [`ici`](https://www.postgresql.org/docs/current/static/tutorial.html) ## Les bases @@ -202,4 +199,4 @@ Certains outils permettent de le faire de façon intelligente : ils permettent Par exemple, c'est ce que permet de faire Knex.js, utilisé par le projet sigma notamment. Mais ces deux notions sont plus générales. Par exemple, Symfony (le framework PHP) sait construire des fichiers migrations (i.e. choisir le schéma de votre bdd) automatiquement, en lisant les paramètres spécifiés dans les annotations de vos Entity.php. Django fait aussi un truc similaire. -Un memo Knex.js est disponible... euh... quelque part, il devrait s'appeler "memo knexjs". +Un memo Knex.js est disponible... euh... quelque part, il devrait s'appeler "memo knexjs". \ No newline at end of file diff --git a/notes/memos.json b/notes/memos.json new file mode 100644 index 0000000000000000000000000000000000000000..f3f6c53dc26df0dba9ce0e4aba2fafd2a5403eb2 --- /dev/null +++ b/notes/memos.json @@ -0,0 +1,9 @@ +{ + "title": "Memos", + "children": [ + "memo_knexjs", + "memo_postgresql", + "memo_jsdoc", + "memo_ldap" + ] +} \ No newline at end of file diff --git a/notes/memos.md b/notes/memos.md new file mode 100644 index 0000000000000000000000000000000000000000..8aab15325d1aeb03e41543b18dab7ee04a6ea0a3 --- /dev/null +++ b/notes/memos.md @@ -0,0 +1,4 @@ +Mémos +=== + +Le projet sigma était assez large et comportant pas mal de facettes avec des technologies pas forcément toujours intuitives ou bien documentées, les mémos de cette section sont là pour permettre au dev perdu de s'y retrouver rapidement face à une technologie qu'il ne maîtrise pas à fond. \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 981beb87fc820a5b36dbfbd527b9fb7eb802947b..f3c1323972057da160929726fae93d06f60450b7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,11 +5,11 @@ "requires": true, "dependencies": { "@apollographql/apollo-tools": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@apollographql/apollo-tools/-/apollo-tools-0.2.9.tgz", - "integrity": "sha512-AEIQwPkS0QLbkpb6WyRhV4aOMxuErasp47ABv5niDKOasQH8mrD8JSGKJAHuQxVe4kB8DE9sLRoc5qeQ0KFCHA==", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@apollographql/apollo-tools/-/apollo-tools-0.3.3.tgz", + "integrity": "sha512-/vLzZjloWB4xzgw2MRs9TUDIdCzS+No1hEClkEKqcnH86c2EgE/W0Dv2nkCTH9WxDrfryziJWbNMurYYkm61Zw==", "requires": { - "apollo-env": "0.2.5" + "apollo-env": "0.3.3" } }, "@apollographql/graphql-playground-html": { @@ -18,9 +18,9 @@ "integrity": "sha512-lqK94b+caNtmKFs5oUVXlSpN3sm5IXZ+KfhMxOtr0LR2SqErzkoJilitjDvJ1WbjHlxLI7WtCjRmOLdOGJqtMQ==" }, "@babel/cli": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.2.0.tgz", - "integrity": "sha512-FLteTkEoony0DX8NbnT51CmwmLBzINdlXmiJCSqCLmqWCDA/xk8EITPWqwDnVLbuK0bsZONt/grqHnQzQ15j0Q==", + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.2.3.tgz", + "integrity": "sha512-bfna97nmJV6nDJhXNPeEfxyMjWnt6+IjUAaDPiYRTBlm8L41n8nvw6UAqUCbvpFfU246gHPxW7sfWwqtF4FcYA==", "dev": true, "requires": { "chokidar": "^2.0.3", @@ -33,155 +33,56 @@ "output-file-sync": "^2.0.0", "slash": "^2.0.0", "source-map": "^0.5.0" + }, + "dependencies": { + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true + } } }, "@babel/code-frame": { - "version": "7.0.0-beta.44", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0-beta.44.tgz", - "integrity": "sha512-cuAuTTIQ9RqcFRJ/Y8PvTh+paepNcaGxwQwjIDRWPXmzzyAeCO4KqS9ikMvq0MCbRk6GlYKwfzStrcP3/jSL8g==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", + "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", "dev": true, "requires": { - "@babel/highlight": "7.0.0-beta.44" + "@babel/highlight": "^7.0.0" } }, "@babel/core": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.2.0.tgz", - "integrity": "sha512-7pvAdC4B+iKjFFp9Ztj0QgBndJ++qaMeonT185wAqUnhipw8idm9Rv1UMyBuKtYjfl6ORNkgEgcsYLfHX/GpLw==", + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.3.4.tgz", + "integrity": "sha512-jRsuseXBo9pN197KnDwhhaaBzyZr2oIcLHHTt2oDdQrej5Qp57dCCJafWx5ivU8/alEYDpssYqv1MUqcxwQlrA==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", - "@babel/generator": "^7.2.0", + "@babel/generator": "^7.3.4", "@babel/helpers": "^7.2.0", - "@babel/parser": "^7.2.0", - "@babel/template": "^7.1.2", - "@babel/traverse": "^7.1.6", - "@babel/types": "^7.2.0", + "@babel/parser": "^7.3.4", + "@babel/template": "^7.2.2", + "@babel/traverse": "^7.3.4", + "@babel/types": "^7.3.4", "convert-source-map": "^1.1.0", "debug": "^4.1.0", "json5": "^2.1.0", - "lodash": "^4.17.10", + "lodash": "^4.17.11", "resolve": "^1.3.2", "semver": "^5.4.1", "source-map": "^0.5.0" }, "dependencies": { - "@babel/code-frame": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", - "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", - "dev": true, - "requires": { - "@babel/highlight": "^7.0.0" - } - }, - "@babel/generator": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.2.0.tgz", - "integrity": "sha512-BA75MVfRlFQG2EZgFYIwyT1r6xSkwfP2bdkY/kLZusEYWiJs4xCowab/alaEaT0wSvmVuXGqiefeBlP+7V1yKg==", - "dev": true, - "requires": { - "@babel/types": "^7.2.0", - "jsesc": "^2.5.1", - "lodash": "^4.17.10", - "source-map": "^0.5.0", - "trim-right": "^1.0.1" - } - }, - "@babel/helper-function-name": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", - "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.0.0", - "@babel/template": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", - "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0.tgz", - "integrity": "sha512-MXkOJqva62dfC0w85mEf/LucPPS/1+04nmmRMPEBUB++hiiThQ2zPtX/mEWQ3mtzCEjIJvPY8nuwxXtQeQwUag==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@babel/highlight": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", - "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - } - }, - "@babel/template": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.1.2.tgz", - "integrity": "sha512-SY1MmplssORfFiLDcOETrW7fCLl+PavlwMh92rrGcikQaRq4iWPVH0MpwPpY3etVMx6RnDjXtr6VZYr/IbP/Ag==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.1.2", - "@babel/types": "^7.1.2" - } - }, - "@babel/traverse": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.1.6.tgz", - "integrity": "sha512-CXedit6GpISz3sC2k2FsGCUpOhUqKdyL0lqNrImQojagnUMXf8hex4AxYFRuMkNGcvJX5QAFGzB5WJQmSv8SiQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/generator": "^7.1.6", - "@babel/helper-function-name": "^7.1.0", - "@babel/helper-split-export-declaration": "^7.0.0", - "@babel/parser": "^7.1.6", - "@babel/types": "^7.1.6", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.10" - } - }, - "@babel/types": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.2.0.tgz", - "integrity": "sha512-b4v7dyfApuKDvmPb+O488UlGuR1WbwMXFsO/cyqMrnfvRAChZKJAYeeglWTjUO1b9UghKKgepAQM5tsvBJca6A==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.10", - "to-fast-properties": "^2.0.0" - } - }, "debug": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz", - "integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "dev": true, "requires": { "ms": "^2.1.1" } }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, "json5": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.0.tgz", @@ -193,7 +94,7 @@ }, "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true }, @@ -206,14 +107,14 @@ } }, "@babel/generator": { - "version": "7.0.0-beta.44", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.0.0-beta.44.tgz", - "integrity": "sha512-5xVb7hlhjGcdkKpMXgicAVgx8syK5VJz193k0i/0sLP6DzE6lRrU1K3B/rFefgdo9LPGMAOOOAWW4jycj07ShQ==", + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.3.4.tgz", + "integrity": "sha512-8EXhHRFqlVVWXPezBW5keTiQi/rJMQTg/Y9uVCEZ0CAF3PKtCCaVRnp64Ii1ujhkoDhhF1fVsImoN4yJ2uz4Wg==", "dev": true, "requires": { - "@babel/types": "7.0.0-beta.44", + "@babel/types": "^7.3.4", "jsesc": "^2.5.1", - "lodash": "^4.2.0", + "lodash": "^4.17.11", "source-map": "^0.5.0", "trim-right": "^1.0.1" } @@ -225,19 +126,6 @@ "dev": true, "requires": { "@babel/types": "^7.0.0" - }, - "dependencies": { - "@babel/types": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.2.0.tgz", - "integrity": "sha512-b4v7dyfApuKDvmPb+O488UlGuR1WbwMXFsO/cyqMrnfvRAChZKJAYeeglWTjUO1b9UghKKgepAQM5tsvBJca6A==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.10", - "to-fast-properties": "^2.0.0" - } - } } }, "@babel/helper-builder-binary-assignment-operator-visitor": { @@ -248,19 +136,6 @@ "requires": { "@babel/helper-explode-assignable-expression": "^7.1.0", "@babel/types": "^7.0.0" - }, - "dependencies": { - "@babel/types": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.2.0.tgz", - "integrity": "sha512-b4v7dyfApuKDvmPb+O488UlGuR1WbwMXFsO/cyqMrnfvRAChZKJAYeeglWTjUO1b9UghKKgepAQM5tsvBJca6A==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.10", - "to-fast-properties": "^2.0.0" - } - } } }, "@babel/helper-call-delegate": { @@ -272,213 +147,20 @@ "@babel/helper-hoist-variables": "^7.0.0", "@babel/traverse": "^7.1.0", "@babel/types": "^7.0.0" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", - "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", - "dev": true, - "requires": { - "@babel/highlight": "^7.0.0" - } - }, - "@babel/generator": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.2.0.tgz", - "integrity": "sha512-BA75MVfRlFQG2EZgFYIwyT1r6xSkwfP2bdkY/kLZusEYWiJs4xCowab/alaEaT0wSvmVuXGqiefeBlP+7V1yKg==", - "dev": true, - "requires": { - "@babel/types": "^7.2.0", - "jsesc": "^2.5.1", - "lodash": "^4.17.10", - "source-map": "^0.5.0", - "trim-right": "^1.0.1" - } - }, - "@babel/helper-function-name": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", - "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.0.0", - "@babel/template": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", - "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0.tgz", - "integrity": "sha512-MXkOJqva62dfC0w85mEf/LucPPS/1+04nmmRMPEBUB++hiiThQ2zPtX/mEWQ3mtzCEjIJvPY8nuwxXtQeQwUag==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@babel/highlight": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", - "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - } - }, - "@babel/template": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.1.2.tgz", - "integrity": "sha512-SY1MmplssORfFiLDcOETrW7fCLl+PavlwMh92rrGcikQaRq4iWPVH0MpwPpY3etVMx6RnDjXtr6VZYr/IbP/Ag==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.1.2", - "@babel/types": "^7.1.2" - } - }, - "@babel/traverse": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.1.6.tgz", - "integrity": "sha512-CXedit6GpISz3sC2k2FsGCUpOhUqKdyL0lqNrImQojagnUMXf8hex4AxYFRuMkNGcvJX5QAFGzB5WJQmSv8SiQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/generator": "^7.1.6", - "@babel/helper-function-name": "^7.1.0", - "@babel/helper-split-export-declaration": "^7.0.0", - "@babel/parser": "^7.1.6", - "@babel/types": "^7.1.6", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.10" - } - }, - "@babel/types": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.2.0.tgz", - "integrity": "sha512-b4v7dyfApuKDvmPb+O488UlGuR1WbwMXFsO/cyqMrnfvRAChZKJAYeeglWTjUO1b9UghKKgepAQM5tsvBJca6A==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.10", - "to-fast-properties": "^2.0.0" - } - }, - "debug": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz", - "integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true - } } }, "@babel/helper-create-class-features-plugin": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.2.1.tgz", - "integrity": "sha512-EsEP7XLFmcJHjcuFYBxYD1FkP0irC8C9fsrt2tX/jrAi/eTnFI6DOPgVFb+WREeg1GboF+Ib+nCHbGBodyAXSg==", + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.3.4.tgz", + "integrity": "sha512-uFpzw6L2omjibjxa8VGZsJUPL5wJH0zzGKpoz0ccBkzIa6C8kWNUbiBmQ0rgOKWlHJ6qzmfa6lTiGchiV8SC+g==", "dev": true, "requires": { "@babel/helper-function-name": "^7.1.0", "@babel/helper-member-expression-to-functions": "^7.0.0", "@babel/helper-optimise-call-expression": "^7.0.0", "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-replace-supers": "^7.1.0" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", - "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", - "dev": true, - "requires": { - "@babel/highlight": "^7.0.0" - } - }, - "@babel/helper-function-name": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", - "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.0.0", - "@babel/template": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", - "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@babel/highlight": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", - "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - } - }, - "@babel/template": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.1.2.tgz", - "integrity": "sha512-SY1MmplssORfFiLDcOETrW7fCLl+PavlwMh92rrGcikQaRq4iWPVH0MpwPpY3etVMx6RnDjXtr6VZYr/IbP/Ag==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.1.2", - "@babel/types": "^7.1.2" - } - }, - "@babel/types": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.2.0.tgz", - "integrity": "sha512-b4v7dyfApuKDvmPb+O488UlGuR1WbwMXFsO/cyqMrnfvRAChZKJAYeeglWTjUO1b9UghKKgepAQM5tsvBJca6A==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.10", - "to-fast-properties": "^2.0.0" - } - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - } + "@babel/helper-replace-supers": "^7.3.4", + "@babel/helper-split-export-declaration": "^7.0.0" } }, "@babel/helper-define-map": { @@ -490,76 +172,6 @@ "@babel/helper-function-name": "^7.1.0", "@babel/types": "^7.0.0", "lodash": "^4.17.10" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", - "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", - "dev": true, - "requires": { - "@babel/highlight": "^7.0.0" - } - }, - "@babel/helper-function-name": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", - "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.0.0", - "@babel/template": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", - "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@babel/highlight": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", - "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - } - }, - "@babel/template": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.1.2.tgz", - "integrity": "sha512-SY1MmplssORfFiLDcOETrW7fCLl+PavlwMh92rrGcikQaRq4iWPVH0MpwPpY3etVMx6RnDjXtr6VZYr/IbP/Ag==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.1.2", - "@babel/types": "^7.1.2" - } - }, - "@babel/types": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.2.0.tgz", - "integrity": "sha512-b4v7dyfApuKDvmPb+O488UlGuR1WbwMXFsO/cyqMrnfvRAChZKJAYeeglWTjUO1b9UghKKgepAQM5tsvBJca6A==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.10", - "to-fast-properties": "^2.0.0" - } - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - } } }, "@babel/helper-explode-assignable-expression": { @@ -570,150 +182,26 @@ "requires": { "@babel/traverse": "^7.1.0", "@babel/types": "^7.0.0" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", - "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", - "dev": true, - "requires": { - "@babel/highlight": "^7.0.0" - } - }, - "@babel/generator": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.2.0.tgz", - "integrity": "sha512-BA75MVfRlFQG2EZgFYIwyT1r6xSkwfP2bdkY/kLZusEYWiJs4xCowab/alaEaT0wSvmVuXGqiefeBlP+7V1yKg==", - "dev": true, - "requires": { - "@babel/types": "^7.2.0", - "jsesc": "^2.5.1", - "lodash": "^4.17.10", - "source-map": "^0.5.0", - "trim-right": "^1.0.1" - } - }, - "@babel/helper-function-name": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", - "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.0.0", - "@babel/template": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", - "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0.tgz", - "integrity": "sha512-MXkOJqva62dfC0w85mEf/LucPPS/1+04nmmRMPEBUB++hiiThQ2zPtX/mEWQ3mtzCEjIJvPY8nuwxXtQeQwUag==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@babel/highlight": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", - "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - } - }, - "@babel/template": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.1.2.tgz", - "integrity": "sha512-SY1MmplssORfFiLDcOETrW7fCLl+PavlwMh92rrGcikQaRq4iWPVH0MpwPpY3etVMx6RnDjXtr6VZYr/IbP/Ag==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.1.2", - "@babel/types": "^7.1.2" - } - }, - "@babel/traverse": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.1.6.tgz", - "integrity": "sha512-CXedit6GpISz3sC2k2FsGCUpOhUqKdyL0lqNrImQojagnUMXf8hex4AxYFRuMkNGcvJX5QAFGzB5WJQmSv8SiQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/generator": "^7.1.6", - "@babel/helper-function-name": "^7.1.0", - "@babel/helper-split-export-declaration": "^7.0.0", - "@babel/parser": "^7.1.6", - "@babel/types": "^7.1.6", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.10" - } - }, - "@babel/types": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.2.0.tgz", - "integrity": "sha512-b4v7dyfApuKDvmPb+O488UlGuR1WbwMXFsO/cyqMrnfvRAChZKJAYeeglWTjUO1b9UghKKgepAQM5tsvBJca6A==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.10", - "to-fast-properties": "^2.0.0" - } - }, - "debug": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz", - "integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true - } } }, "@babel/helper-function-name": { - "version": "7.0.0-beta.44", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.0.0-beta.44.tgz", - "integrity": "sha512-MHRG2qZMKMFaBavX0LWpfZ2e+hLloT++N7rfM3DYOMUOGCD8cVjqZpwiL8a0bOX3IYcQev1ruciT0gdFFRTxzg==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", + "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "7.0.0-beta.44", - "@babel/template": "7.0.0-beta.44", - "@babel/types": "7.0.0-beta.44" + "@babel/helper-get-function-arity": "^7.0.0", + "@babel/template": "^7.1.0", + "@babel/types": "^7.0.0" } }, "@babel/helper-get-function-arity": { - "version": "7.0.0-beta.44", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0-beta.44.tgz", - "integrity": "sha512-w0YjWVwrM2HwP6/H3sEgrSQdkCaxppqFeJtAnB23pRiJB5E/O9Yp7JAAeWBl+gGEgmBFinnTyOv2RN7rcSmMiw==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", + "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", "dev": true, "requires": { - "@babel/types": "7.0.0-beta.44" + "@babel/types": "^7.0.0" } }, "@babel/helper-hoist-variables": { @@ -723,19 +211,6 @@ "dev": true, "requires": { "@babel/types": "^7.0.0" - }, - "dependencies": { - "@babel/types": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.2.0.tgz", - "integrity": "sha512-b4v7dyfApuKDvmPb+O488UlGuR1WbwMXFsO/cyqMrnfvRAChZKJAYeeglWTjUO1b9UghKKgepAQM5tsvBJca6A==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.10", - "to-fast-properties": "^2.0.0" - } - } } }, "@babel/helper-member-expression-to-functions": { @@ -745,19 +220,6 @@ "dev": true, "requires": { "@babel/types": "^7.0.0" - }, - "dependencies": { - "@babel/types": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.2.0.tgz", - "integrity": "sha512-b4v7dyfApuKDvmPb+O488UlGuR1WbwMXFsO/cyqMrnfvRAChZKJAYeeglWTjUO1b9UghKKgepAQM5tsvBJca6A==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.10", - "to-fast-properties": "^2.0.0" - } - } } }, "@babel/helper-module-imports": { @@ -767,92 +229,20 @@ "dev": true, "requires": { "@babel/types": "^7.0.0" - }, - "dependencies": { - "@babel/types": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.2.0.tgz", - "integrity": "sha512-b4v7dyfApuKDvmPb+O488UlGuR1WbwMXFsO/cyqMrnfvRAChZKJAYeeglWTjUO1b9UghKKgepAQM5tsvBJca6A==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.10", - "to-fast-properties": "^2.0.0" - } - } } }, "@babel/helper-module-transforms": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.1.0.tgz", - "integrity": "sha512-0JZRd2yhawo79Rcm4w0LwSMILFmFXjugG3yqf+P/UsKsRS1mJCmMwwlHDlMg7Avr9LrvSpp4ZSULO9r8jpCzcw==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.2.2.tgz", + "integrity": "sha512-YRD7I6Wsv+IHuTPkAmAS4HhY0dkPobgLftHp0cRGZSdrRvmZY8rFvae/GVu3bD00qscuvK3WPHB3YdNpBXUqrA==", "dev": true, "requires": { "@babel/helper-module-imports": "^7.0.0", "@babel/helper-simple-access": "^7.1.0", "@babel/helper-split-export-declaration": "^7.0.0", - "@babel/template": "^7.1.0", - "@babel/types": "^7.0.0", + "@babel/template": "^7.2.2", + "@babel/types": "^7.2.2", "lodash": "^4.17.10" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", - "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", - "dev": true, - "requires": { - "@babel/highlight": "^7.0.0" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0.tgz", - "integrity": "sha512-MXkOJqva62dfC0w85mEf/LucPPS/1+04nmmRMPEBUB++hiiThQ2zPtX/mEWQ3mtzCEjIJvPY8nuwxXtQeQwUag==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@babel/highlight": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", - "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - } - }, - "@babel/template": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.1.2.tgz", - "integrity": "sha512-SY1MmplssORfFiLDcOETrW7fCLl+PavlwMh92rrGcikQaRq4iWPVH0MpwPpY3etVMx6RnDjXtr6VZYr/IbP/Ag==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.1.2", - "@babel/types": "^7.1.2" - } - }, - "@babel/types": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.2.0.tgz", - "integrity": "sha512-b4v7dyfApuKDvmPb+O488UlGuR1WbwMXFsO/cyqMrnfvRAChZKJAYeeglWTjUO1b9UghKKgepAQM5tsvBJca6A==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.10", - "to-fast-properties": "^2.0.0" - } - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - } } }, "@babel/helper-optimise-call-expression": { @@ -862,19 +252,6 @@ "dev": true, "requires": { "@babel/types": "^7.0.0" - }, - "dependencies": { - "@babel/types": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.2.0.tgz", - "integrity": "sha512-b4v7dyfApuKDvmPb+O488UlGuR1WbwMXFsO/cyqMrnfvRAChZKJAYeeglWTjUO1b9UghKKgepAQM5tsvBJca6A==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.10", - "to-fast-properties": "^2.0.0" - } - } } }, "@babel/helper-plugin-utils": { @@ -903,266 +280,18 @@ "@babel/template": "^7.1.0", "@babel/traverse": "^7.1.0", "@babel/types": "^7.0.0" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", - "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", - "dev": true, - "requires": { - "@babel/highlight": "^7.0.0" - } - }, - "@babel/generator": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.2.0.tgz", - "integrity": "sha512-BA75MVfRlFQG2EZgFYIwyT1r6xSkwfP2bdkY/kLZusEYWiJs4xCowab/alaEaT0wSvmVuXGqiefeBlP+7V1yKg==", - "dev": true, - "requires": { - "@babel/types": "^7.2.0", - "jsesc": "^2.5.1", - "lodash": "^4.17.10", - "source-map": "^0.5.0", - "trim-right": "^1.0.1" - } - }, - "@babel/helper-function-name": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", - "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.0.0", - "@babel/template": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", - "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0.tgz", - "integrity": "sha512-MXkOJqva62dfC0w85mEf/LucPPS/1+04nmmRMPEBUB++hiiThQ2zPtX/mEWQ3mtzCEjIJvPY8nuwxXtQeQwUag==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@babel/highlight": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", - "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - } - }, - "@babel/template": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.1.2.tgz", - "integrity": "sha512-SY1MmplssORfFiLDcOETrW7fCLl+PavlwMh92rrGcikQaRq4iWPVH0MpwPpY3etVMx6RnDjXtr6VZYr/IbP/Ag==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.1.2", - "@babel/types": "^7.1.2" - } - }, - "@babel/traverse": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.1.6.tgz", - "integrity": "sha512-CXedit6GpISz3sC2k2FsGCUpOhUqKdyL0lqNrImQojagnUMXf8hex4AxYFRuMkNGcvJX5QAFGzB5WJQmSv8SiQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/generator": "^7.1.6", - "@babel/helper-function-name": "^7.1.0", - "@babel/helper-split-export-declaration": "^7.0.0", - "@babel/parser": "^7.1.6", - "@babel/types": "^7.1.6", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.10" - } - }, - "@babel/types": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.2.0.tgz", - "integrity": "sha512-b4v7dyfApuKDvmPb+O488UlGuR1WbwMXFsO/cyqMrnfvRAChZKJAYeeglWTjUO1b9UghKKgepAQM5tsvBJca6A==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.10", - "to-fast-properties": "^2.0.0" - } - }, - "debug": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz", - "integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true - } } }, "@babel/helper-replace-supers": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.1.0.tgz", - "integrity": "sha512-BvcDWYZRWVuDeXTYZWxekQNO5D4kO55aArwZOTFXw6rlLQA8ZaDicJR1sO47h+HrnCiDFiww0fSPV0d713KBGQ==", + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.3.4.tgz", + "integrity": "sha512-pvObL9WVf2ADs+ePg0jrqlhHoxRXlOa+SHRHzAXIz2xkYuOHfGl+fKxPMaS4Fq+uje8JQPobnertBBvyrWnQ1A==", "dev": true, "requires": { "@babel/helper-member-expression-to-functions": "^7.0.0", "@babel/helper-optimise-call-expression": "^7.0.0", - "@babel/traverse": "^7.1.0", - "@babel/types": "^7.0.0" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", - "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", - "dev": true, - "requires": { - "@babel/highlight": "^7.0.0" - } - }, - "@babel/generator": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.2.0.tgz", - "integrity": "sha512-BA75MVfRlFQG2EZgFYIwyT1r6xSkwfP2bdkY/kLZusEYWiJs4xCowab/alaEaT0wSvmVuXGqiefeBlP+7V1yKg==", - "dev": true, - "requires": { - "@babel/types": "^7.2.0", - "jsesc": "^2.5.1", - "lodash": "^4.17.10", - "source-map": "^0.5.0", - "trim-right": "^1.0.1" - } - }, - "@babel/helper-function-name": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", - "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.0.0", - "@babel/template": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", - "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0.tgz", - "integrity": "sha512-MXkOJqva62dfC0w85mEf/LucPPS/1+04nmmRMPEBUB++hiiThQ2zPtX/mEWQ3mtzCEjIJvPY8nuwxXtQeQwUag==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@babel/highlight": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", - "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - } - }, - "@babel/template": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.1.2.tgz", - "integrity": "sha512-SY1MmplssORfFiLDcOETrW7fCLl+PavlwMh92rrGcikQaRq4iWPVH0MpwPpY3etVMx6RnDjXtr6VZYr/IbP/Ag==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.1.2", - "@babel/types": "^7.1.2" - } - }, - "@babel/traverse": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.1.6.tgz", - "integrity": "sha512-CXedit6GpISz3sC2k2FsGCUpOhUqKdyL0lqNrImQojagnUMXf8hex4AxYFRuMkNGcvJX5QAFGzB5WJQmSv8SiQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/generator": "^7.1.6", - "@babel/helper-function-name": "^7.1.0", - "@babel/helper-split-export-declaration": "^7.0.0", - "@babel/parser": "^7.1.6", - "@babel/types": "^7.1.6", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.10" - } - }, - "@babel/types": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.2.0.tgz", - "integrity": "sha512-b4v7dyfApuKDvmPb+O488UlGuR1WbwMXFsO/cyqMrnfvRAChZKJAYeeglWTjUO1b9UghKKgepAQM5tsvBJca6A==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.10", - "to-fast-properties": "^2.0.0" - } - }, - "debug": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz", - "integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true - } + "@babel/traverse": "^7.3.4", + "@babel/types": "^7.3.4" } }, "@babel/helper-simple-access": { @@ -1173,65 +302,15 @@ "requires": { "@babel/template": "^7.1.0", "@babel/types": "^7.0.0" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", - "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", - "dev": true, - "requires": { - "@babel/highlight": "^7.0.0" - } - }, - "@babel/highlight": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", - "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - } - }, - "@babel/template": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.1.2.tgz", - "integrity": "sha512-SY1MmplssORfFiLDcOETrW7fCLl+PavlwMh92rrGcikQaRq4iWPVH0MpwPpY3etVMx6RnDjXtr6VZYr/IbP/Ag==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.1.2", - "@babel/types": "^7.1.2" - } - }, - "@babel/types": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.2.0.tgz", - "integrity": "sha512-b4v7dyfApuKDvmPb+O488UlGuR1WbwMXFsO/cyqMrnfvRAChZKJAYeeglWTjUO1b9UghKKgepAQM5tsvBJca6A==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.10", - "to-fast-properties": "^2.0.0" - } - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - } } }, "@babel/helper-split-export-declaration": { - "version": "7.0.0-beta.44", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0-beta.44.tgz", - "integrity": "sha512-aQ7QowtkgKKzPGf0j6u77kBMdUFVBKNHw2p/3HX/POt5/oz8ec5cs0GwlgM8Hz7ui5EwJnzyfRmkNF1Nx1N7aA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0.tgz", + "integrity": "sha512-MXkOJqva62dfC0w85mEf/LucPPS/1+04nmmRMPEBUB++hiiThQ2zPtX/mEWQ3mtzCEjIJvPY8nuwxXtQeQwUag==", "dev": true, "requires": { - "@babel/types": "7.0.0-beta.44" + "@babel/types": "^7.0.0" } }, "@babel/helper-wrap-function": { @@ -1239,287 +318,39 @@ "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.2.0.tgz", "integrity": "sha512-o9fP1BZLLSrYlxYEYyl2aS+Flun5gtjTIG8iln+XuEzQTs0PLagAGSXUcqruJwD5fM48jzIEggCKpIfWTcR7pQ==", "dev": true, - "requires": { - "@babel/helper-function-name": "^7.1.0", - "@babel/template": "^7.1.0", - "@babel/traverse": "^7.1.0", - "@babel/types": "^7.2.0" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", - "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", - "dev": true, - "requires": { - "@babel/highlight": "^7.0.0" - } - }, - "@babel/generator": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.2.0.tgz", - "integrity": "sha512-BA75MVfRlFQG2EZgFYIwyT1r6xSkwfP2bdkY/kLZusEYWiJs4xCowab/alaEaT0wSvmVuXGqiefeBlP+7V1yKg==", - "dev": true, - "requires": { - "@babel/types": "^7.2.0", - "jsesc": "^2.5.1", - "lodash": "^4.17.10", - "source-map": "^0.5.0", - "trim-right": "^1.0.1" - } - }, - "@babel/helper-function-name": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", - "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.0.0", - "@babel/template": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", - "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0.tgz", - "integrity": "sha512-MXkOJqva62dfC0w85mEf/LucPPS/1+04nmmRMPEBUB++hiiThQ2zPtX/mEWQ3mtzCEjIJvPY8nuwxXtQeQwUag==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@babel/highlight": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", - "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - } - }, - "@babel/template": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.1.2.tgz", - "integrity": "sha512-SY1MmplssORfFiLDcOETrW7fCLl+PavlwMh92rrGcikQaRq4iWPVH0MpwPpY3etVMx6RnDjXtr6VZYr/IbP/Ag==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.1.2", - "@babel/types": "^7.1.2" - } - }, - "@babel/traverse": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.1.6.tgz", - "integrity": "sha512-CXedit6GpISz3sC2k2FsGCUpOhUqKdyL0lqNrImQojagnUMXf8hex4AxYFRuMkNGcvJX5QAFGzB5WJQmSv8SiQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/generator": "^7.1.6", - "@babel/helper-function-name": "^7.1.0", - "@babel/helper-split-export-declaration": "^7.0.0", - "@babel/parser": "^7.1.6", - "@babel/types": "^7.1.6", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.10" - } - }, - "@babel/types": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.2.0.tgz", - "integrity": "sha512-b4v7dyfApuKDvmPb+O488UlGuR1WbwMXFsO/cyqMrnfvRAChZKJAYeeglWTjUO1b9UghKKgepAQM5tsvBJca6A==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.10", - "to-fast-properties": "^2.0.0" - } - }, - "debug": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz", - "integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true - } - } - }, - "@babel/helpers": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.2.0.tgz", - "integrity": "sha512-Fr07N+ea0dMcMN8nFpuK6dUIT7/ivt9yKQdEEnjVS83tG2pHwPi03gYmk/tyuwONnZ+sY+GFFPlWGgCtW1hF9A==", - "dev": true, - "requires": { - "@babel/template": "^7.1.2", - "@babel/traverse": "^7.1.5", - "@babel/types": "^7.2.0" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", - "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", - "dev": true, - "requires": { - "@babel/highlight": "^7.0.0" - } - }, - "@babel/generator": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.2.0.tgz", - "integrity": "sha512-BA75MVfRlFQG2EZgFYIwyT1r6xSkwfP2bdkY/kLZusEYWiJs4xCowab/alaEaT0wSvmVuXGqiefeBlP+7V1yKg==", - "dev": true, - "requires": { - "@babel/types": "^7.2.0", - "jsesc": "^2.5.1", - "lodash": "^4.17.10", - "source-map": "^0.5.0", - "trim-right": "^1.0.1" - } - }, - "@babel/helper-function-name": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", - "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.0.0", - "@babel/template": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", - "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0.tgz", - "integrity": "sha512-MXkOJqva62dfC0w85mEf/LucPPS/1+04nmmRMPEBUB++hiiThQ2zPtX/mEWQ3mtzCEjIJvPY8nuwxXtQeQwUag==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@babel/highlight": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", - "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - } - }, - "@babel/template": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.1.2.tgz", - "integrity": "sha512-SY1MmplssORfFiLDcOETrW7fCLl+PavlwMh92rrGcikQaRq4iWPVH0MpwPpY3etVMx6RnDjXtr6VZYr/IbP/Ag==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.1.2", - "@babel/types": "^7.1.2" - } - }, - "@babel/traverse": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.1.6.tgz", - "integrity": "sha512-CXedit6GpISz3sC2k2FsGCUpOhUqKdyL0lqNrImQojagnUMXf8hex4AxYFRuMkNGcvJX5QAFGzB5WJQmSv8SiQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/generator": "^7.1.6", - "@babel/helper-function-name": "^7.1.0", - "@babel/helper-split-export-declaration": "^7.0.0", - "@babel/parser": "^7.1.6", - "@babel/types": "^7.1.6", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.10" - } - }, - "@babel/types": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.2.0.tgz", - "integrity": "sha512-b4v7dyfApuKDvmPb+O488UlGuR1WbwMXFsO/cyqMrnfvRAChZKJAYeeglWTjUO1b9UghKKgepAQM5tsvBJca6A==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.10", - "to-fast-properties": "^2.0.0" - } - }, - "debug": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz", - "integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true - } + "requires": { + "@babel/helper-function-name": "^7.1.0", + "@babel/template": "^7.1.0", + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.2.0" + } + }, + "@babel/helpers": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.3.1.tgz", + "integrity": "sha512-Q82R3jKsVpUV99mgX50gOPCWwco9Ec5Iln/8Vyu4osNIOQgSrd9RFrQeUvmvddFNoLwMyOUWU+5ckioEKpDoGA==", + "dev": true, + "requires": { + "@babel/template": "^7.1.2", + "@babel/traverse": "^7.1.5", + "@babel/types": "^7.3.0" } }, "@babel/highlight": { - "version": "7.0.0-beta.44", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0-beta.44.tgz", - "integrity": "sha512-Il19yJvy7vMFm8AVAh6OZzaFoAd0hbkeMZiX3P5HGD+z7dyI7RzndHB0dg6Urh/VAFfHtpOIzDUSxmY6coyZWQ==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", + "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", "dev": true, "requires": { "chalk": "^2.0.0", "esutils": "^2.0.2", - "js-tokens": "^3.0.0" + "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.2.0.tgz", - "integrity": "sha512-M74+GvK4hn1eejD9lZ7967qAwvqTZayQa3g10ag4s9uewgR7TKjeaT0YMyoq+gVfKYABiWZ4MQD701/t5e1Jhg==", + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.3.4.tgz", + "integrity": "sha512-tXZCqWtlOOP4wgCp6RjRvLmfuhnqTLy9VHwRochJBCP2nDm27JnnuFEnXFASVyQNHk36jD1tAammsCEEqgscIQ==", "dev": true }, "@babel/plugin-proposal-async-generator-functions": { @@ -1534,12 +365,12 @@ } }, "@babel/plugin-proposal-class-properties": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.2.1.tgz", - "integrity": "sha512-/4FKFChkQ2Jgb8lBDsvFX496YTi7UWTetVgS8oJUpX1e/DlaoeEK57At27ug8Hu2zI2g8bzkJ+8k9qrHZRPGPA==", + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.3.4.tgz", + "integrity": "sha512-lUf8D3HLs4yYlAo8zjuneLvfxN7qfKv1Yzbj5vjqaqMJxgJA3Ipwp4VUJ+OrOdz53Wbww6ahwB8UhB2HQyLotA==", "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.2.1", + "@babel/helper-create-class-features-plugin": "^7.3.4", "@babel/helper-plugin-utils": "^7.0.0" } }, @@ -1554,9 +385,9 @@ } }, "@babel/plugin-proposal-object-rest-spread": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.2.0.tgz", - "integrity": "sha512-1L5mWLSvR76XYUQJXkd/EEQgjq8HHRP6lQuZTTg0VA4tTGPpGemmCdAfQIz1rzEuWAm+ecP8PyyEm30jC1eQCg==", + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.3.4.tgz", + "integrity": "sha512-j7VQmbbkA+qrzNqbKHrBsW3ddFnOeva6wzSe/zB7T+xaxGc+RCpwo44wCmRixAIGRoIpmVgvzFzNJqQcO3/9RA==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", @@ -1621,9 +452,9 @@ } }, "@babel/plugin-syntax-typescript": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.0.0.tgz", - "integrity": "sha512-5fxmdqiAQVQTIS+KSvYeZuTt91wKtBTYi6JlIkvbQ6hmO+9fZE81ezxmMiFMIsxE7CdRSgzn7nQ1BChcvK9OpA==", + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.3.3.tgz", + "integrity": "sha512-dGwbSMA1YhVS8+31CnPR7LB4pcbrzcV99wQzby4uAfrkZPYZlQ7ImwdpzLqi6Z6IL02b8IAL379CaMwo0x5Lag==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" @@ -1639,9 +470,9 @@ } }, "@babel/plugin-transform-async-to-generator": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.2.0.tgz", - "integrity": "sha512-CEHzg4g5UraReozI9D4fblBYABs7IM6UerAVG7EJVrTLC5keh00aEuLUT+O40+mJCEzaXkYfTCUKIyeDfMOFFQ==", + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.3.4.tgz", + "integrity": "sha512-Y7nCzv2fw/jEZ9f678MuKdMo99MFDJMT/PvD9LisrR5JDFcJH6vYeH6RnjVt3p5tceyGRvTtEN0VOlU+rgHZjA==", "dev": true, "requires": { "@babel/helper-module-imports": "^7.0.0", @@ -1659,19 +490,19 @@ } }, "@babel/plugin-transform-block-scoping": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.2.0.tgz", - "integrity": "sha512-vDTgf19ZEV6mx35yiPJe4fS02mPQUUcBNwWQSZFXSzTSbsJFQvHt7DqyS3LK8oOWALFOsJ+8bbqBgkirZteD5Q==", + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.3.4.tgz", + "integrity": "sha512-blRr2O8IOZLAOJklXLV4WhcEzpYafYQKSGT3+R26lWG41u/FODJuBggehtOwilVAcFu393v3OFj+HmaE6tVjhA==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", - "lodash": "^4.17.10" + "lodash": "^4.17.11" } }, "@babel/plugin-transform-classes": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.2.0.tgz", - "integrity": "sha512-aPCEkrhJYebDXcGTAP+cdUENkH7zqOlgbKwLbghjjHpJRJBWM/FSlCjMoPGA8oUdiMfOrk3+8EFPLLb5r7zj2w==", + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.3.4.tgz", + "integrity": "sha512-J9fAvCFBkXEvBimgYxCjvaVDzL6thk0j0dBvCeZmIUDBwyt+nv6HfbImsSrWsYXfDNDivyANgJlFXDUWRTZBuA==", "dev": true, "requires": { "@babel/helper-annotate-as-pure": "^7.0.0", @@ -1679,88 +510,9 @@ "@babel/helper-function-name": "^7.1.0", "@babel/helper-optimise-call-expression": "^7.0.0", "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-replace-supers": "^7.1.0", + "@babel/helper-replace-supers": "^7.3.4", "@babel/helper-split-export-declaration": "^7.0.0", "globals": "^11.1.0" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", - "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", - "dev": true, - "requires": { - "@babel/highlight": "^7.0.0" - } - }, - "@babel/helper-function-name": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", - "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.0.0", - "@babel/template": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", - "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0.tgz", - "integrity": "sha512-MXkOJqva62dfC0w85mEf/LucPPS/1+04nmmRMPEBUB++hiiThQ2zPtX/mEWQ3mtzCEjIJvPY8nuwxXtQeQwUag==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@babel/highlight": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", - "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - } - }, - "@babel/template": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.1.2.tgz", - "integrity": "sha512-SY1MmplssORfFiLDcOETrW7fCLl+PavlwMh92rrGcikQaRq4iWPVH0MpwPpY3etVMx6RnDjXtr6VZYr/IbP/Ag==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.1.2", - "@babel/types": "^7.1.2" - } - }, - "@babel/types": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.2.0.tgz", - "integrity": "sha512-b4v7dyfApuKDvmPb+O488UlGuR1WbwMXFsO/cyqMrnfvRAChZKJAYeeglWTjUO1b9UghKKgepAQM5tsvBJca6A==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.10", - "to-fast-properties": "^2.0.0" - } - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - } } }, "@babel/plugin-transform-computed-properties": { @@ -1773,9 +525,9 @@ } }, "@babel/plugin-transform-destructuring": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.2.0.tgz", - "integrity": "sha512-coVO2Ayv7g0qdDbrNiadE4bU7lvCd9H539m2gMknyVjjMdwF/iCOM7R+E8PkntoqLkltO0rk+3axhpp/0v68VQ==", + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.3.2.tgz", + "integrity": "sha512-Lrj/u53Ufqxl/sGxyjsJ2XNtNuEjDyjpqdhMNh5aZ+XFOdThL46KBj27Uem4ggoezSYBxKWAil6Hu8HtwqesYw==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" @@ -1828,76 +580,6 @@ "requires": { "@babel/helper-function-name": "^7.1.0", "@babel/helper-plugin-utils": "^7.0.0" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", - "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", - "dev": true, - "requires": { - "@babel/highlight": "^7.0.0" - } - }, - "@babel/helper-function-name": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", - "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.0.0", - "@babel/template": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", - "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@babel/highlight": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", - "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - } - }, - "@babel/template": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.1.2.tgz", - "integrity": "sha512-SY1MmplssORfFiLDcOETrW7fCLl+PavlwMh92rrGcikQaRq4iWPVH0MpwPpY3etVMx6RnDjXtr6VZYr/IbP/Ag==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.1.2", - "@babel/types": "^7.1.2" - } - }, - "@babel/types": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.2.0.tgz", - "integrity": "sha512-b4v7dyfApuKDvmPb+O488UlGuR1WbwMXFsO/cyqMrnfvRAChZKJAYeeglWTjUO1b9UghKKgepAQM5tsvBJca6A==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.10", - "to-fast-properties": "^2.0.0" - } - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - } } }, "@babel/plugin-transform-literals": { @@ -1931,9 +613,9 @@ } }, "@babel/plugin-transform-modules-systemjs": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.2.0.tgz", - "integrity": "sha512-aYJwpAhoK9a+1+O625WIjvMY11wkB/ok0WClVwmeo3mCjcNRjt+/8gHWrB5i+00mUju0gWsBkQnPpdvQ7PImmQ==", + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.3.4.tgz", + "integrity": "sha512-VZ4+jlGOF36S7TjKs8g4ojp4MEI+ebCQZdswWb/T9I4X84j8OtFAyjXjt/M16iIm5RIZn0UMQgg/VgIwo/87vw==", "dev": true, "requires": { "@babel/helper-hoist-variables": "^7.0.0", @@ -1950,6 +632,15 @@ "@babel/helper-plugin-utils": "^7.0.0" } }, + "@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.3.0.tgz", + "integrity": "sha512-NxIoNVhk9ZxS+9lSoAQ/LM0V2UEvARLttEHUrRDGKFaAxOYQcrkN/nLRE+BbbicCAvZPl7wMP0X60HsHE5DtQw==", + "dev": true, + "requires": { + "regexp-tree": "^0.1.0" + } + }, "@babel/plugin-transform-new-target": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.0.0.tgz", @@ -1970,45 +661,23 @@ } }, "@babel/plugin-transform-parameters": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.2.0.tgz", - "integrity": "sha512-kB9+hhUidIgUoBQ0MsxMewhzr8i60nMa2KgeJKQWYrqQpqcBYtnpR+JgkadZVZoaEZ/eKu9mclFaVwhRpLNSzA==", + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.3.3.tgz", + "integrity": "sha512-IrIP25VvXWu/VlBWTpsjGptpomtIkYrN/3aDp4UKm7xK6UxZY88kcJ1UwETbzHAlwN21MnNfwlar0u8y3KpiXw==", "dev": true, "requires": { "@babel/helper-call-delegate": "^7.1.0", "@babel/helper-get-function-arity": "^7.0.0", "@babel/helper-plugin-utils": "^7.0.0" - }, - "dependencies": { - "@babel/helper-get-function-arity": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", - "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@babel/types": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.2.0.tgz", - "integrity": "sha512-b4v7dyfApuKDvmPb+O488UlGuR1WbwMXFsO/cyqMrnfvRAChZKJAYeeglWTjUO1b9UghKKgepAQM5tsvBJca6A==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.10", - "to-fast-properties": "^2.0.0" - } - } } }, "@babel/plugin-transform-regenerator": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.0.0.tgz", - "integrity": "sha512-sj2qzsEx8KDVv1QuJc/dEfilkg3RRPvPYx/VnKLtItVQRWt1Wqf5eVCOLZm29CiGFfYYsA3VPjfizTCV0S0Dlw==", + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.3.4.tgz", + "integrity": "sha512-hvJg8EReQvXT6G9H2MvNPXkv9zK36Vxa1+csAVTpE1J3j0zlHplw76uudEbJxgvqZzAq9Yh45FLD4pk5mKRFQA==", "dev": true, "requires": { - "regenerator-transform": "^0.13.3" + "regenerator-transform": "^0.13.4" } }, "@babel/plugin-transform-shorthand-properties": { @@ -2021,9 +690,9 @@ } }, "@babel/plugin-transform-spread": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.2.0.tgz", - "integrity": "sha512-7TtPIdwjS/i5ZBlNiQePQCovDh9pAhVbp/nGVRBZuUdBiVRThyyLend3OHobc0G+RLCPPAN70+z/MAMhsgJd/A==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.2.2.tgz", + "integrity": "sha512-KWfky/58vubwtS0hLqEnrWJjsMGaOeSBn90Ezn5Jeg9Z8KKHmELbP1yGylMlm5N6TPKeY9A2+UaSYLdxahg01w==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" @@ -2059,13 +728,13 @@ } }, "@babel/plugin-transform-typescript": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.1.0.tgz", - "integrity": "sha512-TOTtVeT+fekAesiCHnPz+PSkYSdOSLyLn42DI45nxg6iCdlQY6LIj/tYqpMB0y+YicoTUiYiXqF8rG6SKfhw6Q==", + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.3.2.tgz", + "integrity": "sha512-Pvco0x0ZSCnexJnshMfaibQ5hnK8aUHSvjCQhC1JR8eeg+iBwt0AtCO7gWxJ358zZevuf9wPSO5rv+WJcbHPXQ==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-syntax-typescript": "^7.0.0" + "@babel/plugin-syntax-typescript": "^7.2.0" } }, "@babel/plugin-transform-unicode-regex": { @@ -2079,27 +748,44 @@ "regexpu-core": "^4.1.3" } }, + "@babel/polyfill": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/@babel/polyfill/-/polyfill-7.2.5.tgz", + "integrity": "sha512-8Y/t3MWThtMLYr0YNC/Q76tqN1w30+b0uQMeFUYauG2UGTR19zyUtFrAzT23zNtBxPp+LbE5E/nwV/q/r3y6ug==", + "requires": { + "core-js": "^2.5.7", + "regenerator-runtime": "^0.12.0" + }, + "dependencies": { + "core-js": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.5.tgz", + "integrity": "sha512-klh/kDpwX8hryYL14M9w/xei6vrv6sE8gTHDG7/T/+SEovB/G4ejwcfE/CBzO6Edsu+OETZMZ3wcX/EjUkrl5A==" + } + } + }, "@babel/preset-env": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.2.0.tgz", - "integrity": "sha512-haGR38j5vOGVeBatrQPr3l0xHbs14505DcM57cbJy48kgMFvvHHoYEhHuRV+7vi559yyAUAVbTWzbK/B/pzJng==", + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.3.4.tgz", + "integrity": "sha512-2mwqfYMK8weA0g0uBKOt4FE3iEodiHy9/CW0b+nWXcbL+pGzLx8ESYc+j9IIxr6LTDHWKgPm71i9smo02bw+gA==", "dev": true, "requires": { "@babel/helper-module-imports": "^7.0.0", "@babel/helper-plugin-utils": "^7.0.0", "@babel/plugin-proposal-async-generator-functions": "^7.2.0", "@babel/plugin-proposal-json-strings": "^7.2.0", - "@babel/plugin-proposal-object-rest-spread": "^7.2.0", + "@babel/plugin-proposal-object-rest-spread": "^7.3.4", "@babel/plugin-proposal-optional-catch-binding": "^7.2.0", "@babel/plugin-proposal-unicode-property-regex": "^7.2.0", "@babel/plugin-syntax-async-generators": "^7.2.0", + "@babel/plugin-syntax-json-strings": "^7.2.0", "@babel/plugin-syntax-object-rest-spread": "^7.2.0", "@babel/plugin-syntax-optional-catch-binding": "^7.2.0", "@babel/plugin-transform-arrow-functions": "^7.2.0", - "@babel/plugin-transform-async-to-generator": "^7.2.0", + "@babel/plugin-transform-async-to-generator": "^7.3.4", "@babel/plugin-transform-block-scoped-functions": "^7.2.0", - "@babel/plugin-transform-block-scoping": "^7.2.0", - "@babel/plugin-transform-classes": "^7.2.0", + "@babel/plugin-transform-block-scoping": "^7.3.4", + "@babel/plugin-transform-classes": "^7.3.4", "@babel/plugin-transform-computed-properties": "^7.2.0", "@babel/plugin-transform-destructuring": "^7.2.0", "@babel/plugin-transform-dotall-regex": "^7.2.0", @@ -2110,12 +796,13 @@ "@babel/plugin-transform-literals": "^7.2.0", "@babel/plugin-transform-modules-amd": "^7.2.0", "@babel/plugin-transform-modules-commonjs": "^7.2.0", - "@babel/plugin-transform-modules-systemjs": "^7.2.0", + "@babel/plugin-transform-modules-systemjs": "^7.3.4", "@babel/plugin-transform-modules-umd": "^7.2.0", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.3.0", "@babel/plugin-transform-new-target": "^7.0.0", "@babel/plugin-transform-object-super": "^7.2.0", "@babel/plugin-transform-parameters": "^7.2.0", - "@babel/plugin-transform-regenerator": "^7.0.0", + "@babel/plugin-transform-regenerator": "^7.3.4", "@babel/plugin-transform-shorthand-properties": "^7.2.0", "@babel/plugin-transform-spread": "^7.2.0", "@babel/plugin-transform-sticky-regex": "^7.2.0", @@ -2129,69 +816,85 @@ } }, "@babel/preset-typescript": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.1.0.tgz", - "integrity": "sha512-LYveByuF9AOM8WrsNne5+N79k1YxjNB6gmpCQsnuSBAcV8QUeB+ZUxQzL7Rz7HksPbahymKkq2qBR+o36ggFZA==", + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.3.3.tgz", + "integrity": "sha512-mzMVuIP4lqtn4du2ynEfdO0+RYcslwrZiJHXu4MGaC1ctJiW2fyaeDrtjJGs7R/KebZ1sgowcIoWf4uRpEfKEg==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-transform-typescript": "^7.1.0" + "@babel/plugin-transform-typescript": "^7.3.2" } }, "@babel/runtime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.0.0.tgz", - "integrity": "sha512-7hGhzlcmg01CvH1EHdSPVXYX1aJ8KCEyz6I9xYIi/asDtzBPMyMhVibhM/K6g/5qnKBwjZtp10bNZIEFTRW1MA==", + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.3.4.tgz", + "integrity": "sha512-IvfvnMdSaLBateu0jfsYIpZTxAc2cKEXEMiezGGN75QcBcecDUKd3PgLAncT0oOgxKy8dd8hrJKj9MfzgfZd6g==", "requires": { "regenerator-runtime": "^0.12.0" - }, - "dependencies": { - "regenerator-runtime": { - "version": "0.12.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", - "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==" - } } }, "@babel/template": { - "version": "7.0.0-beta.44", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.0.0-beta.44.tgz", - "integrity": "sha512-w750Sloq0UNifLx1rUqwfbnC6uSUk0mfwwgGRfdLiaUzfAOiH0tHJE6ILQIUi3KYkjiCDTskoIsnfqZvWLBDng==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.2.2.tgz", + "integrity": "sha512-zRL0IMM02AUDwghf5LMSSDEz7sBCO2YnNmpg3uWTZj/v1rcG2BmQUvaGU8GhU8BvfMh1k2KIAYZ7Ji9KXPUg7g==", "dev": true, "requires": { - "@babel/code-frame": "7.0.0-beta.44", - "@babel/types": "7.0.0-beta.44", - "babylon": "7.0.0-beta.44", - "lodash": "^4.2.0" + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.2.2", + "@babel/types": "^7.2.2" } }, "@babel/traverse": { - "version": "7.0.0-beta.44", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.0.0-beta.44.tgz", - "integrity": "sha512-UHuDz8ukQkJCDASKHf+oDt3FVUzFd+QYfuBIsiNu/4+/ix6pP/C+uQZJ6K1oEfbCMv/IKWbgDEh7fcsnIE5AtA==", + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.3.4.tgz", + "integrity": "sha512-TvTHKp6471OYEcE/91uWmhR6PrrYywQntCHSaZ8CM8Vmp+pjAusal4nGB2WCCQd0rvI7nOMKn9GnbcvTUz3/ZQ==", "dev": true, "requires": { - "@babel/code-frame": "7.0.0-beta.44", - "@babel/generator": "7.0.0-beta.44", - "@babel/helper-function-name": "7.0.0-beta.44", - "@babel/helper-split-export-declaration": "7.0.0-beta.44", - "@babel/types": "7.0.0-beta.44", - "babylon": "7.0.0-beta.44", - "debug": "^3.1.0", + "@babel/code-frame": "^7.0.0", + "@babel/generator": "^7.3.4", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.0.0", + "@babel/parser": "^7.3.4", + "@babel/types": "^7.3.4", + "debug": "^4.1.0", "globals": "^11.1.0", - "invariant": "^2.2.0", - "lodash": "^4.2.0" + "lodash": "^4.17.11" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } } }, "@babel/types": { - "version": "7.0.0-beta.44", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.0.0-beta.44.tgz", - "integrity": "sha512-5eTV4WRmqbaFM3v9gHAIljEQJU4Ssc6fxL61JN+Oe2ga/BwyjzjamwkCVVAQjHGuAX8i0BWo42dshL8eO5KfLQ==", + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.3.4.tgz", + "integrity": "sha512-WEkp8MsLftM7O/ty580wAmZzN1nDmCACc5+jFzUt+GUFNNIi3LdRlueYz0YIlmJhlZx1QYDMZL5vdWCL0fNjFQ==", "dev": true, "requires": { "esutils": "^2.0.2", - "lodash": "^4.2.0", + "lodash": "^4.17.11", "to-fast-properties": "^2.0.0" + }, + "dependencies": { + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + } } }, "@f/animate": { @@ -2236,39 +939,60 @@ } }, "@material-ui/core": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-3.1.2.tgz", - "integrity": "sha512-tTRjlTVJY78GDKRHKSuxpoghrFyDAu9GrYCnaARHaZ2pZWiBHuviqUgAC8n8jWUXG3e6vfAXn9zZWzFedb4LwQ==", + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-3.9.2.tgz", + "integrity": "sha512-aukR3mSH3g115St2OnqoeMRtmxzxxx+Mch7pFKRV3Tz3URExBlZwOolimjxKZpG4LGec8HlhREawafLsDzjVWQ==", "requires": { - "@babel/runtime": "7.0.0", + "@babel/runtime": "^7.2.0", + "@material-ui/system": "^3.0.0-alpha.0", + "@material-ui/utils": "^3.0.0-alpha.2", "@types/jss": "^9.5.6", "@types/react-transition-group": "^2.0.8", "brcast": "^3.0.1", "classnames": "^2.2.5", "csstype": "^2.5.2", "debounce": "^1.1.0", - "deepmerge": "^2.0.1", + "deepmerge": "^3.0.0", "dom-helpers": "^3.2.1", - "hoist-non-react-statics": "^2.5.0", + "hoist-non-react-statics": "^3.2.1", "is-plain-object": "^2.0.4", - "jss": "^9.3.3", + "jss": "^9.8.7", "jss-camel-case": "^6.0.0", "jss-default-unit": "^8.0.2", "jss-global": "^3.0.0", "jss-nested": "^6.0.1", "jss-props-sort": "^6.0.0", "jss-vendor-prefixer": "^7.0.0", - "keycode": "^2.1.9", "normalize-scroll-left": "^0.1.2", "popper.js": "^1.14.1", "prop-types": "^15.6.0", "react-event-listener": "^0.6.2", - "react-jss": "^8.1.0", "react-transition-group": "^2.2.1", "recompose": "0.28.0 - 0.30.0", "warning": "^4.0.1" } }, + "@material-ui/system": { + "version": "3.0.0-alpha.2", + "resolved": "https://registry.npmjs.org/@material-ui/system/-/system-3.0.0-alpha.2.tgz", + "integrity": "sha512-odmxQ0peKpP7RQBQ8koly06YhsPzcoVib1vByVPBH4QhwqBXuYoqlCjt02846fYspAqkrWzjxnWUD311EBbxOA==", + "requires": { + "@babel/runtime": "^7.2.0", + "deepmerge": "^3.0.0", + "prop-types": "^15.6.0", + "warning": "^4.0.1" + } + }, + "@material-ui/utils": { + "version": "3.0.0-alpha.3", + "resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-3.0.0-alpha.3.tgz", + "integrity": "sha512-rwMdMZptX0DivkqBuC+Jdq7BYTXwqKai5G5ejPpuEDKpWzi1Oxp+LygGw329FrKpuKeiqpcymlqJTjmy+quWng==", + "requires": { + "@babel/runtime": "^7.2.0", + "prop-types": "^15.6.0", + "react-is": "^16.6.3" + } + }, "@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -2325,40 +1049,45 @@ }, "@types/accepts": { "version": "1.3.5", - "resolved": "http://registry.npmjs.org/@types/accepts/-/accepts-1.3.5.tgz", + "resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.5.tgz", "integrity": "sha512-jOdnI/3qTpHABjM5cx1Hc0sKsPoYCp+DP/GJRGtDlPd7fiV9oXGGIcjW/ZOxLIvjGz8MA+uMZI9metHlgqbgwQ==", "requires": { "@types/node": "*" } }, "@types/babel-types": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@types/babel-types/-/babel-types-7.0.1.tgz", - "integrity": "sha512-EkcOk09rjhivbovP8WreGRbXW20YRfe/qdgXOGq3it3u3aAOWDRNsQhL/XPAWFF7zhZZ+uR+nT+3b+TCkIap1w==" + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@types/babel-types/-/babel-types-7.0.6.tgz", + "integrity": "sha512-8zYZyy2kgwBXdz2j8Ix7LOghGiZbOiHf6vqmmBX1r76FdAzVNv7cODyJTEglUWiOdRnXh0s/o58neUwv5vaitQ==" }, "@types/babylon": { - "version": "6.16.2", - "resolved": "https://registry.npmjs.org/@types/babylon/-/babylon-6.16.2.tgz", - "integrity": "sha512-+Jty46mPaWe1VAyZbfvgJM4BAdklLWxrT5tc/RjvCgLrtk6gzRY6AOnoWFv4p6hVxhJshDdr2hGVn56alBp97Q==", + "version": "6.16.5", + "resolved": "https://registry.npmjs.org/@types/babylon/-/babylon-6.16.5.tgz", + "integrity": "sha512-xH2e58elpj1X4ynnKp9qSnWlsRTIs6n3tgLGNfwAGHwePw0mulHQllV34n0T25uYSu1k0hRKkWXF890B1yS47w==", "requires": { "@types/babel-types": "*" } }, "@types/bluebird": { - "version": "3.5.24", - "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.24.tgz", - "integrity": "sha512-YeQoDpq4Lm8ppSBqAnAeF/xy1cYp/dMTif2JFcvmAbETMRlvKHT2iLcWu+WyYiJO3b3Ivokwo7EQca/xfLVJmg==", - "dev": true + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.26.tgz", + "integrity": "sha512-aj2mrBLn5ky0GmAg6IPXrQjnN0iB/ulozuJ+oZdrHRAzRbXjGmu4UXsNCjFvPbSaaPZmniocdOzsM392qLOlmQ==" }, "@types/body-parser": { - "version": "1.16.8", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.16.8.tgz", - "integrity": "sha512-BdN2PXxOFnTXFcyONPW6t0fHjz2fvRZHVMFpaS0wYr+Y8fWEaNOs4V8LEu/fpzQlMx+ahdndgTaGTwPC+J/EeA==", + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.17.0.tgz", + "integrity": "sha512-a2+YeUjPkztKJu5aIF2yArYFQQp8d51wZ7DavSHjFuY1mqVgidGyzEQ41JIVNy82fXj8yPgy2vJmfIywgESW6w==", "requires": { - "@types/express": "*", + "@types/connect": "*", "@types/node": "*" } }, + "@types/chai": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.1.7.tgz", + "integrity": "sha512-2Y8uPt0/jwjhQ6EiluT0XCri1Dbplr0ZxfFXUz+ye13gaqE8u5gL5ppao1JrUYr9cIip5S6MvQzBS7Kke7U9VA==", + "dev": true + }, "@types/connect": { "version": "3.4.32", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.32.tgz", @@ -2394,14 +1123,14 @@ } }, "@types/events": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@types/events/-/events-1.2.0.tgz", - "integrity": "sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", + "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==" }, "@types/express": { - "version": "4.11.1", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.11.1.tgz", - "integrity": "sha512-ttWle8cnPA5rAelauSWeWJimtY2RsUf2aspYZs7xPHiWgOlPn6nnUfBMtrkcnjFJuIHJF4gNOdVvpLK2Zmvh6g==", + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.16.1.tgz", + "integrity": "sha512-V0clmJow23WeyblmACoxbHBu2JKlE5TiIme6Lem14FnPW9gsttyHtk6wq7njcdIWH1njAaFgR8gW09lgY98gQg==", "requires": { "@types/body-parser": "*", "@types/express-serve-static-core": "*", @@ -2409,33 +1138,33 @@ } }, "@types/express-serve-static-core": { - "version": "4.11.1", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.11.1.tgz", - "integrity": "sha512-EehCl3tpuqiM8RUb+0255M8PhhSwTtLfmO7zBBdv0ay/VTd/zmrqDfQdZFsa5z/PVMbH2yCMZPXsnrImpATyIw==", + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.16.1.tgz", + "integrity": "sha512-QgbIMRU1EVRry5cIu1ORCQP4flSYqLM1lS5LYyGWfKnFT3E58f0gKto7BR13clBFVrVZ0G0rbLZ1hUpSkgQQOA==", "requires": { - "@types/events": "*", - "@types/node": "*" + "@types/node": "*", + "@types/range-parser": "*" } }, "@types/graphql": { - "version": "0.13.4", - "resolved": "https://registry.npmjs.org/@types/graphql/-/graphql-0.13.4.tgz", - "integrity": "sha512-B4yel4ro2nTb3v0pYO8vO6SjgvFJSrwUY+IO6TUSLdOSB+gQFslylrhRCHxvXMIhxB71mv5PEE9dAX+24S8sew==", + "version": "14.0.7", + "resolved": "https://registry.npmjs.org/@types/graphql/-/graphql-14.0.7.tgz", + "integrity": "sha512-BoLDjdvLQsXPZLJux3lEZANwGr3Xag56Ngy0U3y8uoRSDdeLcn43H3oBcgZlnd++iOQElBpaRVDHPzEDekyvXQ==", "dev": true }, "@types/jss": { - "version": "9.5.6", - "resolved": "https://registry.npmjs.org/@types/jss/-/jss-9.5.6.tgz", - "integrity": "sha512-7TWmR5y1jYG4ka4wTZt65RR0kw4WgALFUWktQIWbLnDd6/z/0SQZ/4+UeH0rhdp+HEdIfmzPBH0VwE/4Z9Evzw==", + "version": "9.5.8", + "resolved": "https://registry.npmjs.org/@types/jss/-/jss-9.5.8.tgz", + "integrity": "sha512-bBbHvjhm42UKki+wZpR89j73ykSXg99/bhuKuYYePtpma3ZAnmeGnl0WxXiZhPGsIfzKwCUkpPC0jlrVMBfRxA==", "requires": { "csstype": "^2.0.0", "indefinite-observable": "^1.0.1" } }, "@types/knex": { - "version": "0.14.26", - "resolved": "https://registry.npmjs.org/@types/knex/-/knex-0.14.26.tgz", - "integrity": "sha512-BhtEH5PbB2kxuAwZ1w+KDNyL5NZXjTfoT6JtwnD3P/l3bqJJISCOAfPeH8O2aY66auscNIaCvIij/eONvlM4Cw==", + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/@types/knex/-/knex-0.15.2.tgz", + "integrity": "sha512-mw8OT8v+FK0SsgDdmio2XSkEM/yLD7ybFtiqW7I65EDTlr2aZtG+p9FhryErpNJDJ2FEXgQhe3JVBG0Gh7YbvQ==", "dev": true, "requires": { "@types/bluebird": "*", @@ -2444,7 +1173,7 @@ }, "@types/ldapjs": { "version": "1.0.3", - "resolved": "http://registry.npmjs.org/@types/ldapjs/-/ldapjs-1.0.3.tgz", + "resolved": "https://registry.npmjs.org/@types/ldapjs/-/ldapjs-1.0.3.tgz", "integrity": "sha512-FSj24s1WsFEfOy8taIKp2DokSZfFkjWYZb88AS5eDj3WTocZ+4DnHjhzrXEs048WQ5mfOLJXMOAnc0kSnHh5Lw==", "requires": { "@types/events": "*", @@ -2457,49 +1186,60 @@ "integrity": "sha512-1w52Nyx4Gq47uuu0EVcsHBxZFJgurQ+rTKS3qMHxR1GY2T8c2AJYd6vZoZ9q1rupaDjU0yT+Jc2XTyXkjeMA+Q==" }, "@types/mime": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.0.tgz", - "integrity": "sha512-A2TAGbTFdBw9azHbpVd+/FkdW2T6msN1uct1O9bH3vTerEHKZhTXJUQXy+hNq1B0RagfU8U+KBdqiZpxjhOUQA==" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.1.tgz", + "integrity": "sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw==" + }, + "@types/mocha": { + "version": "5.2.6", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.6.tgz", + "integrity": "sha512-1axi39YdtBI7z957vdqXI4Ac25e7YihYQtJa+Clnxg1zTJEaIRbndt71O3sP4GAMgiAm0pY26/b9BrY4MR/PMw==", + "dev": true }, "@types/node": { - "version": "10.12.14", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.14.tgz", - "integrity": "sha512-0rVcFRhM93kRGAU88ASCjX9Y3FWDCh+33G5Z5evpKOea4xcpLqDGwmo64+DjgaSezTN5j9KdnUzvxhOw7fNciQ==" + "version": "11.10.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-11.10.4.tgz", + "integrity": "sha512-wa09itaLE8L705aXd8F80jnFpxz3Y1/KRHfKsYL2bPc0XF+wEWu8sR9n5bmeu8Ba1N9z2GRNzm/YdHcghLkLKg==" }, "@types/passport": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/@types/passport/-/passport-0.4.7.tgz", - "integrity": "sha512-EePlxNYx5tf3n0yjdPXX0/zDOv0UCwjMyQo4UkWGlhHteNDItAj7TfDdLttSThVMKQz3uCW7lsGzMuml0f8g9Q==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.0.tgz", + "integrity": "sha512-R2FXqM+AgsMIym0PuKj08Ybx+GR6d2rU3b1/8OcHolJ+4ga2pRPX105wboV6hq1AJvMo2frQzYKdqXS5+4cyMw==", "requires": { "@types/express": "*" } }, "@types/prop-types": { - "version": "15.5.6", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.5.6.tgz", - "integrity": "sha512-ZBFR7TROLVzCkswA3Fmqq+IIJt62/T7aY/Dmz+QkU7CaW2QFqAitCE8Ups7IzmGhcN1YWMBT4Qcoc07jU9hOJQ==" + "version": "15.7.0", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.0.tgz", + "integrity": "sha512-eItQyV43bj4rR3JPV0Skpl1SncRCdziTEK9/v8VwXmV6d/qOUO8/EuWeHBbCZcsfSHfzI5UyMJLCSXtxxznyZg==" + }, + "@types/range-parser": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", + "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==" }, "@types/react": { - "version": "16.4.15", - "resolved": "https://registry.npmjs.org/@types/react/-/react-16.4.15.tgz", - "integrity": "sha512-EFQyVZhZCrRjwYDVziTEUGri/ygArIi/ES+JAI0j+3FR0LZ0uRfq2Ed7YnZ1CCn9CM3malSWwTKw5Jo0gVV82A==", + "version": "16.8.6", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.8.6.tgz", + "integrity": "sha512-bN9qDjEMltmHrl0PZRI4IF2AbB7V5UlRfG+OOduckVnRQ4VzXVSzy/1eLAh778IEqhTnW0mmgL9yShfinNverA==", "requires": { "@types/prop-types": "*", "csstype": "^2.2.0" } }, "@types/react-transition-group": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-2.0.14.tgz", - "integrity": "sha512-pa7qB0/mkhwWMBFoXhX8BcntK8G4eQl4sIfSrJCxnivTYRQWjOWf2ClR9bWdm0EUFBDHzMbKYS+QYfDtBzkY4w==", + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-2.0.16.tgz", + "integrity": "sha512-FUJEx2BGJPU1qVQoWd9v7wpOwnCPTWhcE4iTaU5prry9SvwiI11lCXOci8Nz9cM/Fuf650l7Skg6nlVeCYjPFA==", "requires": { "@types/react": "*" } }, "@types/serve-static": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.1.tgz", - "integrity": "sha512-jDMH+3BQPtvqZVIcsH700Dfi8Q3MIcEx16g/VdxjoqiGR/NntekB10xdBpirMKnPe9z2C5cBmL0vte0YttOr3Q==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.2.tgz", + "integrity": "sha512-/BZ4QRLpH/bNYgZgwhKEh+5AsboDBcUdlBYgzoLX0fpj3Y2gp6EApyOlM3bK53wQS/OE1SrdSYBAbux2D1528Q==", "requires": { "@types/express-serve-static-core": "*", "@types/mime": "*" @@ -2515,175 +1255,179 @@ } }, "@webassemblyjs/ast": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.7.11.tgz", - "integrity": "sha512-ZEzy4vjvTzScC+SH8RBssQUawpaInUdMTYwYYLh54/s8TuT0gBLuyUnppKsVyZEi876VmmStKsUs28UxPgdvrA==", + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.8.5.tgz", + "integrity": "sha512-aJMfngIZ65+t71C3y2nBBg5FFG0Okt9m0XEgWZ7Ywgn1oMAT8cNwx00Uv1cQyHtidq0Xn94R4TAywO+LCQ+ZAQ==", "dev": true, "requires": { - "@webassemblyjs/helper-module-context": "1.7.11", - "@webassemblyjs/helper-wasm-bytecode": "1.7.11", - "@webassemblyjs/wast-parser": "1.7.11" + "@webassemblyjs/helper-module-context": "1.8.5", + "@webassemblyjs/helper-wasm-bytecode": "1.8.5", + "@webassemblyjs/wast-parser": "1.8.5" } }, "@webassemblyjs/floating-point-hex-parser": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.7.11.tgz", - "integrity": "sha512-zY8dSNyYcgzNRNT666/zOoAyImshm3ycKdoLsyDw/Bwo6+/uktb7p4xyApuef1dwEBo/U/SYQzbGBvV+nru2Xg==", + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.8.5.tgz", + "integrity": "sha512-9p+79WHru1oqBh9ewP9zW95E3XAo+90oth7S5Re3eQnECGq59ly1Ri5tsIipKGpiStHsUYmY3zMLqtk3gTcOtQ==", "dev": true }, "@webassemblyjs/helper-api-error": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.7.11.tgz", - "integrity": "sha512-7r1qXLmiglC+wPNkGuXCvkmalyEstKVwcueZRP2GNC2PAvxbLYwLLPr14rcdJaE4UtHxQKfFkuDFuv91ipqvXg==", + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.8.5.tgz", + "integrity": "sha512-Za/tnzsvnqdaSPOUXHyKJ2XI7PDX64kWtURyGiJJZKVEdFOsdKUCPTNEVFZq3zJ2R0G5wc2PZ5gvdTRFgm81zA==", "dev": true }, "@webassemblyjs/helper-buffer": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.7.11.tgz", - "integrity": "sha512-MynuervdylPPh3ix+mKZloTcL06P8tenNH3sx6s0qE8SLR6DdwnfgA7Hc9NSYeob2jrW5Vql6GVlsQzKQCa13w==", + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.8.5.tgz", + "integrity": "sha512-Ri2R8nOS0U6G49Q86goFIPNgjyl6+oE1abW1pS84BuhP1Qcr5JqMwRFT3Ah3ADDDYGEgGs1iyb1DGX+kAi/c/Q==", "dev": true }, "@webassemblyjs/helper-code-frame": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.7.11.tgz", - "integrity": "sha512-T8ESC9KMXFTXA5urJcyor5cn6qWeZ4/zLPyWeEXZ03hj/x9weSokGNkVCdnhSabKGYWxElSdgJ+sFa9G/RdHNw==", + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.8.5.tgz", + "integrity": "sha512-VQAadSubZIhNpH46IR3yWO4kZZjMxN1opDrzePLdVKAZ+DFjkGD/rf4v1jap744uPVU6yjL/smZbRIIJTOUnKQ==", "dev": true, "requires": { - "@webassemblyjs/wast-printer": "1.7.11" + "@webassemblyjs/wast-printer": "1.8.5" } }, "@webassemblyjs/helper-fsm": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.7.11.tgz", - "integrity": "sha512-nsAQWNP1+8Z6tkzdYlXT0kxfa2Z1tRTARd8wYnc/e3Zv3VydVVnaeePgqUzFrpkGUyhUUxOl5ML7f1NuT+gC0A==", + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.8.5.tgz", + "integrity": "sha512-kRuX/saORcg8se/ft6Q2UbRpZwP4y7YrWsLXPbbmtepKr22i8Z4O3V5QE9DbZK908dh5Xya4Un57SDIKwB9eow==", "dev": true }, "@webassemblyjs/helper-module-context": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.7.11.tgz", - "integrity": "sha512-JxfD5DX8Ygq4PvXDucq0M+sbUFA7BJAv/GGl9ITovqE+idGX+J3QSzJYz+LwQmL7fC3Rs+utvWoJxDb6pmC0qg==", - "dev": true + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.8.5.tgz", + "integrity": "sha512-/O1B236mN7UNEU4t9X7Pj38i4VoU8CcMHyy3l2cV/kIF4U5KoHXDVqcDuOs1ltkac90IM4vZdHc52t1x8Yfs3g==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.8.5", + "mamacro": "^0.0.3" + } }, "@webassemblyjs/helper-wasm-bytecode": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.7.11.tgz", - "integrity": "sha512-cMXeVS9rhoXsI9LLL4tJxBgVD/KMOKXuFqYb5oCJ/opScWpkCMEz9EJtkonaNcnLv2R3K5jIeS4TRj/drde1JQ==", + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.8.5.tgz", + "integrity": "sha512-Cu4YMYG3Ddl72CbmpjU/wbP6SACcOPVbHN1dI4VJNJVgFwaKf1ppeFJrwydOG3NDHxVGuCfPlLZNyEdIYlQ6QQ==", "dev": true }, "@webassemblyjs/helper-wasm-section": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.7.11.tgz", - "integrity": "sha512-8ZRY5iZbZdtNFE5UFunB8mmBEAbSI3guwbrsCl4fWdfRiAcvqQpeqd5KHhSWLL5wuxo53zcaGZDBU64qgn4I4Q==", + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.8.5.tgz", + "integrity": "sha512-VV083zwR+VTrIWWtgIUpqfvVdK4ff38loRmrdDBgBT8ADXYsEZ5mPQ4Nde90N3UYatHdYoDIFb7oHzMncI02tA==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.7.11", - "@webassemblyjs/helper-buffer": "1.7.11", - "@webassemblyjs/helper-wasm-bytecode": "1.7.11", - "@webassemblyjs/wasm-gen": "1.7.11" + "@webassemblyjs/ast": "1.8.5", + "@webassemblyjs/helper-buffer": "1.8.5", + "@webassemblyjs/helper-wasm-bytecode": "1.8.5", + "@webassemblyjs/wasm-gen": "1.8.5" } }, "@webassemblyjs/ieee754": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.7.11.tgz", - "integrity": "sha512-Mmqx/cS68K1tSrvRLtaV/Lp3NZWzXtOHUW2IvDvl2sihAwJh4ACE0eL6A8FvMyDG9abes3saB6dMimLOs+HMoQ==", + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.8.5.tgz", + "integrity": "sha512-aaCvQYrvKbY/n6wKHb/ylAJr27GglahUO89CcGXMItrOBqRarUMxWLJgxm9PJNuKULwN5n1csT9bYoMeZOGF3g==", "dev": true, "requires": { "@xtuc/ieee754": "^1.2.0" } }, "@webassemblyjs/leb128": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.7.11.tgz", - "integrity": "sha512-vuGmgZjjp3zjcerQg+JA+tGOncOnJLWVkt8Aze5eWQLwTQGNgVLcyOTqgSCxWTR4J42ijHbBxnuRaL1Rv7XMdw==", + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.8.5.tgz", + "integrity": "sha512-plYUuUwleLIziknvlP8VpTgO4kqNaH57Y3JnNa6DLpu/sGcP6hbVdfdX5aHAV716pQBKrfuU26BJK29qY37J7A==", "dev": true, "requires": { - "@xtuc/long": "4.2.1" + "@xtuc/long": "4.2.2" } }, "@webassemblyjs/utf8": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.7.11.tgz", - "integrity": "sha512-C6GFkc7aErQIAH+BMrIdVSmW+6HSe20wg57HEC1uqJP8E/xpMjXqQUxkQw07MhNDSDcGpxI9G5JSNOQCqJk4sA==", + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.8.5.tgz", + "integrity": "sha512-U7zgftmQriw37tfD934UNInokz6yTmn29inT2cAetAsaU9YeVCveWEwhKL1Mg4yS7q//NGdzy79nlXh3bT8Kjw==", "dev": true }, "@webassemblyjs/wasm-edit": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.7.11.tgz", - "integrity": "sha512-FUd97guNGsCZQgeTPKdgxJhBXkUbMTY6hFPf2Y4OedXd48H97J+sOY2Ltaq6WGVpIH8o/TGOVNiVz/SbpEMJGg==", + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.8.5.tgz", + "integrity": "sha512-A41EMy8MWw5yvqj7MQzkDjU29K7UJq1VrX2vWLzfpRHt3ISftOXqrtojn7nlPsZ9Ijhp5NwuODuycSvfAO/26Q==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.7.11", - "@webassemblyjs/helper-buffer": "1.7.11", - "@webassemblyjs/helper-wasm-bytecode": "1.7.11", - "@webassemblyjs/helper-wasm-section": "1.7.11", - "@webassemblyjs/wasm-gen": "1.7.11", - "@webassemblyjs/wasm-opt": "1.7.11", - "@webassemblyjs/wasm-parser": "1.7.11", - "@webassemblyjs/wast-printer": "1.7.11" + "@webassemblyjs/ast": "1.8.5", + "@webassemblyjs/helper-buffer": "1.8.5", + "@webassemblyjs/helper-wasm-bytecode": "1.8.5", + "@webassemblyjs/helper-wasm-section": "1.8.5", + "@webassemblyjs/wasm-gen": "1.8.5", + "@webassemblyjs/wasm-opt": "1.8.5", + "@webassemblyjs/wasm-parser": "1.8.5", + "@webassemblyjs/wast-printer": "1.8.5" } }, "@webassemblyjs/wasm-gen": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.7.11.tgz", - "integrity": "sha512-U/KDYp7fgAZX5KPfq4NOupK/BmhDc5Kjy2GIqstMhvvdJRcER/kUsMThpWeRP8BMn4LXaKhSTggIJPOeYHwISA==", + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.8.5.tgz", + "integrity": "sha512-BCZBT0LURC0CXDzj5FXSc2FPTsxwp3nWcqXQdOZE4U7h7i8FqtFK5Egia6f9raQLpEKT1VL7zr4r3+QX6zArWg==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.7.11", - "@webassemblyjs/helper-wasm-bytecode": "1.7.11", - "@webassemblyjs/ieee754": "1.7.11", - "@webassemblyjs/leb128": "1.7.11", - "@webassemblyjs/utf8": "1.7.11" + "@webassemblyjs/ast": "1.8.5", + "@webassemblyjs/helper-wasm-bytecode": "1.8.5", + "@webassemblyjs/ieee754": "1.8.5", + "@webassemblyjs/leb128": "1.8.5", + "@webassemblyjs/utf8": "1.8.5" } }, "@webassemblyjs/wasm-opt": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.7.11.tgz", - "integrity": "sha512-XynkOwQyiRidh0GLua7SkeHvAPXQV/RxsUeERILmAInZegApOUAIJfRuPYe2F7RcjOC9tW3Cb9juPvAC/sCqvg==", + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.8.5.tgz", + "integrity": "sha512-HKo2mO/Uh9A6ojzu7cjslGaHaUU14LdLbGEKqTR7PBKwT6LdPtLLh9fPY33rmr5wcOMrsWDbbdCHq4hQUdd37Q==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.7.11", - "@webassemblyjs/helper-buffer": "1.7.11", - "@webassemblyjs/wasm-gen": "1.7.11", - "@webassemblyjs/wasm-parser": "1.7.11" + "@webassemblyjs/ast": "1.8.5", + "@webassemblyjs/helper-buffer": "1.8.5", + "@webassemblyjs/wasm-gen": "1.8.5", + "@webassemblyjs/wasm-parser": "1.8.5" } }, "@webassemblyjs/wasm-parser": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.7.11.tgz", - "integrity": "sha512-6lmXRTrrZjYD8Ng8xRyvyXQJYUQKYSXhJqXOBLw24rdiXsHAOlvw5PhesjdcaMadU/pyPQOJ5dHreMjBxwnQKg==", + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.8.5.tgz", + "integrity": "sha512-pi0SYE9T6tfcMkthwcgCpL0cM9nRYr6/6fjgDtL6q/ZqKHdMWvxitRi5JcZ7RI4SNJJYnYNaWy5UUrHQy998lw==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.7.11", - "@webassemblyjs/helper-api-error": "1.7.11", - "@webassemblyjs/helper-wasm-bytecode": "1.7.11", - "@webassemblyjs/ieee754": "1.7.11", - "@webassemblyjs/leb128": "1.7.11", - "@webassemblyjs/utf8": "1.7.11" + "@webassemblyjs/ast": "1.8.5", + "@webassemblyjs/helper-api-error": "1.8.5", + "@webassemblyjs/helper-wasm-bytecode": "1.8.5", + "@webassemblyjs/ieee754": "1.8.5", + "@webassemblyjs/leb128": "1.8.5", + "@webassemblyjs/utf8": "1.8.5" } }, "@webassemblyjs/wast-parser": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.7.11.tgz", - "integrity": "sha512-lEyVCg2np15tS+dm7+JJTNhNWq9yTZvi3qEhAIIOaofcYlUp0UR5/tVqOwa/gXYr3gjwSZqw+/lS9dscyLelbQ==", + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.8.5.tgz", + "integrity": "sha512-daXC1FyKWHF1i11obK086QRlsMsY4+tIOKgBqI1lxAnkp9xe9YMcgOxm9kLe+ttjs5aWV2KKE1TWJCN57/Btsg==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.7.11", - "@webassemblyjs/floating-point-hex-parser": "1.7.11", - "@webassemblyjs/helper-api-error": "1.7.11", - "@webassemblyjs/helper-code-frame": "1.7.11", - "@webassemblyjs/helper-fsm": "1.7.11", - "@xtuc/long": "4.2.1" + "@webassemblyjs/ast": "1.8.5", + "@webassemblyjs/floating-point-hex-parser": "1.8.5", + "@webassemblyjs/helper-api-error": "1.8.5", + "@webassemblyjs/helper-code-frame": "1.8.5", + "@webassemblyjs/helper-fsm": "1.8.5", + "@xtuc/long": "4.2.2" } }, "@webassemblyjs/wast-printer": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.7.11.tgz", - "integrity": "sha512-m5vkAsuJ32QpkdkDOUPGSltrg8Cuk3KBx4YrmAGQwCZPRdUHXxG4phIOuuycLemHFr74sWL9Wthqss4fzdzSwg==", + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.8.5.tgz", + "integrity": "sha512-w0U0pD4EhlnvRyeJzBqaVSJAo9w/ce7/WPogeXLzGkO6hzhr4GnQIZ4W4uUt5b9ooAaXPtnXlj0gzsXEOUNYMg==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.7.11", - "@webassemblyjs/wast-parser": "1.7.11", - "@xtuc/long": "4.2.1" + "@webassemblyjs/ast": "1.8.5", + "@webassemblyjs/wast-parser": "1.8.5", + "@xtuc/long": "4.2.2" } }, "@xtuc/ieee754": { @@ -2693,9 +1437,9 @@ "dev": true }, "@xtuc/long": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.1.tgz", - "integrity": "sha512-FZdkNBDqBRHKQ2MEbSC17xnPFOhZxeJ2YGSfr2BKf3sujG49Qe3bB+rGCwQfIaA7WHnGeGkSijX4FuBCdrzW/g==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "dev": true }, "abbrev": { @@ -2714,19 +1458,15 @@ } }, "acorn": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.5.3.tgz", - "integrity": "sha512-jd5MkIUlbbmb07nXH0DT3y7rDVtkzDi4XZOUVWAer8ajmF/DTSSbl5oNFyDOl/OXA33Bl79+ypHhl2pN20VeOQ==", - "dev": true + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=" }, "acorn-dynamic-import": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-3.0.0.tgz", - "integrity": "sha512-zVWV8Z8lislJoOKKqdNMOB+s6+XV5WERty8MnKBeFgwA+19XJjJHs2RP5dzM57FftIs+jQnRToLiWazKr6sSWg==", - "dev": true, - "requires": { - "acorn": "^5.0.0" - } + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-4.0.0.tgz", + "integrity": "sha512-d3OEjQV4ROpoflsnUA8HozoIR504TFxNivYEUi6uwz0IYhBkTDXGuWlNdMtybRt3nqVx/L6XqMt0FxkXuWKZhw==", + "dev": true }, "acorn-globals": { "version": "3.1.0", @@ -2744,42 +1484,31 @@ } }, "acorn-jsx": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", - "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", - "dev": true, - "requires": { - "acorn": "^3.0.4" - }, - "dependencies": { - "acorn": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", - "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", - "dev": true - } - } + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.1.tgz", + "integrity": "sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg==", + "dev": true }, "ajv": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.2.1.tgz", - "integrity": "sha1-KKarxJOiq+D7TIUHrK7bQ/pVBnE=", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", + "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", "requires": { - "fast-deep-equal": "^1.0.0", + "fast-deep-equal": "^2.0.1", "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" } }, "ajv-errors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", - "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", - "dev": true + "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==" }, "ajv-keywords": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.1.0.tgz", - "integrity": "sha1-rCsnk5xUPpXSwG5/f1wnvkqlQ74=" + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.0.tgz", + "integrity": "sha512-aUjdRFISbuFOl0EIZc+9e4FfZp0bDZgAdOOf30bJmw8VM9v84SHyVyxDfbWxpGYbdZD/9XoKxfHVNmxPkhwyGw==" }, "align-text": { "version": "0.1.4", @@ -2810,25 +1539,30 @@ "string-width": "^2.0.0" } }, + "ansi-colors": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", + "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==" + }, "ansi-escape-sequences": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/ansi-escape-sequences/-/ansi-escape-sequences-4.0.0.tgz", - "integrity": "sha512-v+0wW9Wezwsyb0uF4aBVCjmSqit3Ru7PZFziGF0o2KwTvN2zWfTi3BRLq9EkJFdg3eBbyERXGTntVpBxH1J68Q==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/ansi-escape-sequences/-/ansi-escape-sequences-4.0.1.tgz", + "integrity": "sha512-G3Aona26cXv8nWIwID6MP11WSishqnyOPQjYaVJ7CfY2Xgu5sHOXM39nQg6XtyfF9++oLV6l2uFGojBb4zglGA==", "dev": true, "requires": { "array-back": "^2.0.0" } }, "ansi-escapes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", - "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", "dev": true }, "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "dev": true }, "ansi-styles": { @@ -2847,23 +1581,34 @@ "requires": { "micromatch": "^3.1.4", "normalize-path": "^2.1.1" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } } }, "apollo-cache-control": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/apollo-cache-control/-/apollo-cache-control-0.4.0.tgz", - "integrity": "sha512-WuriaNQIugTE8gYwfBWWCbbQTSKul/cV4JMi5UgqNIUvjHvnKZQLKbt5uYWow6QQNMkLT9hey8QPYkWpogkeSA==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/apollo-cache-control/-/apollo-cache-control-0.5.2.tgz", + "integrity": "sha512-uehXDUrd3Qim+nzxqqN7XT1YTbNSyumW3/FY5BxbKZTI8d4oPG4eyVQKqaggooSjswKQnOoIQVes3+qg9tGAkw==", "requires": { "apollo-server-env": "2.2.0", - "graphql-extensions": "0.4.0" + "graphql-extensions": "0.5.4" }, "dependencies": { "graphql-extensions": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/graphql-extensions/-/graphql-extensions-0.4.0.tgz", - "integrity": "sha512-8TUgIIUVpXWOcqq9RdmTSHUrhc3a/s+saKv9cCl8TYWHK9vyJIdea7ZaSKHGDthZNcsN+C3LulZYRL3Ah8ukoA==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/graphql-extensions/-/graphql-extensions-0.5.4.tgz", + "integrity": "sha512-qLThJGVMqcItE7GDf/xX/E40m/aeqFheEKiR5bfra4q5eHxQKGjnIc20P9CVqjOn9I0FkEiU9ypOobfmIf7t6g==", "requires": { - "@apollographql/apollo-tools": "^0.2.6" + "@apollographql/apollo-tools": "^0.3.3" } } } @@ -2908,60 +1653,126 @@ "integrity": "sha512-SAtyEjmA7KiEoL2eAOAUM6M9arQJGWxJKK0S9x0WyPOosHS420RXoxPhn57u/8orRnK8Kxm0nHQQNTX203cP1Q==", "dev": true, "requires": { - "esutils": "^2.0.2", - "lodash": "^4.2.0", - "to-fast-properties": "^2.0.0" + "esutils": "^2.0.2", + "lodash": "^4.2.0", + "to-fast-properties": "^2.0.0" + } + }, + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + }, + "cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "dev": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + } + }, + "core-js": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.5.tgz", + "integrity": "sha512-klh/kDpwX8hryYL14M9w/xei6vrv6sE8gTHDG7/T/+SEovB/G4ejwcfE/CBzO6Edsu+OETZMZ3wcX/EjUkrl5A==", + "dev": true + }, + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", + "dev": true, + "requires": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" } }, - "cliui": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", - "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", "dev": true, "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0", - "wrap-ansi": "^2.0.0" + "locate-path": "^2.0.0" } }, + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", + "dev": true + }, "graphql": { "version": "0.13.2", - "resolved": "http://registry.npmjs.org/graphql/-/graphql-0.13.2.tgz", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-0.13.2.tgz", "integrity": "sha512-QZ5BL8ZO/B20VA8APauGBg3GyEgZ19eduvpLWoq5x7gMmWnHoy8rlQWPLmWgFvo1yNgjSEFMesmS4R6pPr7xog==", "dev": true, "requires": { "iterall": "^1.2.1" } }, - "graphql-config": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/graphql-config/-/graphql-config-1.2.1.tgz", - "integrity": "sha512-BOtbEOn/fD13jT0peCy3Fzp1DSTsA/1AcZp266AQ5Sk3wFndKCEa/H7donbu5UriOw1V/N1WDirYPnr7rd8E7Q==", + "invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", + "dev": true + }, + "lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", "dev": true, "requires": { - "graphql": "^0.12.3", - "graphql-import": "^0.4.0", - "graphql-request": "^1.4.0", - "js-yaml": "^3.10.0", - "lodash": "^4.17.4", - "minimatch": "^3.0.4" - }, - "dependencies": { - "graphql": { - "version": "0.12.3", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-0.12.3.tgz", - "integrity": "sha512-Hn9rdu4zacplKXNrLCvR8YFiTGnbM4Zw/UH8FDmzBDsH7ou40lSNH4tIlsxcYnz2TGNVJCpu1WxCM23yd6kzhA==", - "dev": true, - "requires": { - "iterall": "1.1.3" - } - }, - "iterall": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.1.3.tgz", - "integrity": "sha512-Cu/kb+4HiNSejAPhSaN1VukdNTTi/r4/e+yykqjlG/IW+1gZH5b4+Bq3whDX4tvbYugta3r8KTMUiqT3fIGxuQ==", - "dev": true - } + "invert-kv": "^1.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "mem": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", + "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" } }, "node-fetch": { @@ -2974,30 +1785,59 @@ "is-stream": "^1.0.1" } }, - "source-map-support": { - "version": "0.5.9", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.9.tgz", - "integrity": "sha512-gR6Rw4MvUlYy83vP0vxoVNzM6t8MUXqNuRsuBmBHQDu1Fh6X015FrLdgoDKcNdkwGubozq0P4N0Q37UyFVr1EA==", + "os-locale": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", + "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", + "dev": true, + "requires": { + "execa": "^0.7.0", + "lcid": "^1.0.0", + "mem": "^1.1.0" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", "dev": true, "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } + "p-try": "^1.0.0" } }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + }, "y18n": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", "dev": true }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + }, "yargs": { "version": "10.1.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-10.1.2.tgz", @@ -3030,161 +1870,93 @@ } }, "apollo-datasource": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/apollo-datasource/-/apollo-datasource-0.2.1.tgz", - "integrity": "sha512-r185+JTa5KuF1INeTAk7AEP76zwMN6c8Ph1lmpzJMNwBUEzTGnLClrccCskCBx4SxfnkdKbuQdwn9JwCJUWrdg==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/apollo-datasource/-/apollo-datasource-0.3.1.tgz", + "integrity": "sha512-qdEUeonc9pPZvYwXK36h2NZoT7Pddmy0HYOzdV0ON5pcG1YtNmUyyYi83Q60V5wTWjuaCjyJ9hOY6wr0BMvQuA==", "requires": { - "apollo-server-caching": "0.2.1", + "apollo-server-caching": "0.3.1", "apollo-server-env": "2.2.0" } }, "apollo-engine-reporting": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/apollo-engine-reporting/-/apollo-engine-reporting-0.2.0.tgz", - "integrity": "sha512-Q6FfVb10v/nrv8FaFsPjIYlWh62jaYav3LuMgM9PsHWGK/zRQFXOEwLxcY2UCvG7O1moxF3XGmfBhMgo54py+Q==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/apollo-engine-reporting/-/apollo-engine-reporting-1.0.7.tgz", + "integrity": "sha512-mFsXvd+1/o5jSa9tI2RoXYGcvCLcwwcfLwchjSTxqUd4ViB8RbqYKynzEZ+Omji7PBRM0azioBm43f7PSsQPqA==", "requires": { - "apollo-engine-reporting-protobuf": "0.2.0", + "apollo-engine-reporting-protobuf": "0.2.1", + "apollo-graphql": "^0.1.0", + "apollo-server-core": "2.4.8", "apollo-server-env": "2.2.0", "async-retry": "^1.2.1", - "graphql-extensions": "0.4.0", - "lodash": "^4.17.10" - }, - "dependencies": { - "graphql-extensions": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/graphql-extensions/-/graphql-extensions-0.4.0.tgz", - "integrity": "sha512-8TUgIIUVpXWOcqq9RdmTSHUrhc3a/s+saKv9cCl8TYWHK9vyJIdea7ZaSKHGDthZNcsN+C3LulZYRL3Ah8ukoA==", - "requires": { - "@apollographql/apollo-tools": "^0.2.6" - } - } + "graphql-extensions": "0.5.7" } }, "apollo-engine-reporting-protobuf": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/apollo-engine-reporting-protobuf/-/apollo-engine-reporting-protobuf-0.2.0.tgz", - "integrity": "sha512-qI+GJKN78UMJ9Aq/ORdiM2qymZ5yswem+/VDdVFocq+/e1QqxjnpKjQWISkswci5+WtpJl9SpHBNxG98uHDKkA==", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/apollo-engine-reporting-protobuf/-/apollo-engine-reporting-protobuf-0.2.1.tgz", + "integrity": "sha512-5pYR84uWeylRS2OJowtkTczT3bWTwOErWtfnkRKccUi/wZ/AZJBP+D5HKNzM7xoFcz9XvrJyS+wBTz1oBi0Jiw==", "requires": { "protobufjs": "^6.8.6" } }, "apollo-env": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/apollo-env/-/apollo-env-0.2.5.tgz", - "integrity": "sha512-Gc7TEbwCl7jJVutnn8TWfzNSkrrqyoo0DP92BQJFU9pZbJhpidoXf2Sw1YwOJl82rRKH3ujM3C8vdZLOgpFcFA==", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/apollo-env/-/apollo-env-0.3.3.tgz", + "integrity": "sha512-VsUX14bfQCJpKmTyYNBTeLrdeFabjmpSPVQ2y4IKnwqaxVqZuRca3WFE8ercszO1tLwS6HMM7mFw+IIbtQXo/w==", "requires": { - "core-js": "^3.0.0-beta.3", + "core-js": "3.0.0-beta.13", "node-fetch": "^2.2.0" - }, - "dependencies": { - "core-js": { - "version": "3.0.0-beta.5", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.0.0-beta.5.tgz", - "integrity": "sha512-lv/UPXe8QIvAX4XEgz3u9gpSbYr0Et6gaVhwMEH6SN9Uk+aIhk9IMwQUa35pymUiA4t2THPOaqysDJtX4jcm3w==" - }, - "node-fetch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.3.0.tgz", - "integrity": "sha512-MOd8pV3fxENbryESLgVIeaGKrdl+uaYhCSSVkjeOb/31/njTpcis5aWfdqgNlHIrKOLRbMnfPINPOML2CIFeXA==" - } + } + }, + "apollo-graphql": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/apollo-graphql/-/apollo-graphql-0.1.1.tgz", + "integrity": "sha512-UImgDIeB0n0fryYqtdz0CwJ9uDtXwg/3Q6rXzRAqgqBYz46VkmWa7nu2LX9GmDtiXB5VUOVCtyMEnvFwC3o27g==", + "requires": { + "lodash.sortby": "^4.7.0" } }, "apollo-link": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/apollo-link/-/apollo-link-1.2.2.tgz", - "integrity": "sha512-Uk/BC09dm61DZRDSu52nGq0nFhq7mcBPTjy5EEH1eunJndtCaNXQhQz/BjkI2NdrfGI+B+i5he6YSoRBhYizdw==", + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/apollo-link/-/apollo-link-1.2.8.tgz", + "integrity": "sha512-lfzGRxhK9RmiH3HPFi7TIEBhhDY9M5a2ZDnllcfy5QDk7cCQHQ1WQArcw1FK0g1B+mV4Kl72DSrlvZHZJEolrA==", "requires": { - "@types/graphql": "0.12.6", - "apollo-utilities": "^1.0.0", - "zen-observable-ts": "^0.8.9" - }, - "dependencies": { - "@types/graphql": { - "version": "0.12.6", - "resolved": "https://registry.npmjs.org/@types/graphql/-/graphql-0.12.6.tgz", - "integrity": "sha512-wXAVyLfkG1UMkKOdMijVWFky39+OD/41KftzqfX1Oejd0Gm6dOIKjCihSVECg6X7PHjftxXmfOKA/d1H79ZfvQ==" - } + "zen-observable-ts": "^0.8.15" } }, "apollo-server-caching": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/apollo-server-caching/-/apollo-server-caching-0.2.1.tgz", - "integrity": "sha512-+U9F3X297LL8Gqy6ypfDNEv/DfV/tDht9Dr2z3AMaEkNW1bwO6rmdDL01zYxDuVDVq6Z3qSiNCSO2pXE2F0zmA==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/apollo-server-caching/-/apollo-server-caching-0.3.1.tgz", + "integrity": "sha512-mfxzikYXbB/OoEms77AGYwRh7FF3Oim5v5XWAL+VL49FrkbZt5lopVa4bABi7Mz8Nt3Htl9EBJN8765s/yh8IA==", "requires": { "lru-cache": "^5.0.0" - }, - "dependencies": { - "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "requires": { - "yallist": "^3.0.2" - } - }, - "yallist": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", - "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==" - } } }, "apollo-server-core": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/apollo-server-core/-/apollo-server-core-2.3.1.tgz", - "integrity": "sha512-8jMWYOQIZi9mDJlHe2rXg8Cp4xKYogeRu23jkcNy+k5UjZL+eO+kHXbNFiTaP4HLYYEpe2XE3asxp6q5YUEQeQ==", + "version": "2.4.8", + "resolved": "https://registry.npmjs.org/apollo-server-core/-/apollo-server-core-2.4.8.tgz", + "integrity": "sha512-N+5UOzHhMOnHizEiArJtNvEe/cGhSHQyTn5tlU4RJ36FDBJ/WlYZfPbGDMLISSUCJ6t+aP8GLL4Mnudt9d2PDQ==", "requires": { - "@apollographql/apollo-tools": "^0.2.6", + "@apollographql/apollo-tools": "^0.3.3", "@apollographql/graphql-playground-html": "^1.6.6", "@types/ws": "^6.0.0", - "apollo-cache-control": "0.4.0", - "apollo-datasource": "0.2.1", - "apollo-engine-reporting": "0.2.0", - "apollo-server-caching": "0.2.1", + "apollo-cache-control": "0.5.2", + "apollo-datasource": "0.3.1", + "apollo-engine-reporting": "1.0.7", + "apollo-server-caching": "0.3.1", "apollo-server-env": "2.2.0", - "apollo-server-errors": "2.2.0", - "apollo-server-plugin-base": "0.2.1", - "apollo-tracing": "0.4.0", - "graphql-extensions": "0.4.1", + "apollo-server-errors": "2.2.1", + "apollo-server-plugin-base": "0.3.7", + "apollo-tracing": "0.5.2", + "fast-json-stable-stringify": "^2.0.0", + "graphql-extensions": "0.5.7", "graphql-subscriptions": "^1.0.0", "graphql-tag": "^2.9.2", "graphql-tools": "^4.0.0", "graphql-upload": "^8.0.2", - "json-stable-stringify": "^1.0.1", - "lodash": "^4.17.10", + "sha.js": "^2.4.11", "subscriptions-transport-ws": "^0.9.11", "ws": "^6.0.0" - }, - "dependencies": { - "apollo-link": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/apollo-link/-/apollo-link-1.2.4.tgz", - "integrity": "sha512-B1z+9H2nTyWEhMXRFSnoZ1vSuAYP+V/EdUJvRx9uZ8yuIBZMm6reyVtr1n0BWlKeSFyPieKJy2RLzmITAAQAMQ==", - "requires": { - "apollo-utilities": "^1.0.0", - "zen-observable-ts": "^0.8.11" - } - }, - "graphql-tools": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/graphql-tools/-/graphql-tools-4.0.3.tgz", - "integrity": "sha512-NNZM0WSnVLX1zIMUxu7SjzLZ4prCp15N5L2T2ro02OVyydZ0fuCnZYRnx/yK9xjGWbZA0Q58yEO//Bv/psJWrg==", - "requires": { - "apollo-link": "^1.2.3", - "apollo-utilities": "^1.0.1", - "deprecated-decorator": "^0.1.6", - "iterall": "^1.1.3", - "uuid": "^3.1.0" - } - }, - "zen-observable-ts": { - "version": "0.8.11", - "resolved": "https://registry.npmjs.org/zen-observable-ts/-/zen-observable-ts-0.8.11.tgz", - "integrity": "sha512-8bs7rgGV4kz5iTb9isudkuQjtWwPnQ8lXq6/T76vrepYZVMsDEv6BXaEA+DHdJSK3KVLduagi9jSpSAJ5NgKHw==", - "requires": { - "zen-observable": "^0.8.0" - } - } } }, "apollo-server-env": { @@ -3194,119 +1966,73 @@ "requires": { "node-fetch": "^2.1.2", "util.promisify": "^1.0.0" - }, - "dependencies": { - "node-fetch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.3.0.tgz", - "integrity": "sha512-MOd8pV3fxENbryESLgVIeaGKrdl+uaYhCSSVkjeOb/31/njTpcis5aWfdqgNlHIrKOLRbMnfPINPOML2CIFeXA==" - } } }, "apollo-server-errors": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/apollo-server-errors/-/apollo-server-errors-2.2.0.tgz", - "integrity": "sha512-gV9EZG2tovFtT1cLuCTavnJu2DaKxnXPRNGSTo+SDI6IAk6cdzyW0Gje5N2+3LybI0Wq5KAbW6VLei31S4MWmg==" + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/apollo-server-errors/-/apollo-server-errors-2.2.1.tgz", + "integrity": "sha512-wY/YE3iJVMYC+WYIf8QODBjIP4jhI+oc7kiYo9mrz7LdYPKAgxr/he+NteGcqn/0Ea9K5/ZFTGJDbEstSMeP8g==" }, "apollo-server-express": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/apollo-server-express/-/apollo-server-express-2.3.1.tgz", - "integrity": "sha512-J+rObr4GdT/5j6qTByUJoSvZSjTAX/7VqIkr2t+GxwcVUFGet2MdOHuV6rtWKc8CRgvVKfKN6iBrb2EOFcp2LQ==", + "version": "2.4.8", + "resolved": "https://registry.npmjs.org/apollo-server-express/-/apollo-server-express-2.4.8.tgz", + "integrity": "sha512-i60l32mfVe33jnKDPNYgUKUKu4Al0xEm2HLOSMgtJ9Wbpe/MbOx5X8M5F27fnHYdM+G5XfAErsakAyRGnQJ48Q==", "requires": { "@apollographql/graphql-playground-html": "^1.6.6", "@types/accepts": "^1.3.5", "@types/body-parser": "1.17.0", "@types/cors": "^2.8.4", - "@types/express": "4.16.0", + "@types/express": "4.16.1", "accepts": "^1.3.5", - "apollo-server-core": "2.3.1", + "apollo-server-core": "2.4.8", "body-parser": "^1.18.3", "cors": "^2.8.4", "graphql-subscriptions": "^1.0.0", "graphql-tools": "^4.0.0", "type-is": "^1.6.16" - }, - "dependencies": { - "@types/body-parser": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.17.0.tgz", - "integrity": "sha512-a2+YeUjPkztKJu5aIF2yArYFQQp8d51wZ7DavSHjFuY1mqVgidGyzEQ41JIVNy82fXj8yPgy2vJmfIywgESW6w==", - "requires": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "@types/express": { - "version": "4.16.0", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.16.0.tgz", - "integrity": "sha512-TtPEYumsmSTtTetAPXlJVf3kEqb6wZK0bZojpJQrnD/djV4q1oB6QQ8aKvKqwNPACoe02GNiy5zDzcYivR5Z2w==", - "requires": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "*", - "@types/serve-static": "*" - } - }, - "apollo-link": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/apollo-link/-/apollo-link-1.2.4.tgz", - "integrity": "sha512-B1z+9H2nTyWEhMXRFSnoZ1vSuAYP+V/EdUJvRx9uZ8yuIBZMm6reyVtr1n0BWlKeSFyPieKJy2RLzmITAAQAMQ==", - "requires": { - "apollo-utilities": "^1.0.0", - "zen-observable-ts": "^0.8.11" - } - }, - "graphql-tools": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/graphql-tools/-/graphql-tools-4.0.3.tgz", - "integrity": "sha512-NNZM0WSnVLX1zIMUxu7SjzLZ4prCp15N5L2T2ro02OVyydZ0fuCnZYRnx/yK9xjGWbZA0Q58yEO//Bv/psJWrg==", - "requires": { - "apollo-link": "^1.2.3", - "apollo-utilities": "^1.0.1", - "deprecated-decorator": "^0.1.6", - "iterall": "^1.1.3", - "uuid": "^3.1.0" - } - }, - "zen-observable-ts": { - "version": "0.8.11", - "resolved": "https://registry.npmjs.org/zen-observable-ts/-/zen-observable-ts-0.8.11.tgz", - "integrity": "sha512-8bs7rgGV4kz5iTb9isudkuQjtWwPnQ8lXq6/T76vrepYZVMsDEv6BXaEA+DHdJSK3KVLduagi9jSpSAJ5NgKHw==", - "requires": { - "zen-observable": "^0.8.0" - } - } } }, "apollo-server-plugin-base": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/apollo-server-plugin-base/-/apollo-server-plugin-base-0.2.1.tgz", - "integrity": "sha512-497NIY9VWRYCrMSkgR11IrIUO4Fsy6aGgnpOJoTdLQAnkDD9SJDSRzwKj4gypUoTT2unfKDng4eMxXVZlHvjOw==" + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/apollo-server-plugin-base/-/apollo-server-plugin-base-0.3.7.tgz", + "integrity": "sha512-hW1jaLKf9qNOxMTwRq2CSqz3eqXsZuEiCc8/mmEtOciiVBq1GMtxFf19oIYM9HQuPvQU2RWpns1VrYN59L3vbg==" + }, + "apollo-server-testing": { + "version": "2.4.8", + "resolved": "https://registry.npmjs.org/apollo-server-testing/-/apollo-server-testing-2.4.8.tgz", + "integrity": "sha512-AmNn5pDn9FJ9AJbmc7gwsUFaUt4uf44IFHaCfZow/jkAeY2JZnIozt8LYC8Koidy+Lfb+i/HsjkgbBodElbGMQ==", + "dev": true, + "requires": { + "apollo-server-core": "2.4.8" + } }, "apollo-tracing": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/apollo-tracing/-/apollo-tracing-0.4.0.tgz", - "integrity": "sha512-BlM8iQUQva4fm0xD/pLwkcz0degfB9a/aAn4k4cK36eLVD8XUkl7ptEB0c+cwcj7tOYpV1r5QX1XwdayBzlHSg==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/apollo-tracing/-/apollo-tracing-0.5.2.tgz", + "integrity": "sha512-2FdwRvPIq9uuF6OzONroXep6VBGqzHOkP6LlcFQe7SdwxfRP+SD/ycHNSC1acVg2b8d+am9Kzqg2vV54UpOIKA==", "requires": { "apollo-server-env": "2.2.0", - "graphql-extensions": "0.4.0" + "graphql-extensions": "0.5.4" }, "dependencies": { "graphql-extensions": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/graphql-extensions/-/graphql-extensions-0.4.0.tgz", - "integrity": "sha512-8TUgIIUVpXWOcqq9RdmTSHUrhc3a/s+saKv9cCl8TYWHK9vyJIdea7ZaSKHGDthZNcsN+C3LulZYRL3Ah8ukoA==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/graphql-extensions/-/graphql-extensions-0.5.4.tgz", + "integrity": "sha512-qLThJGVMqcItE7GDf/xX/E40m/aeqFheEKiR5bfra4q5eHxQKGjnIc20P9CVqjOn9I0FkEiU9ypOobfmIf7t6g==", "requires": { - "@apollographql/apollo-tools": "^0.2.6" + "@apollographql/apollo-tools": "^0.3.3" } } } }, "apollo-utilities": { - "version": "1.0.20", - "resolved": "https://registry.npmjs.org/apollo-utilities/-/apollo-utilities-1.0.20.tgz", - "integrity": "sha512-2M4BJCyX/9UXGJFoV4sTnVTZ4Q29aM18Z1avDrwvlCGGwoRTz50sGBAfTiWnUnnNQyPIIJEYElScw46DgIu0Rg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/apollo-utilities/-/apollo-utilities-1.2.1.tgz", + "integrity": "sha512-Zv8Udp9XTSFiN8oyXOjf6PMHepD4yxxReLsl6dPUy5Ths7jti3nmlBzZUOxuTWRwZn0MoclqL7RQ5UEJN8MAxg==", "requires": { - "fast-json-stable-stringify": "^2.0.0" + "fast-json-stable-stringify": "^2.0.0", + "ts-invariant": "^0.2.1", + "tslib": "^1.9.3" } }, "aproba": { @@ -3314,6 +2040,12 @@ "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" }, + "arg": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.0.tgz", + "integrity": "sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg==", + "dev": true + }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -3390,11 +2122,6 @@ "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" }, - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=" - }, "asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", @@ -3433,7 +2160,7 @@ }, "util": { "version": "0.10.3", - "resolved": "http://registry.npmjs.org/util/-/util-0.10.3.tgz", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", "dev": true, "requires": { @@ -3447,11 +2174,23 @@ "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, "assign-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" }, + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true + }, "async": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", @@ -3482,9 +2221,9 @@ "integrity": "sha512-aaZsHXP6vMebFl2hsR7zJmhnRyeGkLFcPKBP0FuOerdIgervBjPlEV7H6cQGtu6fV0l6O/7KO+nPrlfCLWOTUQ==" }, "atob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.1.tgz", - "integrity": "sha1-ri1acpR38onWDdf5amMUoi3Wwio=" + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" }, "babel-code-frame": { "version": "6.26.0", @@ -3497,6 +2236,12 @@ "js-tokens": "^3.0.2" }, "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, "ansi-styles": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", @@ -3516,6 +2261,12 @@ "supports-color": "^2.0.0" } }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + }, "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", @@ -3534,15 +2285,15 @@ } }, "babel-eslint": { - "version": "8.2.6", - "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-8.2.6.tgz", - "integrity": "sha512-aCdHjhzcILdP8c9lej7hvXKvQieyRt20SF102SIGyY4cUIiw6UaAtK4j2o3dXX74jEmy0TJ0CEhv4fTIM3SzcA==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.0.1.tgz", + "integrity": "sha512-z7OT1iNV+TjOwHNLLyJk+HN+YVWX+CLE6fPD2SymJZOZQBs+QIexFjhm4keGTm8MW9xr4EC9Q0PbaLB24V5GoQ==", "dev": true, "requires": { - "@babel/code-frame": "7.0.0-beta.44", - "@babel/traverse": "7.0.0-beta.44", - "@babel/types": "7.0.0-beta.44", - "babylon": "7.0.0-beta.44", + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.0.0", + "@babel/traverse": "^7.0.0", + "@babel/types": "^7.0.0", "eslint-scope": "3.7.1", "eslint-visitor-keys": "^1.0.0" } @@ -3554,6 +2305,18 @@ "requires": { "core-js": "^2.4.0", "regenerator-runtime": "^0.11.0" + }, + "dependencies": { + "core-js": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.5.tgz", + "integrity": "sha512-klh/kDpwX8hryYL14M9w/xei6vrv6sE8gTHDG7/T/+SEovB/G4ejwcfE/CBzO6Edsu+OETZMZ3wcX/EjUkrl5A==" + }, + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + } } }, "babel-types": { @@ -3565,20 +2328,12 @@ "esutils": "^2.0.2", "lodash": "^4.17.4", "to-fast-properties": "^1.0.3" - }, - "dependencies": { - "to-fast-properties": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", - "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=" - } } }, "babylon": { - "version": "7.0.0-beta.44", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.44.tgz", - "integrity": "sha512-5Hlm13BJVAioCHpImtFqNOF2H3ieTOHd0fmFGMxOJ9jgeFqeAwsv3u5P5cR7CSeFrkgHsT19DgFJkHV0/Mcd8g==", - "dev": true + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==" }, "backo2": { "version": "1.0.2", @@ -3660,13 +2415,6 @@ "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", "requires": { "safe-buffer": "5.1.2" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - } } }, "bcryptjs": { @@ -3675,20 +2423,20 @@ "integrity": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms=" }, "big.js": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz", - "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==" + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==" }, "binary-extensions": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.12.0.tgz", - "integrity": "sha512-DYWGk01lDcxeS/K9IHPGWfT8PsJmbXRtRd2Sx72Tnb8pcYZQFF1oSDb8hJtS1vhp212q1Rzi5dUf9+nq0o9UIg==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.0.tgz", + "integrity": "sha512-EgmjVLMn22z7eGGv3kcnHwSnJXmFHjISTY9E/S5lIcTD3Oxw05QTcBLNkJFzcb3cNueUdF/IN4U+d78V0zO8Hw==", "dev": true }, "bluebird": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", - "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.3.tgz", + "integrity": "sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw==" }, "bn.js": { "version": "4.11.8", @@ -3713,14 +2461,6 @@ "type-is": "~1.6.16" }, "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, "http-errors": { "version": "1.6.3", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", @@ -3732,30 +2472,6 @@ "statuses": ">= 1.4.0 < 2" } }, - "iconv-lite": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", - "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" - }, - "raw-body": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", - "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", - "requires": { - "bytes": "3.0.0", - "http-errors": "1.6.3", - "iconv-lite": "0.4.23", - "unpipe": "1.0.0" - } - }, "setprototypeof": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", @@ -3776,6 +2492,14 @@ "string-width": "^2.0.0", "term-size": "^1.2.0", "widest-line": "^2.0.0" + }, + "dependencies": { + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + } } }, "brace-expansion": { @@ -3825,9 +2549,15 @@ "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", "dev": true }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, "browserify-aes": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", "dev": true, "requires": { @@ -3858,21 +2588,13 @@ "requires": { "cipher-base": "^1.0.1", "des.js": "^1.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - } + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" } }, "browserify-rsa": { "version": "4.0.1", - "resolved": "http://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", "dev": true, "requires": { @@ -3905,19 +2627,19 @@ } }, "browserslist": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.3.5.tgz", - "integrity": "sha512-z9ZhGc3d9e/sJ9dIx5NFXkKoaiQTnrvrMsN3R1fGb1tkWWNSz12UewJn9TNxGo1l7J23h0MRaPmk7jfeTZYs1w==", + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.4.2.tgz", + "integrity": "sha512-ISS/AIAiHERJ3d45Fz0AVYKkgcy+F/eJHzKEvv1j0wwKGKD9T3BrwKr/5g45L+Y4XIK5PlTqefHciRFcfE1Jxg==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30000912", - "electron-to-chromium": "^1.3.86", - "node-releases": "^1.0.5" + "caniuse-lite": "^1.0.30000939", + "electron-to-chromium": "^1.3.113", + "node-releases": "^1.1.8" } }, "buffer": { "version": "4.9.1", - "resolved": "http://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", "dev": true, "requires": { @@ -3934,8 +2656,7 @@ "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" }, "buffer-writer": { "version": "2.0.0", @@ -3972,35 +2693,11 @@ } }, "busboy": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz", - "integrity": "sha1-bCpiLvz0fFe7vh4qnDetNseSVFM=", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.3.0.tgz", + "integrity": "sha512-e+kzZRAbbvJPLjQz2z+zAyr78BSi9IFeBTyLwF76g78Q2zRt/RZ1NtS3MS17v2yLqYfLz69zHdC+1L4ja8PwqQ==", "requires": { - "dicer": "0.2.5", - "readable-stream": "1.1.x" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - }, - "readable-stream": { - "version": "1.1.14", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" - } + "dicer": "0.3.0" } }, "bytes": { @@ -4009,22 +2706,23 @@ "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" }, "cacache": { - "version": "10.0.4", - "resolved": "http://registry.npmjs.org/cacache/-/cacache-10.0.4.tgz", - "integrity": "sha512-Dph0MzuH+rTQzGPNT9fAnrPmMmjKfST6trxJeK7NQuHRaVw24VzPRWTmg9MpcwOVQZO0E1FBICUlFeNaKPIfHA==", - "requires": { - "bluebird": "^3.5.1", - "chownr": "^1.0.1", - "glob": "^7.1.2", - "graceful-fs": "^4.1.11", - "lru-cache": "^4.1.1", - "mississippi": "^2.0.0", + "version": "11.3.2", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-11.3.2.tgz", + "integrity": "sha512-E0zP4EPGDOaT2chM08Als91eYnf8Z+eH1awwwVsngUmgppfM5jjJ8l3z5vO5p5w/I3LsiXawb1sW0VY65pQABg==", + "requires": { + "bluebird": "^3.5.3", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.3", + "graceful-fs": "^4.1.15", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", "mkdirp": "^0.5.1", "move-concurrently": "^1.0.1", "promise-inflight": "^1.0.1", "rimraf": "^2.6.2", - "ssri": "^5.2.4", - "unique-filename": "^1.1.0", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", "y18n": "^4.0.0" } }, @@ -4055,19 +2753,10 @@ "mkdirp2": "^1.0.3" } }, - "caller-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", - "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", - "dev": true, - "requires": { - "callsites": "^0.2.0" - } - }, "callsites": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", - "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.0.0.tgz", + "integrity": "sha512-tWnkwu9YEq2uzlBDI4RcLn8jrFvF9AOi8PxDNU3hZZjJcjkcRAq3vCI+vZcg1SuxISDYe86k9VZFwAxDiJGoAw==", "dev": true }, "camel-case": { @@ -4081,15 +2770,14 @@ } }, "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", - "dev": true + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=" }, "caniuse-lite": { - "version": "1.0.30000918", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000918.tgz", - "integrity": "sha512-CAZ9QXGViBvhHnmIHhsTPSWFBujDaelKnUj7wwImbyQRxmXynYqKGi3UaZTSz9MoVh+1EVxOS/DFIkrJYgR3aw==", + "version": "1.0.30000939", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000939.tgz", + "integrity": "sha512-oXB23ImDJOgQpGjRv1tCtzAvJr4/OvrHi5SO2vUgB0g0xpdZZoA/BxfImiWfdwoYdUTtQrPsXsvYU/dmCSM8gg==", "dev": true }, "capture-stack-trace": { @@ -4116,11 +2804,24 @@ "lazy-cache": "^1.0.3" } }, + "chai": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", + "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", + "dev": true, + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "pathval": "^1.1.0", + "type-detect": "^4.0.5" + } + }, "chalk": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", - "dev": true, "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -4128,9 +2829,9 @@ } }, "change-case": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/change-case/-/change-case-3.0.2.tgz", - "integrity": "sha512-Mww+SLF6MZ0U6kdg11algyKd5BARbyM4TbFBepwowYSR5ClfQGCGtxNXgykpN0uF/bstWeaGDT4JWaDh8zWAHA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/change-case/-/change-case-3.1.0.tgz", + "integrity": "sha512-2AZp7uJZbYEzRPsFoa+ijKdvp9zsrnnt6+yFokfwEpeJm0xuJDVoxiRCAaTzyJND8GJkofo2IcKWaUZ/OECVzw==", "dev": true, "requires": { "camel-case": "^3.0.0", @@ -4167,30 +2868,35 @@ } }, "chardet": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", - "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", "dev": true }, "chokidar": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", - "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.2.tgz", + "integrity": "sha512-IwXUx0FXc5ibYmPC2XeEj5mpXoV66sR+t3jqu2NS2GYwCktt3KF1/Qqjws/NkegajBA4RbZ5+DDwlOiJsxDHEg==", "dev": true, "requires": { "anymatch": "^2.0.0", - "async-each": "^1.0.0", - "braces": "^2.3.0", - "fsevents": "^1.2.2", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", "glob-parent": "^3.1.0", - "inherits": "^2.0.1", + "inherits": "^2.0.3", "is-binary-path": "^1.0.0", "is-glob": "^4.0.0", - "lodash.debounce": "^4.0.8", - "normalize-path": "^2.1.1", + "normalize-path": "^3.0.0", "path-is-absolute": "^1.0.0", - "readdirp": "^2.0.0", - "upath": "^1.0.5" + "readdirp": "^2.2.1", + "upath": "^1.1.0" } }, "chownr": { @@ -4223,12 +2929,6 @@ "safe-buffer": "^5.0.1" } }, - "circular-json": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", - "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", - "dev": true - }, "class-utils": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", @@ -4256,11 +2956,18 @@ "integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==" }, "clean-css": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.1.11.tgz", - "integrity": "sha1-Ls3xRaujj1R0DybO/Q/z4D4SXWo=", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz", + "integrity": "sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g==", "requires": { - "source-map": "0.5.x" + "source-map": "~0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } } }, "cli-boxes": { @@ -4285,9 +2992,9 @@ "dev": true }, "clipboard": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.1.tgz", - "integrity": "sha512-7yhQBmtN+uYZmfRjjVjKa0dZdWuabzpSKGtyQZN+9C8xlC788SSJjOHWh7tzurfwTqTD5UDYAhIv5fRJg3sHjQ==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.4.tgz", + "integrity": "sha512-Vw26VSLRpJfBofiVaFb/I8PVfdI1OxKcYShe6fm0sP/DtmiWQNCjhM/okTvdCo0G+lMMm1rMYbk4IK4x1X+kgQ==", "requires": { "good-listener": "^1.2.2", "select": "^1.1.2", @@ -4302,20 +3009,8 @@ "center-align": "^0.1.1", "right-align": "^0.1.1", "wordwrap": "0.0.2" - }, - "dependencies": { - "wordwrap": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", - "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=" - } } }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" - }, "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", @@ -4342,11 +3037,11 @@ } }, "color-convert": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", - "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "requires": { - "color-name": "^1.1.1" + "color-name": "1.1.3" } }, "color-name": { @@ -4398,9 +3093,9 @@ } }, "commander": { - "version": "2.17.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", - "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==" + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", + "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==" }, "common-sequence": { "version": "1.0.2", @@ -4448,10 +3143,11 @@ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "concat-stream": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.1.tgz", - "integrity": "sha512-gslSSJx03QKa59cIKqeJO9HQ/WZMotvYJCuaUULrLpjj8oG40kV2Z+gz82pVxlTkOADi4PJxQPPfhl1ELYrrXw==", + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", "requires": { + "buffer-from": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^2.2.2", "typedarray": "^0.0.6" @@ -4526,13 +3222,6 @@ "@types/babylon": "^6.16.2", "babel-types": "^6.26.0", "babylon": "^6.18.0" - }, - "dependencies": { - "babylon": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", - "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==" - } } }, "constants-browserify": { @@ -4572,9 +3261,9 @@ "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" }, "cookie-parser": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.3.tgz", - "integrity": "sha1-D+MfoZ0AC5X0qt8fU/3CuKIDuqU=", + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.4.tgz", + "integrity": "sha512-lo13tqF3JEtFO7FyA49CqbhaFkskRJ0u/UAiINgrIXeRCY41c88/zxtrECl8AKH3B0hj9q10+h3Kt8I7KlW4tw==", "requires": { "cookie": "0.3.1", "cookie-signature": "1.0.6" @@ -4604,24 +3293,26 @@ "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" }, "copy-webpack-plugin": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-4.6.0.tgz", - "integrity": "sha512-Y+SQCF+0NoWQryez2zXn5J5knmr9z/9qSQt7fbL78u83rxmigOy8X5+BFn8CFSuX+nKT8gpYwJX68ekqtQt6ZA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-5.0.0.tgz", + "integrity": "sha512-iiDj+8nnZeW/i8vYJ3+ABSZkOefJnDYIGLojiZKKFDvf1wcEInABXH1+hN7axQMn04qvJxKjgVOee0e14XPtCg==", "requires": { - "cacache": "^10.0.4", - "find-cache-dir": "^1.0.0", + "cacache": "^11.3.1", + "find-cache-dir": "^2.0.0", "globby": "^7.1.1", "is-glob": "^4.0.0", "loader-utils": "^1.1.0", "minimatch": "^3.0.4", - "p-limit": "^1.0.0", - "serialize-javascript": "^1.4.0" + "normalize-path": "^3.0.0", + "p-limit": "^2.1.0", + "serialize-javascript": "^1.4.0", + "webpack-log": "^2.0.0" } }, "core-js": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.3.tgz", - "integrity": "sha1-isw4NFgk8W2DZbfJtCWRaOjtYD4=" + "version": "3.0.0-beta.13", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.0.0-beta.13.tgz", + "integrity": "sha512-16Q43c/3LT9NyePUJKL8nRIQgYWjcBhjJSMWg96PVSxoS0PeE0NHitPI3opBrs9MGGHjte1KoEVr9W63YKlTXQ==" }, "core-util-is": { "version": "1.0.2", @@ -4663,7 +3354,7 @@ }, "create-hash": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", "dev": true, "requires": { @@ -4676,7 +3367,7 @@ }, "create-hmac": { "version": "1.1.7", - "resolved": "http://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", "dev": true, "requires": { @@ -4689,22 +3380,38 @@ } }, "cross-fetch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-2.0.0.tgz", - "integrity": "sha512-gnx0GnDyW73iDq6DpqceL8i4GGn55PPKDzNwZkopJ3mKPcfJ0BUIXBsnYfJBVw+jFDB+hzIp2ELNRdqoxN6M3w==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-2.2.2.tgz", + "integrity": "sha1-pH/09/xxLauo9qaVoRyUhEDUVyM=", "dev": true, "requires": { - "node-fetch": "2.0.0", - "whatwg-fetch": "2.0.3" + "node-fetch": "2.1.2", + "whatwg-fetch": "2.0.4" + }, + "dependencies": { + "node-fetch": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.1.2.tgz", + "integrity": "sha1-q4hOjn5X44qUR1POxwb3iNF2i7U=", + "dev": true + }, + "whatwg-fetch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz", + "integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==", + "dev": true + } } }, "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "dev": true, "requires": { - "lru-cache": "^4.0.1", + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", "shebang-command": "^1.2.0", "which": "^1.2.9" } @@ -4743,9 +3450,9 @@ } }, "csstype": { - "version": "2.5.7", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.5.7.tgz", - "integrity": "sha512-Nt5VDyOTIIV4/nRFswoCKps1R5CD1hkiyjBE9/thNaNZILLEviVw9yWQw15+O+CpNjQKB/uvdcxFFOrSflY3Yw==" + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.2.tgz", + "integrity": "sha512-Rl7PvTae0pflc1YtxtKbiSqq20Ts6vpIYOD5WBafl4y123DyHUeLrRdQP66sQW8/6gmX8jrYJLXwNeMqYVJcow==" }, "cyclist": { "version": "0.2.2", @@ -4772,9 +3479,9 @@ "integrity": "sha512-mYtLl1xfZLi1m4RtQYlZgJUNQjl4ZxVnHzIR8nLLgi4q1YT8o/WM+MK/f8yfcc9s5Ir5zRaPZyZU6xs1Syoocg==" }, "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "requires": { "ms": "2.0.0" } @@ -4789,6 +3496,15 @@ "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" }, + "deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "requires": { + "type-detect": "^4.0.0" + } + }, "deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", @@ -4802,9 +3518,9 @@ "dev": true }, "deepmerge": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz", - "integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==" + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-3.2.0.tgz", + "integrity": "sha512-6+LuZGU7QCNUnAJyX8cIrlzoEgggTM6B7mm+znKOX4t5ltluT9KLjN6g61ECMS0LTsLW7yDpNoxhix5FZcrIow==" }, "define-properties": { "version": "1.1.3", @@ -4851,43 +3567,6 @@ } } }, - "del": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", - "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", - "dev": true, - "requires": { - "globby": "^5.0.0", - "is-path-cwd": "^1.0.0", - "is-path-in-cwd": "^1.0.0", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "rimraf": "^2.2.8" - }, - "dependencies": { - "globby": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", - "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", - "dev": true, - "requires": { - "array-union": "^1.0.1", - "arrify": "^1.0.0", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - } - } - }, "delegate": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", @@ -4924,40 +3603,22 @@ "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=" }, "dicer": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz", - "integrity": "sha1-WZbAhrszIYyBLAkL3cCc0S+stw8=", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.3.0.tgz", + "integrity": "sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA==", "requires": { - "readable-stream": "1.1.x", "streamsearch": "0.1.2" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - }, - "readable-stream": { - "version": "1.1.14", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" - } } }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, "diffie-hellman": { "version": "5.0.3", - "resolved": "http://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", "dev": true, "requires": { @@ -4967,18 +3628,17 @@ } }, "dir-glob": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.0.0.tgz", - "integrity": "sha512-37qirFDz8cA5fimp9feo43fSuRo2gHwaIn6dXL8Ber1dGwUosDrGZeCCXq57WnIqE4aQ+u3eQZzsk1yOzhdwag==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz", + "integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==", "requires": { - "arrify": "^1.0.1", "path-type": "^3.0.0" } }, "dmd": { - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/dmd/-/dmd-3.0.12.tgz", - "integrity": "sha512-79w644JdsB2TthYpVl2bDurX7i9Abaegg2E7X46Ajc135aASTMXxrHzJ9mOa5X5nbmnXwlBYiF68K+1baX+BzQ==", + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/dmd/-/dmd-3.0.13.tgz", + "integrity": "sha512-FV/417bH2c/CYpe8BjFEAHoaHaItcJnPlKELi/qyPZdmUom8joyuC78OhhfPUdyKD/WcouTQ2LxQT4M/RoiJ3w==", "dev": true, "requires": { "array-back": "^2.0.0", @@ -4996,9 +3656,9 @@ } }, "doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, "requires": { "esutils": "^2.0.2" @@ -5010,9 +3670,12 @@ "integrity": "sha1-6oCxBqh1OHdOijpKWv4pPeSJ4Kk=" }, "dom-helpers": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.3.1.tgz", - "integrity": "sha512-2Sm+JaYn74OiTM2wHvxJOo3roiq/h25Yi69Fqk269cNUwIXsCvATB6CRSFC9Am/20G2b28hGv/+7NiWydIrPvg==" + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz", + "integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==", + "requires": { + "@babel/runtime": "^7.1.2" + } }, "domain-browser": { "version": "1.2.0", @@ -5044,12 +3707,12 @@ "integrity": "sha512-HygQCKUBSFl8wKQZBSemMywRWcEDNidvNbjGVyZu3nbZ8qq9ubiPoGLMdRDpfSrpkkm9BXYFkpKxxFX38o/76w==" }, "dtrace-provider": { - "version": "0.8.6", - "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.6.tgz", - "integrity": "sha1-QooiOv4DQl0s1tY0f99AxmkDVj0=", + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.7.tgz", + "integrity": "sha1-3JObTT4GIM/gwc2APQ0tftBP/QQ=", "optional": true, "requires": { - "nan": "^2.3.3" + "nan": "^2.10.0" } }, "duplexer3": { @@ -5059,9 +3722,9 @@ "dev": true }, "duplexify": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.6.1.tgz", - "integrity": "sha512-vM58DwdnKmty+FSPzT14K9JXb90H+j5emaR4KYbr2KTIz00WHGbWOe5ghQTx233ZCLZtrGDALzKwcjEtSt35mA==", + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", "requires": { "end-of-stream": "^1.0.0", "inherits": "^2.0.1", @@ -5070,9 +3733,9 @@ } }, "ecdsa-sig-formatter": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.10.tgz", - "integrity": "sha1-HFlQAPBKiJffuFAAiSoPTDOvhsM=", + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", "requires": { "safe-buffer": "^5.0.1" } @@ -5083,9 +3746,9 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "electron-to-chromium": { - "version": "1.3.90", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.90.tgz", - "integrity": "sha512-IjJZKRhFbWSOX1w0sdIXgp4CMRguu6UYcTckyFF/Gjtemsu/25eZ+RXwFlV+UWcIueHyQA1UnRJxocTpH5NdGA==", + "version": "1.3.113", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.113.tgz", + "integrity": "sha512-De+lPAxEcpxvqPTyZAXELNpRZXABRxf+uL/rSykstQhzj/B0l1150G/ExIIxKc16lI89Hgz81J0BHAcbTqK49g==", "dev": true }, "elliptic": { @@ -5103,6 +3766,12 @@ "minimalistic-crypto-utils": "^1.0.0" } }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, "emojis-list": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", @@ -5141,9 +3810,9 @@ } }, "entities": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz", - "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" }, "errno": { "version": "0.1.7", @@ -5164,15 +3833,16 @@ } }, "es-abstract": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.12.0.tgz", - "integrity": "sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", + "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", "requires": { - "es-to-primitive": "^1.1.1", + "es-to-primitive": "^1.2.0", "function-bind": "^1.1.1", - "has": "^1.0.1", - "is-callable": "^1.1.3", - "is-regex": "^1.0.4" + "has": "^1.0.3", + "is-callable": "^1.1.4", + "is-regex": "^1.0.4", + "object-keys": "^1.0.12" } }, "es-to-primitive": { @@ -5196,75 +3866,86 @@ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "eslint": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.19.1.tgz", - "integrity": "sha512-bT3/1x1EbZB7phzYu7vCr1v3ONuzDtX8WjuM9c0iYxe+cq+pwcKEoQjl7zd3RpC6YOLgnSy3cTN58M2jcoPDIQ==", + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.15.0.tgz", + "integrity": "sha512-xwG7SS5JLeqkiR3iOmVgtF8Y6xPdtr6AAsN6ph7Q6R/fv+3UlKYoika8SmNzmb35qdRF+RfTY35kMEdtbi+9wg==", "dev": true, "requires": { - "ajv": "^5.3.0", - "babel-code-frame": "^6.22.0", + "@babel/code-frame": "^7.0.0", + "ajv": "^6.9.1", "chalk": "^2.1.0", - "concat-stream": "^1.6.0", - "cross-spawn": "^5.1.0", - "debug": "^3.1.0", - "doctrine": "^2.1.0", - "eslint-scope": "^3.7.1", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "eslint-scope": "^4.0.2", + "eslint-utils": "^1.3.1", "eslint-visitor-keys": "^1.0.0", - "espree": "^3.5.4", - "esquery": "^1.0.0", + "espree": "^5.0.1", + "esquery": "^1.0.1", "esutils": "^2.0.2", - "file-entry-cache": "^2.0.0", + "file-entry-cache": "^5.0.1", "functional-red-black-tree": "^1.0.1", "glob": "^7.1.2", - "globals": "^11.0.1", - "ignore": "^3.3.3", + "globals": "^11.7.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", - "inquirer": "^3.0.6", - "is-resolvable": "^1.0.0", - "js-yaml": "^3.9.1", + "inquirer": "^6.2.2", + "js-yaml": "^3.12.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.3.0", - "lodash": "^4.17.4", - "minimatch": "^3.0.2", + "lodash": "^4.17.11", + "minimatch": "^3.0.4", "mkdirp": "^0.5.1", "natural-compare": "^1.4.0", "optionator": "^0.8.2", "path-is-inside": "^1.0.2", - "pluralize": "^7.0.0", "progress": "^2.0.0", - "regexpp": "^1.0.1", - "require-uncached": "^1.0.3", - "semver": "^5.3.0", + "regexpp": "^2.0.1", + "semver": "^5.5.1", "strip-ansi": "^4.0.0", - "strip-json-comments": "~2.0.1", - "table": "4.0.2", - "text-table": "~0.2.0" + "strip-json-comments": "^2.0.1", + "table": "^5.2.3", + "text-table": "^0.2.0" }, "dependencies": { - "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "dev": true, "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" + "ms": "^2.1.1" } }, - "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", + "eslint-scope": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.2.tgz", + "integrity": "sha512-5q1+B/ogmHl8+paxtOKx38Z8LtWkVGuNt3+GQNErqwLl6ViNp/gdJGMCjZNxZ8j/VYjDNZ2Fo+eQc1TAVPIzbg==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "dev": true } } }, "eslint-config-standard": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-11.0.0.tgz", - "integrity": "sha512-oDdENzpViEe5fwuRCWla7AXQd++/oyIp8zP+iP9jiUPG6NBj3SHgdgtl/kTn00AjeN+1HNvavTKmYbMo+xMOlw==", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-12.0.0.tgz", + "integrity": "sha512-COUz8FnXhqFitYj4DTqHzidjIL/t4mumGZto5c7DrBpvWoie+Sn3P4sLEzUGeYhRElWuFEf8K1S1EfvD1vixCQ==", "dev": true }, "eslint-import-resolver-node": { @@ -5275,23 +3956,12 @@ "requires": { "debug": "^2.6.9", "resolve": "^1.5.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } } }, "eslint-loader": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/eslint-loader/-/eslint-loader-2.1.1.tgz", - "integrity": "sha512-1GrJFfSevQdYpoDzx8mEE2TDWsb/zmFuY09l6hURg1AeFIKQOvZ+vH0UPjzmd1CZIbfTV5HUkMeBmFiDBkgIsQ==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/eslint-loader/-/eslint-loader-2.1.2.tgz", + "integrity": "sha512-rA9XiXEOilLYPOIInvVH5S/hYfyTPyxag6DZhoQOduM+3TkghAEQ3VcFO8VnX4J4qg/UIBzp72aOf/xvYmpmsg==", "dev": true, "requires": { "loader-fs-cache": "^1.0.0", @@ -5302,81 +3972,97 @@ } }, "eslint-module-utils": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.2.0.tgz", - "integrity": "sha1-snA2LNiLGkitMIl2zn+lTphBF0Y=", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.3.0.tgz", + "integrity": "sha512-lmDJgeOOjk8hObTysjqH7wyMi+nsHwwvfBykwfhjR1LNdd7C2uFJBvx4OpWYpXOw4df1yE1cDEVd1yLHitk34w==", "dev": true, "requires": { "debug": "^2.6.8", - "pkg-dir": "^1.0.0" + "pkg-dir": "^2.0.0" }, "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", "dev": true, "requires": { - "ms": "2.0.0" + "locate-path": "^2.0.0" } }, - "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", "dev": true, "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" } }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", "dev": true, "requires": { - "pinkie-promise": "^2.0.0" + "p-try": "^1.0.0" } }, - "pkg-dir": { + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz", - "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, + "pkg-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", + "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", "dev": true, "requires": { - "find-up": "^1.0.0" + "find-up": "^2.1.0" } } } }, + "eslint-plugin-es": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-1.4.0.tgz", + "integrity": "sha512-XfFmgFdIUDgvaRAlaXUkxrRg5JSADoRC8IkKLc/cISeR3yHVMefFHQZpcyXXEUUPHfy5DwviBcrfqlyqEwlQVw==", + "dev": true, + "requires": { + "eslint-utils": "^1.3.0", + "regexpp": "^2.0.1" + } + }, "eslint-plugin-import": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.14.0.tgz", - "integrity": "sha512-FpuRtniD/AY6sXByma2Wr0TXvXJ4nA/2/04VPlfpmUDPOpOY264x+ILiwnrk/k4RINgDAyFZByxqPUbSQ5YE7g==", + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.16.0.tgz", + "integrity": "sha512-z6oqWlf1x5GkHIFgrSvtmudnqM6Q60KM4KvpWi5ubonMjycLjndvd5+8VAZIsTlHC03djdgJuyKG6XO577px6A==", "dev": true, "requires": { "contains-path": "^0.1.0", - "debug": "^2.6.8", + "debug": "^2.6.9", "doctrine": "1.5.0", - "eslint-import-resolver-node": "^0.3.1", - "eslint-module-utils": "^2.2.0", - "has": "^1.0.1", - "lodash": "^4.17.4", - "minimatch": "^3.0.3", + "eslint-import-resolver-node": "^0.3.2", + "eslint-module-utils": "^2.3.0", + "has": "^1.0.3", + "lodash": "^4.17.11", + "minimatch": "^3.0.4", "read-pkg-up": "^2.0.0", - "resolve": "^1.6.0" + "resolve": "^1.9.0" }, "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, "doctrine": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", @@ -5386,48 +4072,41 @@ "esutils": "^2.0.2", "isarray": "^1.0.0" } - }, - "resolve": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", - "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", - "dev": true, - "requires": { - "path-parse": "^1.0.5" - } } } }, "eslint-plugin-node": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-6.0.1.tgz", - "integrity": "sha512-Q/Cc2sW1OAISDS+Ji6lZS2KV4b7ueA/WydVWd1BECTQwVvfQy5JAi3glhINoKzoMnfnuRgNP+ZWKrGAbp3QDxw==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-8.0.1.tgz", + "integrity": "sha512-ZjOjbjEi6jd82rIpFSgagv4CHWzG9xsQAVp1ZPlhRnnYxcTgENUVBvhYmkQ7GvT1QFijUSo69RaiOJKhMu6i8w==", "dev": true, "requires": { - "ignore": "^3.3.6", + "eslint-plugin-es": "^1.3.1", + "eslint-utils": "^1.3.1", + "ignore": "^5.0.2", "minimatch": "^3.0.4", - "resolve": "^1.3.3", - "semver": "^5.4.1" + "resolve": "^1.8.1", + "semver": "^5.5.0" }, "dependencies": { - "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", + "ignore": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.0.5.tgz", + "integrity": "sha512-kOC8IUb8HSDMVcYrDVezCxpJkzSQWTAzf3olpKM6o9rM5zpojx23O0Fl8Wr4+qJ6ZbPEHqf1fdwev/DS7v7pmA==", "dev": true } } }, "eslint-plugin-promise": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-3.8.0.tgz", - "integrity": "sha512-JiFL9UFR15NKpHyGii1ZcvmtIqa3UTwiDAGb8atSffe43qJ3+1czVGN6UtkklpcJ2DVnqvTMzEKRaJdBkAL2aQ==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-4.0.1.tgz", + "integrity": "sha512-Si16O0+Hqz1gDHsys6RtFRrW7cCTB6P7p3OJmKp3Y3dxpQE2qwOA7d3xnV+0mBmrPoi0RBnxlCKvqu70te6wjg==", "dev": true }, "eslint-plugin-standard": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-standard/-/eslint-plugin-standard-3.1.0.tgz", - "integrity": "sha512-fVcdyuKRr0EZ4fjWl3c+gp1BANFJD1+RaWa2UPYfMZ6jCtp5RG00kSaXnK/dE5sYzt4kaWJ9qdxqUfc0d9kX0w==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-standard/-/eslint-plugin-standard-4.0.0.tgz", + "integrity": "sha512-OwxJkR6TQiYMmt1EsNRMe5qG3GsbjlcOhbGUBY4LtavF9DsLaTcoR+j2Tdjqi23oUwKNUqX7qcn5fPStafMdlA==", "dev": true }, "eslint-scope": { @@ -5440,6 +4119,12 @@ "estraverse": "^4.1.1" } }, + "eslint-utils": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.3.1.tgz", + "integrity": "sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q==", + "dev": true + }, "eslint-visitor-keys": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", @@ -5447,19 +4132,28 @@ "dev": true }, "espree": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", - "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-5.0.1.tgz", + "integrity": "sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A==", "dev": true, "requires": { - "acorn": "^5.5.0", - "acorn-jsx": "^3.0.0" + "acorn": "^6.0.7", + "acorn-jsx": "^5.0.0", + "eslint-visitor-keys": "^1.0.0" + }, + "dependencies": { + "acorn": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz", + "integrity": "sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==", + "dev": true + } } }, "esprima": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", - "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true }, "esquery": { @@ -5502,9 +4196,9 @@ "integrity": "sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA==" }, "events": { - "version": "1.1.1", - "resolved": "http://registry.npmjs.org/events/-/events-1.1.1.tgz", - "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz", + "integrity": "sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA==", "dev": true }, "evp_bytestokey": { @@ -5518,13 +4212,13 @@ } }, "execa": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", - "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", "dev": true, "requires": { - "cross-spawn": "^5.0.1", - "get-stream": "^3.0.0", + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", "is-stream": "^1.1.0", "npm-run-path": "^2.0.0", "p-finally": "^1.0.0", @@ -5546,14 +4240,6 @@ "to-regex": "^3.0.1" }, "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, "define-property": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", @@ -5617,18 +4303,15 @@ "vary": "~1.1.2" }, "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" } } }, @@ -5657,16 +4340,6 @@ "parseurl": "~1.3.2", "uid-safe": "~2.1.5", "utils-merge": "1.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - } } }, "express-unless": { @@ -5699,14 +4372,25 @@ } }, "external-editor": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", - "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.3.tgz", + "integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==", "dev": true, "requires": { - "chardet": "^0.4.0", - "iconv-lite": "^0.4.17", + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", "tmp": "^0.0.33" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + } } }, "extglob": { @@ -5774,9 +4458,9 @@ "integrity": "sha1-WtlGwi9bMrp/jNdCZxHG6KP8JSk=" }, "fast-deep-equal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" }, "fast-json-stable-stringify": { "version": "2.0.0", @@ -5813,8 +4497,7 @@ "figgy-pudding": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz", - "integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==", - "dev": true + "integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==" }, "figures": { "version": "2.0.0", @@ -5826,22 +4509,21 @@ } }, "file-entry-cache": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", - "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", + "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", "dev": true, "requires": { - "flat-cache": "^1.2.1", - "object-assign": "^4.0.1" + "flat-cache": "^2.0.1" } }, "file-loader": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-1.1.11.tgz", - "integrity": "sha512-TGR4HU7HUsGg6GCOPJnFk06RhWgEWFLAGWiT6rcD+GRC2keU3s9RGJ+b3Z6/U73jwwNb2gKLJ7YCrp+jvU4ALg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-3.0.1.tgz", + "integrity": "sha512-4sNIOXgtH/9WZq4NvlfU3Opn5ynUsqBwSLyM+I7UOwdGigTBYfVVQEwe/msZNX/j4pCJTIM14Fsw66Svo1oVrw==", "requires": { "loader-utils": "^1.0.2", - "schema-utils": "^0.4.5" + "schema-utils": "^1.0.0" } }, "file-set": { @@ -5852,22 +4534,6 @@ "requires": { "array-back": "^2.0.0", "glob": "^7.1.3" - }, - "dependencies": { - "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - } } }, "fill-range": { @@ -5905,24 +4571,21 @@ "unpipe": "~1.0.0" }, "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" } } }, "find-cache-dir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-1.0.0.tgz", - "integrity": "sha1-kojj6ePMN0hxfTnq3hfPcfww7m8=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.0.0.tgz", + "integrity": "sha512-LDUY6V1Xs5eFskUVYtIwatojt6+9xC9Chnlk/jYOOvn3FAFfSaWddxahDGyNHh0b2dMXa6YW2m0tk8TdVaXHlA==", "requires": { "commondir": "^1.0.1", "make-dir": "^1.0.0", - "pkg-dir": "^2.0.0" + "pkg-dir": "^3.0.0" } }, "find-replace": { @@ -5936,11 +4599,11 @@ } }, "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "requires": { - "locate-path": "^2.0.0" + "locate-path": "^3.0.0" } }, "findup-sync": { @@ -5965,9 +4628,9 @@ } }, "fined": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fined/-/fined-1.1.0.tgz", - "integrity": "sha1-s33IRLdqL15wgeiE98CuNE8VNHY=", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/fined/-/fined-1.1.1.tgz", + "integrity": "sha512-jQp949ZmEbiYHk3gkbdtpJ0G1+kgtLQBNdP5edFP7Fh+WAYceLQz6yO1SBj72Xkg8GVyTB3bBzAYrHJVh5Xd5g==", "requires": { "expand-tilde": "^2.0.2", "is-plain-object": "^2.0.3", @@ -5977,37 +4640,59 @@ } }, "flagged-respawn": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.0.tgz", - "integrity": "sha1-Tnmumy6zi/hrO7Vr8+ClaqX8q9c=" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz", + "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==" + }, + "flat": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", + "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", + "dev": true, + "requires": { + "is-buffer": "~2.0.3" + }, + "dependencies": { + "is-buffer": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz", + "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==", + "dev": true + } + } }, "flat-cache": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.0.tgz", - "integrity": "sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", "dev": true, "requires": { - "circular-json": "^0.3.1", - "del": "^2.0.2", - "graceful-fs": "^4.1.2", - "write": "^0.2.1" + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" } }, + "flatted": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.0.tgz", + "integrity": "sha512-R+H8IZclI8AAkSBRQJLVOsxwAoHd6WC40b4QTNWIjzAa6BXOBfQcM587MXDTVPeYaopFNWHUFLx7eNmHDSxMWg==", + "dev": true + }, "flush-write-stream": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.0.3.tgz", - "integrity": "sha512-calZMC10u0FMUqoiunI2AiGIIUtUIvifNwkHhNupZH4cbNnW1Itkoh/Nf5HFYmDrwWPjrUxpkZT0KhuCq0jmGw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", + "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.4" + "inherits": "^2.0.3", + "readable-stream": "^2.3.6" } }, "fmtr": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fmtr/-/fmtr-1.1.0.tgz", - "integrity": "sha1-R1RcTNo3rWkbDSDiaHbQdexz/mg=", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/fmtr/-/fmtr-1.1.1.tgz", + "integrity": "sha512-nb0b2t9fKK7/Xxh3cv5MNnqaLbT6N67anBsPy0eOwTErTXtz12diKDzDoeqXTN8Ie8/qg2xJEXNBW3Nb2Dd0lQ==", "requires": { - "lodash": "^4.17.4" + "lodash": "^4.17.11" } }, "for-in": { @@ -6056,9 +4741,9 @@ "integrity": "sha1-invTcYa23d84E/I4WLV+yq9eQdQ=" }, "fs-capacitor": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fs-capacitor/-/fs-capacitor-1.0.1.tgz", - "integrity": "sha512-XdZK0Q78WP29Vm3FGgJRhRhrBm51PagovzWtW2kJ3Q6cYJbGtZqWSGTSPwvtEkyjIirFd7b8Yes/dpOYjt4RRQ==" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fs-capacitor/-/fs-capacitor-2.0.1.tgz", + "integrity": "sha512-kyV2oaG1/pu9NPosfGACmBym6okgzyg6hEtA5LSUq0dGpGLe278MVfMwVnSHDA/OBcTCHkPNqWL9eIwbPN6dDg==" }, "fs-readdir-recursive": { "version": "1.1.0", @@ -6089,9 +4774,9 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fsevents": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.4.tgz", - "integrity": "sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.7.tgz", + "integrity": "sha512-Pxm6sI2MeBD7RdD12RYsqaP0nMiwx8eZBXCa6z2L+mRHm2DYrOYwihmhjpkdjUHwQhslWQjRpEgNq4XvBmaAuw==", "dev": true, "optional": true, "requires": { @@ -6117,7 +4802,7 @@ "optional": true }, "are-we-there-yet": { - "version": "1.1.4", + "version": "1.1.5", "bundled": true, "dev": true, "optional": true, @@ -6141,7 +4826,7 @@ } }, "chownr": { - "version": "1.0.1", + "version": "1.1.1", "bundled": true, "dev": true, "optional": true @@ -6177,7 +4862,7 @@ } }, "deep-extend": { - "version": "0.5.1", + "version": "0.6.0", "bundled": true, "dev": true, "optional": true @@ -6226,7 +4911,7 @@ } }, "glob": { - "version": "7.1.2", + "version": "7.1.3", "bundled": true, "dev": true, "optional": true, @@ -6246,12 +4931,12 @@ "optional": true }, "iconv-lite": { - "version": "0.4.21", + "version": "0.4.24", "bundled": true, "dev": true, "optional": true, "requires": { - "safer-buffer": "^2.1.0" + "safer-buffer": ">= 2.1.2 < 3" } }, "ignore-walk": { @@ -6312,16 +4997,16 @@ "dev": true }, "minipass": { - "version": "2.2.4", + "version": "2.3.5", "bundled": true, "dev": true, "requires": { - "safe-buffer": "^5.1.1", + "safe-buffer": "^5.1.2", "yallist": "^3.0.0" } }, "minizlib": { - "version": "1.1.0", + "version": "1.2.1", "bundled": true, "dev": true, "optional": true, @@ -6344,7 +5029,7 @@ "optional": true }, "needle": { - "version": "2.2.0", + "version": "2.2.4", "bundled": true, "dev": true, "optional": true, @@ -6355,18 +5040,18 @@ } }, "node-pre-gyp": { - "version": "0.10.0", + "version": "0.10.3", "bundled": true, "dev": true, "optional": true, "requires": { "detect-libc": "^1.0.2", "mkdirp": "^0.5.1", - "needle": "^2.2.0", + "needle": "^2.2.1", "nopt": "^4.0.1", "npm-packlist": "^1.1.6", "npmlog": "^4.0.2", - "rc": "^1.1.7", + "rc": "^1.2.7", "rimraf": "^2.6.1", "semver": "^5.3.0", "tar": "^4" @@ -6383,13 +5068,13 @@ } }, "npm-bundled": { - "version": "1.0.3", + "version": "1.0.5", "bundled": true, "dev": true, "optional": true }, "npm-packlist": { - "version": "1.1.10", + "version": "1.2.0", "bundled": true, "dev": true, "optional": true, @@ -6464,12 +5149,12 @@ "optional": true }, "rc": { - "version": "1.2.7", + "version": "1.2.8", "bundled": true, "dev": true, "optional": true, "requires": { - "deep-extend": "^0.5.1", + "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" @@ -6499,16 +5184,16 @@ } }, "rimraf": { - "version": "2.6.2", + "version": "2.6.3", "bundled": true, "dev": true, "optional": true, "requires": { - "glob": "^7.0.5" + "glob": "^7.1.3" } }, "safe-buffer": { - "version": "5.1.1", + "version": "5.1.2", "bundled": true, "dev": true }, @@ -6525,7 +5210,7 @@ "optional": true }, "semver": { - "version": "5.5.0", + "version": "5.6.0", "bundled": true, "dev": true, "optional": true @@ -6576,17 +5261,17 @@ "optional": true }, "tar": { - "version": "4.4.1", + "version": "4.4.8", "bundled": true, "dev": true, "optional": true, "requires": { - "chownr": "^1.0.1", + "chownr": "^1.1.1", "fs-minipass": "^1.2.5", - "minipass": "^2.2.4", - "minizlib": "^1.1.0", + "minipass": "^2.3.4", + "minizlib": "^1.1.1", "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.1", + "safe-buffer": "^5.1.2", "yallist": "^3.0.2" } }, @@ -6597,12 +5282,12 @@ "optional": true }, "wide-align": { - "version": "1.1.2", + "version": "1.1.3", "bundled": true, "dev": true, "optional": true, "requires": { - "string-width": "^1.0.2" + "string-width": "^1.0.2 || 2" } }, "wrappy": { @@ -6611,7 +5296,7 @@ "dev": true }, "yallist": { - "version": "3.0.2", + "version": "3.0.3", "bundled": true, "dev": true } @@ -6629,9 +5314,9 @@ "dev": true }, "fuse.js": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-3.3.0.tgz", - "integrity": "sha512-ESBRkGLWMuVkapqYCcNO1uqMg5qbCKkgb+VS6wsy17Rix0/cMS9kSOZoYkjH8Ko//pgJ/EEGu0GTjk2mjX2LGQ==" + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-3.4.2.tgz", + "integrity": "sha512-WVbrm+cAxPtyMqdtL7cYhR7aZJPhtOfjNClPya8GKMVukKDYs7pEnPINeRVX1C9WmWgU8MdYGYbUPAP2AJXdoQ==" }, "get-caller-file": { "version": "1.0.3", @@ -6639,21 +5324,30 @@ "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", "dev": true }, - "get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", "dev": true }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, "get-value": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=" }, "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -6703,12 +5397,6 @@ "resolve-dir": "^1.0.0" } }, - "global-modules-path": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/global-modules-path/-/global-modules-path-2.3.0.tgz", - "integrity": "sha512-HchvMJNYh9dGSCy8pOQ2O8u/hoXaL+0XhnrwH0RyLiSXMMTl9W3N6KUU73+JFOg5PGjtzl6VZzUQsnrpm7Szag==", - "dev": true - }, "global-prefix": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", @@ -6722,9 +5410,9 @@ } }, "globals": { - "version": "11.5.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.5.0.tgz", - "integrity": "sha512-hYyf+kI8dm3nORsiiXUQigOU62hDLfJ9G01uyGMxhc6BKsircrUhC4uJPQPUSuq2GrTmiiEt7ewxlMdBewfmKQ==", + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.11.0.tgz", + "integrity": "sha512-WHq43gS+6ufNOEqlrDBxVEbb8ntfXrfAUU2ZOpCxrBdGKW3gyv8mCxAfIBD0DroPKGrJ2eSsXsLtY9MPntsyTw==", "dev": true }, "globby": { @@ -6738,13 +5426,6 @@ "ignore": "^3.3.5", "pify": "^3.0.0", "slash": "^1.0.0" - }, - "dependencies": { - "slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=" - } } }, "good-listener": { @@ -6757,7 +5438,7 @@ }, "got": { "version": "6.7.1", - "resolved": "http://registry.npmjs.org/got/-/got-6.7.1.tgz", + "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", "dev": true, "requires": { @@ -6772,27 +5453,66 @@ "timed-out": "^4.0.0", "unzip-response": "^2.0.1", "url-parse-lax": "^1.0.0" + }, + "dependencies": { + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", + "dev": true + } } }, "graceful-fs": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==" }, "graphql": { - "version": "14.0.2", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-14.0.2.tgz", - "integrity": "sha512-gUC4YYsaiSJT1h40krG3J+USGlwhzNTXSb4IOZljn9ag5Tj+RkoXrWp+Kh7WyE3t1NCfab5kzCuxBIvOMERMXw==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-14.1.1.tgz", + "integrity": "sha512-C5zDzLqvfPAgTtP8AUPIt9keDabrdRAqSWjj2OPRKrKxI9Fb65I36s1uCs1UUBFnSWTdO7hyHi7z1ZbwKMKF6Q==", "requires": { "iterall": "^1.2.2" } }, + "graphql-config": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/graphql-config/-/graphql-config-1.2.1.tgz", + "integrity": "sha512-BOtbEOn/fD13jT0peCy3Fzp1DSTsA/1AcZp266AQ5Sk3wFndKCEa/H7donbu5UriOw1V/N1WDirYPnr7rd8E7Q==", + "dev": true, + "requires": { + "graphql": "^0.12.3", + "graphql-import": "^0.4.0", + "graphql-request": "^1.4.0", + "js-yaml": "^3.10.0", + "lodash": "^4.17.4", + "minimatch": "^3.0.4" + }, + "dependencies": { + "graphql": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-0.12.3.tgz", + "integrity": "sha512-Hn9rdu4zacplKXNrLCvR8YFiTGnbM4Zw/UH8FDmzBDsH7ou40lSNH4tIlsxcYnz2TGNVJCpu1WxCM23yd6kzhA==", + "dev": true, + "requires": { + "iterall": "1.1.3" + } + }, + "iterall": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.1.3.tgz", + "integrity": "sha512-Cu/kb+4HiNSejAPhSaN1VukdNTTi/r4/e+yykqjlG/IW+1gZH5b4+Bq3whDX4tvbYugta3r8KTMUiqT3fIGxuQ==", + "dev": true + } + } + }, "graphql-extensions": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/graphql-extensions/-/graphql-extensions-0.4.1.tgz", - "integrity": "sha512-Xei4rBxbsTHU6dYiq9y1xxbpRMU3+Os7yD3vXV5W4HbTaxRMizDmu6LAvV4oBEi0ttwICHARQjYTjDTDhHnxrQ==", + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/graphql-extensions/-/graphql-extensions-0.5.7.tgz", + "integrity": "sha512-HrU6APE1PiehZ46scMB3S5DezSeCATd8v+e4mmg2bqszMyCFkmAnmK6hR1b5VjHxhzt5/FX21x1WsXfqF4FwdQ==", "requires": { - "@apollographql/apollo-tools": "^0.2.6" + "@apollographql/apollo-tools": "^0.3.3" } }, "graphql-import": { @@ -6805,12 +5525,12 @@ } }, "graphql-request": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/graphql-request/-/graphql-request-1.6.0.tgz", - "integrity": "sha512-qqAPLZuaGlwZDsMQ2FfgEyZMcXFMsPPDl6bQQlmwP/xCnk1TqxkE1S644LsHTXAHYPvmRWsIimfdcnys5+o+fQ==", + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/graphql-request/-/graphql-request-1.8.2.tgz", + "integrity": "sha512-dDX2M+VMsxXFCmUX0Vo0TopIZIX4ggzOtiCsThgtrKR4niiaagsGTDIHj3fsOMFETpa064vzovI+4YV4QnMbcg==", "dev": true, "requires": { - "cross-fetch": "2.0.0" + "cross-fetch": "2.2.2" } }, "graphql-subscriptions": { @@ -6822,16 +5542,16 @@ } }, "graphql-tag": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.10.0.tgz", - "integrity": "sha512-9FD6cw976TLLf9WYIUPCaaTpniawIjHWZSwIRZSjrfufJamcXbVVYfN2TWvJYbw0Xf2JjYbl1/f2+wDnBVw3/w==" + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.10.1.tgz", + "integrity": "sha512-jApXqWBzNXQ8jYa/HLkZJaVw9jgwNqZkywa2zfFn16Iv1Zb7ELNHkJaXHR7Quvd5SIGsy6Ny7SUKATgnu05uEg==" }, "graphql-tools": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/graphql-tools/-/graphql-tools-3.1.1.tgz", - "integrity": "sha512-yHvPkweUB0+Q/GWH5wIG60bpt8CTwBklCSzQdEHmRUgAdEQKxw+9B7zB3dG7wB3Ym7M7lfrS4Ej+jtDZfA2UXg==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/graphql-tools/-/graphql-tools-4.0.4.tgz", + "integrity": "sha512-chF12etTIGVVGy3fCTJ1ivJX2KB7OSG4c6UOJQuqOHCmBQwTyNgCDuejZKvpYxNZiEx7bwIjrodDgDe9RIkjlw==", "requires": { - "apollo-link": "^1.2.2", + "apollo-link": "^1.2.3", "apollo-utilities": "^1.0.1", "deprecated-decorator": "^0.1.6", "iterall": "^1.1.3", @@ -6839,33 +5559,14 @@ } }, "graphql-upload": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/graphql-upload/-/graphql-upload-8.0.2.tgz", - "integrity": "sha512-u8a5tKPfJ0rU4MY+B3skabL8pEjMkm3tUzq25KBx6nT0yEWmqUO7Z5tdwvwYLFpkLwew94Gue0ARbZtar3gLTw==", + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/graphql-upload/-/graphql-upload-8.0.4.tgz", + "integrity": "sha512-jsTfVYXJ5mU6BXiiJ20CUCAcf41ICCQJ2ltwQFUuaFKiY4JhlG99uZZp5S3hbpQ/oA1kS7hz4pRtsnxPCa7Yfg==", "requires": { - "busboy": "^0.2.14", - "fs-capacitor": "^1.0.0", + "busboy": "^0.3.0", + "fs-capacitor": "^2.0.0", "http-errors": "^1.7.1", "object-path": "^0.11.4" - }, - "dependencies": { - "http-errors": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.1.tgz", - "integrity": "sha512-jWEUgtZWGSMba9I1N3gc1HmvpBUaNC9vDdA46yScAdp+C5rdEuKWUBLWTQpW9FwSWSbYYs++b6SDCxf9UEJzfw==", - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - } - }, - "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" - } } }, "graphql-voyager": { @@ -6887,10 +5588,16 @@ "viz.js": "2.0.0" } }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, "handlebars": { - "version": "4.0.12", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.12.tgz", - "integrity": "sha512-RhmTekP+FZL+XNhwS1Wf+bTTZpdLougwt5pcgA1tuz6Jcx0fpH/7z0qd71RKnZHBCxIRBHfBOnio4gViPemNzA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.0.tgz", + "integrity": "sha512-l2jRuU1NAWK6AW5qqcTATWQJvNPEwkM7NEKSiv/gqOsoSQbVoWyqVEY5GS+XPQ88zLNmqASRpzfdm8d79hJS+w==", "dev": true, "requires": { "async": "^2.5.0", @@ -6900,14 +5607,21 @@ }, "dependencies": { "async": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", - "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", + "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", "dev": true, "requires": { - "lodash": "^4.17.10" + "lodash": "^4.17.11" } }, + "commander": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", + "dev": true, + "optional": true + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -6928,11 +5642,11 @@ } }, "has": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz", - "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "requires": { - "function-bind": "^1.0.2" + "function-bind": "^1.1.1" } }, "has-ansi": { @@ -6942,6 +5656,14 @@ "dev": true, "requires": { "ansi-regex": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + } } }, "has-flag": { @@ -7003,6 +5725,12 @@ "minimalistic-assert": "^1.0.1" } }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, "header-case": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/header-case/-/header-case-1.0.1.tgz", @@ -7025,14 +5753,17 @@ } }, "hoist-non-react-statics": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz", - "integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==" + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.0.tgz", + "integrity": "sha512-0XsbTXxgiaCDYDIWFcwkmerZPSwywfUqYmwT4jzewKTQSWoE6FCMoUVOeBJWK3E/CrWbxRG3m5GzY4lnIwGRBA==", + "requires": { + "react-is": "^16.7.0" + } }, "homedir-polyfill": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz", - "integrity": "sha1-TCu8inWJmP7r9e1oWA921GdotLw=", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", "requires": { "parse-passwd": "^1.0.0" } @@ -7044,14 +5775,15 @@ "dev": true }, "http-errors": { - "version": "1.6.3", - "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", "requires": { "depd": "~1.1.2", "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" } }, "https-browserify": { @@ -7061,14 +5793,17 @@ "dev": true }, "hyphenate-style-name": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.2.tgz", - "integrity": "sha1-MRYKNpMK2vH8BMYHT360FGXU7Es=" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.3.tgz", + "integrity": "sha512-EcuixamT82oplpoJ2XU4pDtKGWQ7b00CD9f1ug9IaQ3p1bkHMiKCZ9ut9QDI6qsa6cpUuB+A/I+zLtdNK4n2DQ==" }, "iconv-lite": { - "version": "0.4.19", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", - "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } }, "ieee754": { "version": "1.1.12", @@ -7082,9 +5817,9 @@ "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=" }, "ignore": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.7.tgz", - "integrity": "sha512-YGG3ejvBNHRqu0559EOxxNFihD0AjpvHlC/pdGKd3X3ofe+CoJkYazwNJYTNebqpPKN+VVQbh4ZFn1DivMNuHA==" + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", + "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==" }, "ignore-by-default": { "version": "1.0.1", @@ -7092,6 +5827,16 @@ "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=", "dev": true }, + "import-fresh": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.0.0.tgz", + "integrity": "sha512-pOnA9tfM3Uwics+SaBLCNyZZZbK+4PTu0OPZtLlMIrv17EdBoC15S9Kn8ckJ9TZTyKb3ywNE5y1yeDxxGA7nTQ==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, "import-lazy": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", @@ -7106,60 +5851,6 @@ "requires": { "pkg-dir": "^3.0.0", "resolve-cwd": "^2.0.0" - }, - "dependencies": { - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.0.0.tgz", - "integrity": "sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-try": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", - "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", - "dev": true - }, - "pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "dev": true, - "requires": { - "find-up": "^3.0.0" - } - } } }, "imurmurhash": { @@ -7168,18 +5859,11 @@ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" }, "indefinite-observable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/indefinite-observable/-/indefinite-observable-1.0.1.tgz", - "integrity": "sha1-CZFUI8yNb36xy3iCrRNGM8mm7cM=", - "requires": { - "symbol-observable": "1.0.4" - }, - "dependencies": { - "symbol-observable": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.4.tgz", - "integrity": "sha1-Kb9hXUqnEhvdiYsi1LP5vE4qoD0=" - } + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/indefinite-observable/-/indefinite-observable-1.0.2.tgz", + "integrity": "sha512-Mps0898zEduHyPhb7UCgNmfzlqNZknVmaFz5qzr0mm04YQ5FGLhAyK/dJ+NaRxGyR6juQXIxh5Ev0xx+qq0nYA==", + "requires": { + "symbol-observable": "1.2.0" } }, "indexof": { @@ -7214,31 +5898,58 @@ "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" }, "inquirer": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz", - "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.2.tgz", + "integrity": "sha512-Z2rREiXA6cHRR9KBOarR3WuLlFzlIfAEIiB45ll5SSadMg7WqOh1MKEjjndfuH5ewXdixWCxqnVfGOQzPeiztA==", "dev": true, "requires": { - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.0", + "ansi-escapes": "^3.2.0", + "chalk": "^2.4.2", "cli-cursor": "^2.1.0", "cli-width": "^2.0.0", - "external-editor": "^2.0.4", + "external-editor": "^3.0.3", "figures": "^2.0.0", - "lodash": "^4.3.0", + "lodash": "^4.17.11", "mute-stream": "0.0.7", "run-async": "^2.2.0", - "rx-lite": "^4.0.8", - "rx-lite-aggregates": "^4.0.8", + "rxjs": "^6.4.0", "string-width": "^2.1.0", - "strip-ansi": "^4.0.0", + "strip-ansi": "^5.0.0", "through": "^2.3.6" + }, + "dependencies": { + "ansi-regex": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.0.0.tgz", + "integrity": "sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w==", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "strip-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.0.0.tgz", + "integrity": "sha512-Uu7gQyZI7J7gn5qLn1Np3G9vcYGTVqB+lFTytnDJv83dd8T22aGH451P3jueT2/QemInJDfxHB5Tde5OzgG1Ow==", + "dev": true, + "requires": { + "ansi-regex": "^4.0.0" + } + } } }, "interpret": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", - "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz", + "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==" }, "invariant": { "version": "2.2.4", @@ -7249,9 +5960,9 @@ } }, "invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", "dev": true }, "ipaddr.js": { @@ -7306,15 +6017,6 @@ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" }, - "is-builtin-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", - "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", - "dev": true, - "requires": { - "builtin-modules": "^1.0.0" - } - }, "is-callable": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", @@ -7401,11 +6103,6 @@ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, - "is-function": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.1.tgz", - "integrity": "sha1-Es+5i2W1fdPRk6MSH19uL0N2ArU=" - }, "is-glob": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", @@ -7464,40 +6161,10 @@ }, "is-obj": { "version": "1.0.1", - "resolved": "http://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", "dev": true }, - "is-odd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-odd/-/is-odd-2.0.0.tgz", - "integrity": "sha512-OTiixgpZAT1M4NHgS5IguFp/Vz2VI3U7Goh4/HA1adtwyLtSBrxYlcSYkhpAE07s4fKEcjrFxyvtQBND4vFQyQ==", - "requires": { - "is-number": "^4.0.0" - }, - "dependencies": { - "is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==" - } - } - }, - "is-path-cwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", - "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", - "dev": true - }, - "is-path-in-cwd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", - "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", - "dev": true, - "requires": { - "is-path-inside": "^1.0.0" - } - }, "is-path-inside": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", @@ -7548,12 +6215,6 @@ "is-unc-path": "^1.0.0" } }, - "is-resolvable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", - "dev": true - }, "is-retry-allowed": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz", @@ -7636,9 +6297,9 @@ "integrity": "sha512-yynBb1g+RFUPY64fTrFv7nsjRrENBQJaX2UL+2Szc9REFrSNm1rpSXHGzhmAy7a9uv3vlvgBlXnf9RqmPH1/DA==" }, "js-levenshtein": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.4.tgz", - "integrity": "sha512-PxfGzSs0ztShKrUYPIn5r0MtyAhYcCwmndozzpz8YObbPnD1jFxzlBGbRnX2mIu6Z13xN6+PTu05TQFnZFlzow==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", + "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==", "dev": true }, "js-stringify": { @@ -7647,14 +6308,14 @@ "integrity": "sha1-Fzb939lyTyijaCrcYjCufk6Weds=" }, "js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "js-yaml": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.11.0.tgz", - "integrity": "sha512-saJstZWv7oNeOyBh3+Dx1qWzhW0+e6/8eDzo7p5rDFqxntSztloLtuKu+Ejhtq82jsilwOIZYsCz+lIjthg1Hw==", + "version": "3.12.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.2.tgz", + "integrity": "sha512-QHn/Lh/7HhZ/Twc7vJYQTkjuCa0kaCcDcjK5Zlk2rvnUpy7DxMJ23+Jc2dcyvltwQVg1nygAVlB2oRDFHoRS5Q==", "dev": true, "requires": { "argparse": "^1.0.7", @@ -7761,9 +6422,9 @@ } }, "jsesc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.1.tgz", - "integrity": "sha1-5CGiqOINawgZ3yiQj3glJrlt0f4=", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", "dev": true }, "json-parse-better-errors": { @@ -7773,17 +6434,9 @@ "dev": true }, "json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" - }, - "json-stable-stringify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", - "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", - "requires": { - "jsonify": "~0.0.0" - } + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -7792,21 +6445,26 @@ "dev": true }, "json5": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", - "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=" - }, - "jsonify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", - "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "requires": { + "minimist": "^1.2.0" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + } + } }, "jsonwebtoken": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.4.0.tgz", - "integrity": "sha512-coyXjRTCy0pw5WYBpMvWOMN+Kjaik2MwTUIq9cna/W7NpO9E+iYbumZONAz3hcr+tXFJECoQVrtmIoC3Oz0gvg==", + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.0.tgz", + "integrity": "sha512-IqEycp0znWHNA11TpYi77bVgyBO/pGESDh7Ajhas+u0ttkGkKYIIAjniL4Bw5+oVejVF+SYkaI7XKfwCCyeTuA==", "requires": { - "jws": "^3.1.5", + "jws": "^3.2.1", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", @@ -7814,7 +6472,8 @@ "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", - "ms": "^2.1.1" + "ms": "^2.1.1", + "semver": "^5.6.0" }, "dependencies": { "ms": { @@ -7852,52 +6511,11 @@ "hyphenate-style-name": "^1.0.2" } }, - "jss-compose": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/jss-compose/-/jss-compose-5.0.0.tgz", - "integrity": "sha512-YofRYuiA0+VbeOw0VjgkyO380sA4+TWDrW52nSluD9n+1FWOlDzNbgpZ/Sb3Y46+DcAbOS21W5jo6SAqUEiuwA==", - "requires": { - "warning": "^3.0.0" - }, - "dependencies": { - "warning": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz", - "integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=", - "requires": { - "loose-envify": "^1.0.0" - } - } - } - }, "jss-default-unit": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/jss-default-unit/-/jss-default-unit-8.0.2.tgz", "integrity": "sha512-WxNHrF/18CdoAGw2H0FqOEvJdREXVXLazn7PQYU7V6/BWkCV0GkmWsppNiExdw8dP4TU1ma1dT9zBNJ95feLmg==" }, - "jss-expand": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/jss-expand/-/jss-expand-5.3.0.tgz", - "integrity": "sha512-NiM4TbDVE0ykXSAw6dfFmB1LIqXP/jdd0ZMnlvlGgEMkMt+weJIl8Ynq1DsuBY9WwkNyzWktdqcEW2VN0RAtQg==" - }, - "jss-extend": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jss-extend/-/jss-extend-6.2.0.tgz", - "integrity": "sha512-YszrmcB6o9HOsKPszK7NeDBNNjVyiW864jfoiHoMlgMIg2qlxKw70axZHqgczXHDcoyi/0/ikP1XaHDPRvYtEA==", - "requires": { - "warning": "^3.0.0" - }, - "dependencies": { - "warning": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz", - "integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=", - "requires": { - "loose-envify": "^1.0.0" - } - } - } - }, "jss-global": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/jss-global/-/jss-global-3.0.0.tgz", @@ -7921,46 +6539,11 @@ } } }, - "jss-preset-default": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/jss-preset-default/-/jss-preset-default-4.5.0.tgz", - "integrity": "sha512-qZbpRVtHT7hBPpZEBPFfafZKWmq3tA/An5RNqywDsZQGrlinIF/mGD9lmj6jGqu8GrED2SMHZ3pPKLmjCZoiaQ==", - "requires": { - "jss-camel-case": "^6.1.0", - "jss-compose": "^5.0.0", - "jss-default-unit": "^8.0.2", - "jss-expand": "^5.3.0", - "jss-extend": "^6.2.0", - "jss-global": "^3.0.0", - "jss-nested": "^6.0.1", - "jss-props-sort": "^6.0.0", - "jss-template": "^1.0.1", - "jss-vendor-prefixer": "^7.0.0" - } - }, "jss-props-sort": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/jss-props-sort/-/jss-props-sort-6.0.0.tgz", "integrity": "sha512-E89UDcrphmI0LzmvYk25Hp4aE5ZBsXqMWlkFXS0EtPkunJkRr+WXdCNYbXbksIPnKlBenGB9OxzQY+mVc70S+g==" }, - "jss-template": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/jss-template/-/jss-template-1.0.1.tgz", - "integrity": "sha512-m5BqEWha17fmIVXm1z8xbJhY6GFJxNB9H68GVnCWPyGYfxiAgY9WTQyvDAVj+pYRgrXSOfN5V1T4+SzN1sJTeg==", - "requires": { - "warning": "^3.0.0" - }, - "dependencies": { - "warning": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz", - "integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=", - "requires": { - "loose-envify": "^1.0.0" - } - } - } - }, "jss-vendor-prefixer": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/jss-vendor-prefixer/-/jss-vendor-prefixer-7.0.0.tgz", @@ -7979,29 +6562,24 @@ } }, "jwa": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.1.6.tgz", - "integrity": "sha512-tBO/cf++BUsJkYql/kBbJroKOgHWEigTKBAjjBEmrMGYd1QMBC74Hr4Wo2zCZw6ZrVhlJPvoMrkcOnlWR/DJfw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.0.tgz", + "integrity": "sha512-mt6IHaq0ZZWDBspg0Pheu3r9sVNMEZn+GJe1zcdYyhFcDSclp3J8xEdO4PjZolZ2i8xlaVU1LetHM0nJejYsEw==", "requires": { "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.10", + "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "jws": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.1.5.tgz", - "integrity": "sha512-GsCSexFADNQUr8T5HPJvayTjvPIfoyJPtLQBwn5a4WZQchcrPMPMAWcC1AzJVRDKyD6ZPROPAxgv6rfHViO4uQ==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.1.tgz", + "integrity": "sha512-bGA2omSrFUkd72dhh05bIAN832znP4wOU3lfuXtRBuGTbsmNmDXMQg28f0Vsxaxgk4myF5YkKQpz6qeRpMgX9g==", "requires": { - "jwa": "^1.1.5", + "jwa": "^1.2.0", "safe-buffer": "^5.0.1" } }, - "keycode": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.0.tgz", - "integrity": "sha1-PQr1bce4uOXLqNCpfxByBO7CKwQ=" - }, "kind-of": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", @@ -8017,19 +6595,20 @@ } }, "knex": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/knex/-/knex-0.15.2.tgz", - "integrity": "sha1-YFm4dIlgX0zIdZmm0qnSZXCek0A=", - "requires": { - "babel-runtime": "^6.26.0", - "bluebird": "^3.5.1", - "chalk": "2.3.2", - "commander": "^2.16.0", - "debug": "3.1.0", + "version": "0.16.3", + "resolved": "https://registry.npmjs.org/knex/-/knex-0.16.3.tgz", + "integrity": "sha512-jGTOBW8b7exaBPfCKJSlv5q320IvWw9hEdtnURtbb0k3HusfZrR4UYiEewem8Nl7VqJILoCj99SjCK3W54UNPg==", + "requires": { + "@babel/polyfill": "^7.0.0", + "@types/bluebird": "^3.5.25", + "bluebird": "^3.5.3", + "chalk": "2.4.1", + "commander": "^2.19.0", + "debug": "4.1.0", "inherits": "~2.0.3", "interpret": "^1.1.0", "liftoff": "2.5.0", - "lodash": "^4.17.10", + "lodash": "^4.17.11", "minimist": "1.2.0", "mkdirp": "^0.5.1", "pg-connection-string": "2.0.0", @@ -8039,14 +6618,12 @@ "v8flags": "^3.1.1" }, "dependencies": { - "chalk": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", - "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", + "debug": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz", + "integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==", "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "ms": "^2.1.1" } }, "minimist": { @@ -8054,10 +6631,10 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" }, - "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" } } }, @@ -8076,12 +6653,12 @@ "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=" }, "lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", "dev": true, "requires": { - "invert-kv": "^1.0.0" + "invert-kv": "^2.0.0" } }, "ldap-escape": { @@ -8109,35 +6686,21 @@ } }, "ldapauth-fork": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ldapauth-fork/-/ldapauth-fork-4.1.0.tgz", - "integrity": "sha512-OFz3aJDqYGpgo96gVBKhaIFIO/mc9OFpn9IdINHo22eUmF0leU836HgksIQQ3Ga0Mz48xwEhSZ/rJlWOzaUXJQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/ldapauth-fork/-/ldapauth-fork-4.2.0.tgz", + "integrity": "sha512-DFYhOO9UPX/fIUnwAjJ4zCq2osR3pyS9TK24oBRLHj7+iw9OAbckHLcdDkgI//IajUEsF6Ngz0uXod6A88L2HA==", "requires": { "@types/ldapjs": "^1.0.0", "@types/node": "^10.12.12", "bcryptjs": "^2.4.0", "ldapjs": "^1.0.2", - "lru-cache": "^5.1.1", - "moment": "^2.22.2" + "lru-cache": "^5.1.1" }, "dependencies": { - "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "requires": { - "yallist": "^3.0.2" - } - }, - "moment": { - "version": "2.23.0", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.23.0.tgz", - "integrity": "sha512-3IE39bHVqFbWWaPOMHZF98Q9c3LDKGTmypMiTM2QygGXXElkFWIH7GxfmlwmY2vwa+wmNsoYZmG2iusf1ZjJoA==" - }, - "yallist": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", - "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==" + "@types/node": { + "version": "10.12.29", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.29.tgz", + "integrity": "sha512-J/tnbnj8HcsBgCe2apZbdUpQ7hs4d7oZNTYA5bekWdP0sr2NGsOpI/HRdDroEi209tEvTcTtxhD0FfED3DhEcw==" } } }, @@ -8255,27 +6818,27 @@ } }, "loader-runner": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.3.1.tgz", - "integrity": "sha512-By6ZFY7ETWOc9RFaAIb23IjJVcM4dvJC/N57nmdz9RSkMXvAXGI7SyVlAw3v8vjtDRlqThgVDVmTnr9fqMlxkw==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", + "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==", "dev": true }, "loader-utils": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.1.0.tgz", - "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", + "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", "requires": { - "big.js": "^3.1.3", + "big.js": "^5.2.2", "emojis-list": "^2.0.0", - "json5": "^0.5.0" + "json5": "^1.0.1" } }, "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "requires": { - "p-locate": "^2.0.0", + "p-locate": "^3.0.0", "path-exists": "^3.0.0" } }, @@ -8284,23 +6847,12 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" }, - "lodash-es": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.11.tgz", - "integrity": "sha512-DHb1ub+rMjjrxqlB3H56/6MXtm1lSksDp2rA2cNWjG8mlDUYFhUj3Di2Zn5IwSU87xLv8tNIQ7sSwE/YOX/D/Q==" - }, "lodash.camelcase": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=", "dev": true }, - "lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", - "dev": true - }, "lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -8359,6 +6911,20 @@ "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", "integrity": "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=" }, + "lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=" + }, + "log-symbols": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", + "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "dev": true, + "requires": { + "chalk": "^2.0.1" + } + }, "long": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", @@ -8399,12 +6965,11 @@ "dev": true }, "lru-cache": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.2.tgz", - "integrity": "sha512-wgeVXhrDwAWnIF/yZARsFnMBtdFXOg1b8RIrhilp+0iDYN4mdQcNZElDZ0e4B64BhaxeQ5zN7PMyvu7we1kPeQ==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" + "yallist": "^3.0.2" } }, "make-dir": { @@ -8415,6 +6980,12 @@ "pify": "^3.0.0" } }, + "make-error": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz", + "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==", + "dev": true + }, "make-iterator": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", @@ -8423,10 +6994,16 @@ "kind-of": "^6.0.2" } }, + "mamacro": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/mamacro/-/mamacro-0.0.3.tgz", + "integrity": "sha512-qMEwh+UujcQ+kbz3T6V+wAmO2U8veoq2w+3wY8MquqwVA3jChfwY+Tk52GZKDfACEPjuZ7r2oJLejwpt8jtwTA==", + "dev": true + }, "map-age-cleaner": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.2.tgz", - "integrity": "sha512-UN1dNocxQq44IhJyMI4TU8phc2m9BddacHRPRjKGLYaF0jqd3xLz0jS0skpAU9WgYyoR4gHtUpzytNBS385FWQ==", + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", "dev": true, "requires": { "p-defer": "^1.0.0" @@ -8446,9 +7023,9 @@ } }, "marked": { - "version": "0.3.17", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.17.tgz", - "integrity": "sha512-+AKbNsjZl6jFfLPwHhWmGTqE009wTKn3RTmn9K8oUKHrX/abPJjtcRtXpYB/FFrwPJRUA86LX/de3T0knkPCmQ==", + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.19.tgz", + "integrity": "sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==", "dev": true }, "md5.js": { @@ -8460,14 +7037,6 @@ "hash-base": "^3.0.0", "inherits": "^2.0.1", "safe-buffer": "^5.1.2" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - } } }, "mdurl": { @@ -8481,12 +7050,14 @@ "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" }, "mem": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", - "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.1.0.tgz", + "integrity": "sha512-I5u6Q1x7wxO0kdOpYBB28xueHADYps5uty/zg936CiG8NTe5sJL8EjrCuLneuDW3PlMdZBGDIn8BirEVdovZvg==", "dev": true, "requires": { - "mimic-fn": "^1.0.0" + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^1.0.0", + "p-is-promise": "^2.0.0" } }, "memory-fs": { @@ -8545,16 +7116,16 @@ "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" }, "mime-db": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", - "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==" + "version": "1.38.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz", + "integrity": "sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg==" }, "mime-types": { - "version": "2.1.18", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", - "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "version": "2.1.22", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.22.tgz", + "integrity": "sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog==", "requires": { - "mime-db": "~1.33.0" + "mime-db": "~1.38.0" } }, "mimic-fn": { @@ -8589,9 +7160,9 @@ "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" }, "mississippi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-2.0.0.tgz", - "integrity": "sha512-zHo8v+otD1J10j/tC+VNoGK9keCuByhKovAvdn74dmxJl9+mWHnx6EMsDN4lgRoMI/eYo2nchAxniIbUPb5onw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", + "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", "requires": { "concat-stream": "^1.5.0", "duplexify": "^3.4.2", @@ -8599,7 +7170,7 @@ "flush-write-stream": "^1.0.0", "from2": "^2.1.0", "parallel-transform": "^1.1.0", - "pump": "^2.0.1", + "pump": "^3.0.0", "pumpify": "^1.3.3", "stream-each": "^1.1.0", "through2": "^2.0.0" @@ -8619,29 +7190,139 @@ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", "requires": { - "is-plain-object": "^2.0.4" + "is-plain-object": "^2.0.4" + } + } + } + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "0.0.8" + } + }, + "mkdirp2": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp2/-/mkdirp2-1.0.4.tgz", + "integrity": "sha512-Q2PKB4ZR4UPtjLl76JfzlgSCUZhSV1AXQgAZa1qt5RiaALFjP/CDrGvFBrOz7Ck6McPcwMAxTsJvWOUjOU8XMw==", + "dev": true + }, + "mocha": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.0.2.tgz", + "integrity": "sha512-RtTJsmmToGyeTznSOMoM6TPEk1A84FQaHIciKrRqARZx+B5ccJ5tXlmJzEKGBxZdqk9UjpRsesZTUkZmR5YnuQ==", + "dev": true, + "requires": { + "ansi-colors": "3.2.3", + "browser-stdout": "1.3.1", + "debug": "3.2.6", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "findup-sync": "2.0.0", + "glob": "7.1.3", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "3.12.0", + "log-symbols": "2.2.0", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "ms": "2.1.1", + "node-environment-flags": "1.0.4", + "object.assign": "4.1.0", + "strip-json-comments": "2.0.1", + "supports-color": "6.0.0", + "which": "1.3.1", + "wide-align": "1.1.3", + "yargs": "12.0.5", + "yargs-parser": "11.1.1", + "yargs-unparser": "1.5.0" + }, + "dependencies": { + "ansi-colors": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", + "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", + "dev": true + }, + "cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "dev": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + } + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "js-yaml": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz", + "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "supports-color": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", + "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "yargs": { + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", + "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", + "dev": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^11.1.1" } } } }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "requires": { - "minimist": "0.0.8" - } - }, - "mkdirp2": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp2/-/mkdirp2-1.0.4.tgz", - "integrity": "sha512-Q2PKB4ZR4UPtjLl76JfzlgSCUZhSV1AXQgAZa1qt5RiaALFjP/CDrGvFBrOz7Ck6McPcwMAxTsJvWOUjOU8XMw==", + "mocha-graphql-register": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mocha-graphql-register/-/mocha-graphql-register-1.0.0.tgz", + "integrity": "sha1-FALzIKxAFLxoya196htZsLe4pp8=", "dev": true }, "moment": { - "version": "2.21.0", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.21.0.tgz", - "integrity": "sha512-TCZ36BjURTeFTM/CwRcViQlfkMvL1/vFISuNLO5GkcVm1+QHfbSiNqZuWeMFjj1/3+uAjXswgRk30j1kkLYJBQ==", + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", + "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==", "optional": true }, "morgan": { @@ -8654,16 +7335,6 @@ "depd": "~1.1.2", "on-finished": "~2.3.0", "on-headers": "~1.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - } } }, "move-concurrently": { @@ -8726,22 +7397,21 @@ } }, "nan": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.9.2.tgz", - "integrity": "sha512-ltW65co7f3PQWBDbqVvaU1WtFJUsNW7sWWm4HINhbMQIyVyzIeyZ8toX5TC5eeooE6piZoaEh4cZkueSKG3KYw==", + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.12.1.tgz", + "integrity": "sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw==", "optional": true }, "nanomatch": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.9.tgz", - "integrity": "sha512-n8R9bS8yQ6eSXaV6jHUpKzD8gLsin02w1HSFiegwrs9E098Ylhw5jdyKPaYqvHknHaSCKTPp7C8dGCQ0q9koXA==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", "requires": { "arr-diff": "^4.0.0", "array-unique": "^0.3.2", "define-property": "^2.0.2", "extend-shallow": "^3.0.2", "fragment-cache": "^0.2.1", - "is-odd": "^2.0.0", "is-windows": "^1.0.2", "kind-of": "^6.0.2", "object.pick": "^1.3.0", @@ -8788,16 +7458,24 @@ "lower-case": "^1.1.1" } }, + "node-environment-flags": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.4.tgz", + "integrity": "sha512-M9rwCnWVLW7PX+NUWe3ejEdiLYinRpsEre9hMkU/6NS4h+EEulYaDH1gCEZ2gyXsmw+RXYDaV2JkkTNcsPDJ0Q==", + "dev": true, + "requires": { + "object.getownpropertydescriptors": "^2.0.3" + } + }, "node-fetch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.0.0.tgz", - "integrity": "sha1-mCu6Q+zU8pIqKcwYamu7C7c/y6Y=", - "dev": true + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.3.0.tgz", + "integrity": "sha512-MOd8pV3fxENbryESLgVIeaGKrdl+uaYhCSSVkjeOb/31/njTpcis5aWfdqgNlHIrKOLRbMnfPINPOML2CIFeXA==" }, "node-libs-browser": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.1.0.tgz", - "integrity": "sha512-5AzFzdoIMb89hBGMZglEegffzgRg+ZFoUmisQ8HI4j1KDdpx13J0taNp2y9xPbur6W61gepGDDotGBVQ7mfUCg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.0.tgz", + "integrity": "sha512-5MQunG/oyOaBdttrL40dA7bUfPORLRWMUJLQtMg7nluxUvk5XwnLdL9twQHFAjRx/y7mIMkLKT9++qPbbk6BZA==", "dev": true, "requires": { "assert": "^1.1.1", @@ -8807,7 +7485,7 @@ "constants-browserify": "^1.0.0", "crypto-browserify": "^3.11.0", "domain-browser": "^1.1.1", - "events": "^1.0.0", + "events": "^3.0.0", "https-browserify": "^1.0.0", "os-browserify": "^0.3.0", "path-browserify": "0.0.0", @@ -8821,35 +7499,69 @@ "timers-browserify": "^2.0.4", "tty-browserify": "0.0.0", "url": "^0.11.0", - "util": "^0.10.3", + "util": "^0.11.0", "vm-browserify": "0.0.4" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + }, + "util": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", + "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", + "dev": true, + "requires": { + "inherits": "2.0.3" + } + } } }, "node-releases": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.1.tgz", - "integrity": "sha512-2UXrBr6gvaebo5TNF84C66qyJJ6r0kxBObgZIDX3D3/mt1ADKiHux3NJPWisq0wxvJJdkjECH+9IIKYViKj71Q==", + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.9.tgz", + "integrity": "sha512-oic3GT4OtbWWKfRolz5Syw0Xus0KRFxeorLNj0s93ofX6PWyuzKjsiGxsCtWktBwwmTF6DdRRf2KreGqeOk5KA==", "dev": true, "requires": { "semver": "^5.3.0" } }, "nodemon": { - "version": "1.18.8", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-1.18.8.tgz", - "integrity": "sha512-CgC/JdCf+CT7Z+K6wWaV30t8GU1DPtXpr/6PuXC1/LboXCmUQNKOaz0AEMjoWDTt2AdHOBFxgv41dyC0i79SbA==", + "version": "1.18.10", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-1.18.10.tgz", + "integrity": "sha512-we51yBb1TfEvZamFchRgcfLbVYgg0xlGbyXmOtbBzDwxwgewYS/YbZ5tnlnsH51+AoSTTsT3A2E/FloUbtH8cQ==", "dev": true, "requires": { - "chokidar": "^2.0.4", + "chokidar": "^2.1.0", "debug": "^3.1.0", "ignore-by-default": "^1.0.1", "minimatch": "^3.0.4", - "pstree.remy": "^1.1.3", + "pstree.remy": "^1.1.6", "semver": "^5.5.0", "supports-color": "^5.2.0", "touch": "^3.1.0", "undefsafe": "^2.0.2", "update-notifier": "^2.5.0" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } } }, "nopt": { @@ -8862,25 +7574,21 @@ } }, "normalize-package-data": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", "dev": true, "requires": { "hosted-git-info": "^2.1.4", - "is-builtin-module": "^1.0.0", + "resolve": "^1.10.0", "semver": "2 || 3 || 4 || 5", "validate-npm-package-license": "^3.0.1" } }, "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" }, "normalize-scroll-left": { "version": "0.1.2", @@ -8942,15 +7650,15 @@ "dev": true }, "object-hash": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-1.3.0.tgz", - "integrity": "sha512-05KzQ70lSeGSrZJQXE5wNDiTkBJDlUT/myi6RX9dVIvz7a7Qh4oH93BQdiPMn27nldYvVQCKMUaM83AfizZlsQ==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-1.3.1.tgz", + "integrity": "sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA==", "dev": true }, "object-keys": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz", - "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.0.tgz", + "integrity": "sha512-6OO5X1+2tYkNyNEx6TsCxEqFfRWaqx6EtMiSbGrw8Ob8v9Ne+Hl8rBAgLBZn5wjEz3s/s6U1WXFUFOcxxAwUpg==" }, "object-path": { "version": "0.11.4", @@ -8971,6 +7679,18 @@ "isobject": "^3.0.0" } }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, "object.defaults": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", @@ -9017,9 +7737,9 @@ } }, "on-headers": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz", - "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" }, "once": { "version": "1.4.0", @@ -9046,14 +7766,6 @@ "requires": { "minimist": "~0.0.1", "wordwrap": "~0.0.2" - }, - "dependencies": { - "wordwrap": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", - "dev": true - } } }, "optionator": { @@ -9068,6 +7780,14 @@ "prelude-ls": "~1.1.2", "type-check": "~0.3.2", "wordwrap": "~1.0.0" + }, + "dependencies": { + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + } } }, "os-browserify": { @@ -9082,14 +7802,14 @@ "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" }, "os-locale": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", - "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", "dev": true, "requires": { - "execa": "^0.7.0", - "lcid": "^1.0.0", - "mem": "^1.1.0" + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" } }, "os-tmpdir": { @@ -9122,31 +7842,31 @@ "dev": true }, "p-is-promise": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz", - "integrity": "sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.0.0.tgz", + "integrity": "sha512-pzQPhYMCAgLAKPWD2jC3Se9fEfrD9npNos0y150EeqZll7akhEgGhTW/slB6lHku8AvYGiJ+YJ5hfHKePPgFWg==", "dev": true }, "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", "requires": { - "p-try": "^1.0.0" + "p-try": "^2.0.0" } }, "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "requires": { - "p-limit": "^1.1.0" + "p-limit": "^2.0.0" } }, "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", + "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==" }, "package-json": { "version": "4.0.1", @@ -9173,14 +7893,14 @@ } }, "packet-reader": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-0.3.1.tgz", - "integrity": "sha1-zWLmCvjX/qinBexP+ZCHHEaHHyc=" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", + "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" }, "pako": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.7.tgz", - "integrity": "sha512-3HNK5tW4x8o5mO8RuHZp3Ydw9icZXx0RANAOMzlMzx7LVXhMJ4mo3MOBpzyd7r/+RUu8BmndP47LXT+vzjtWcQ==", + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.10.tgz", + "integrity": "sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw==", "dev": true }, "parallel-transform": { @@ -9202,17 +7922,27 @@ "no-case": "^2.2.0" } }, + "parent-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.0.tgz", + "integrity": "sha512-8Mf5juOMmiE4FcmzYc4IaiS9L3+9paz2KOiXzkRviCP6aDmN49Hz6EMWz0lGNp9pX80GvvAuLADtyGfW/Em3TA==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, "parse-asn1": { - "version": "5.1.1", - "resolved": "http://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz", - "integrity": "sha512-KPx7flKXg775zZpnp9SxJlz00gTd4BmJ2yJufSc44gMCRrRQ7NSzAcSJQfifuOLgW6bEi+ftrALtsgALeB2Adw==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.4.tgz", + "integrity": "sha512-Qs5duJcuvNExRfFZ99HDD3z4mAi3r9Wl/FOjEOijlxwCZs7E7mW2vjTpgQ4J8LpTF8x5v+1Vn5UQFejmWT11aw==", "dev": true, "requires": { "asn1.js": "^4.0.0", "browserify-aes": "^1.0.0", "create-hash": "^1.1.0", "evp_bytestokey": "^1.0.0", - "pbkdf2": "^3.0.3" + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" } }, "parse-filepath": { @@ -9269,14 +7999,21 @@ } }, "passport-ldapauth": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/passport-ldapauth/-/passport-ldapauth-2.1.1.tgz", - "integrity": "sha512-DiK9nwZthdCeZE+TRx2AzRk9mg8OeAz4+tZdXC8EPVAVmeW7YSWyK4XCJ8/B7ySWpEZtrN1OcrKtWjpLXFy0/A==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/passport-ldapauth/-/passport-ldapauth-2.1.2.tgz", + "integrity": "sha512-V+oYNhJwW/ncYHS1IX3eVN9TUH38OsD15Cwo8w+FIV+GXKFJRtUhZyt5nNwExyU0ujMya8Rtzm04+4d8BjvBUg==", "requires": { - "@types/node": "^10.12.12", - "@types/passport": "^0.4.7", - "ldapauth-fork": "^4.1.0", + "@types/node": "^10.12.26", + "@types/passport": "^1.0.0", + "ldapauth-fork": "^4.2.0", "passport-strategy": "^1.0.0" + }, + "dependencies": { + "@types/node": { + "version": "10.12.29", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.29.tgz", + "integrity": "sha512-J/tnbnj8HcsBgCe2apZbdUpQ7hs4d7oZNTYA5bekWdP0sr2NGsOpI/HRdDroEi209tEvTcTtxhD0FfED3DhEcw==" + } } }, "passport-strategy": { @@ -9295,7 +8032,7 @@ }, "path-browserify": { "version": "0.0.0", - "resolved": "http://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz", "integrity": "sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo=", "dev": true }, @@ -9337,9 +8074,9 @@ "dev": true }, "path-parse": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", - "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=" + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" }, "path-root": { "version": "0.1.1", @@ -9367,6 +8104,12 @@ "pify": "^3.0.0" } }, + "pathval": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", + "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", + "dev": true + }, "pause": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", @@ -9386,15 +8129,15 @@ } }, "pg": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/pg/-/pg-7.7.1.tgz", - "integrity": "sha512-p3I0mXOmUvCoVlCMFW6iYSrnguPol6q8He15NGgSIdM3sPGjFc+8JGCeKclw8ZR4ETd+Jxy2KNiaPUcocHZeMw==", + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/pg/-/pg-7.8.1.tgz", + "integrity": "sha512-m9aIrOV4mgfo+1Ze+eNoJwaWZDvpeBz8Kzwi0zzqLC+tQBsQgIuu+FGPqzyRv9HFlS7tHO1I33LKp9gP5g7U4Q==", "requires": { "buffer-writer": "2.0.0", - "packet-reader": "0.3.1", + "packet-reader": "1.0.0", "pg-connection-string": "0.1.3", "pg-pool": "^2.0.4", - "pg-types": "~1.12.1", + "pg-types": "~2.0.0", "pgpass": "1.x", "semver": "4.3.2" }, @@ -9416,17 +8159,23 @@ "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.0.0.tgz", "integrity": "sha1-Pu/lmX4G2Ugh5NUC5CtqHHP434I=" }, + "pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==" + }, "pg-pool": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-2.0.4.tgz", - "integrity": "sha512-Mi2AsmlFkVMpI28NreaDkz5DkfxLOG16C/HNwi091LDlOiDiQACtAroLxSd1vIS2imBqxdjjO9cQZg2CwsOPbw==" + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-2.0.6.tgz", + "integrity": "sha512-hod2zYQxM8Gt482q+qONGTYcg/qVcV32VHVPtktbBJs0us3Dj7xibISw0BAAXVMCzt8A/jhfJvpZaxUlqtqs0g==" }, "pg-types": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-1.12.1.tgz", - "integrity": "sha1-1kCH45A7WP+q0nnnWVxSIIoUw9I=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.0.0.tgz", + "integrity": "sha512-THUD7gQll5tys+5eQ8Rvs7DjHiIC3bLqixk3gMN9Hu8UrCBAOjf35FoI39rTGGc3lM2HU/R+Knpxvd11mCwOMA==", "requires": { - "postgres-array": "~1.0.0", + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", "postgres-bytea": "~1.0.0", "postgres-date": "~1.0.0", "postgres-interval": "^1.1.0" @@ -9461,23 +8210,17 @@ } }, "pkg-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", - "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", "requires": { - "find-up": "^2.1.0" + "find-up": "^3.0.0" } }, - "pluralize": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", - "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", - "dev": true - }, "popper.js": { - "version": "1.14.4", - "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.14.4.tgz", - "integrity": "sha1-juwdj/AqWjoVLdQ0FKFce3n9abY=" + "version": "1.14.7", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.14.7.tgz", + "integrity": "sha512-4q1hNvoUre/8srWsH7hnoSJ5xVmIL4qgz+s4qf2TnJIMyZFUFMGH+9vE7mXynAlHSZ/NdTmmow86muD0myUkVQ==" }, "posix-character-classes": { "version": "0.1.1", @@ -9485,9 +8228,9 @@ "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" }, "postgres-array": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-1.0.3.tgz", - "integrity": "sha512-5wClXrAP0+78mcsNX3/ithQ5exKvCyK5lr5NEEEeGwwM6NJdQgzIJBVxLvRW+huFpX92F2QnZ5CcokH0VhK2qQ==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==" }, "postgres-bytea": { "version": "1.0.0", @@ -9500,9 +8243,9 @@ "integrity": "sha1-4tiXAu/bJY/52c7g/pG9BpdSV6g=" }, "postgres-interval": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.1.2.tgz", - "integrity": "sha512-fC3xNHeTskCxL1dC8KOtxXt7YeFmlbTYtn7ul8MkVERuTmf7pI4DrkAxcw3kh1fQ9uz4wQmd03a1mRiXUZChfQ==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", "requires": { "xtend": "^4.0.0" } @@ -9541,9 +8284,9 @@ "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" }, "progress": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz", - "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true }, "promise": { @@ -9560,12 +8303,13 @@ "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=" }, "prop-types": { - "version": "15.6.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz", - "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==", + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", "requires": { - "loose-envify": "^1.3.1", - "object-assign": "^4.1.1" + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" } }, "protobufjs": { @@ -9586,6 +8330,13 @@ "@types/long": "^4.0.0", "@types/node": "^10.1.0", "long": "^4.0.0" + }, + "dependencies": { + "@types/node": { + "version": "10.12.29", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.29.tgz", + "integrity": "sha512-J/tnbnj8HcsBgCe2apZbdUpQ7hs4d7oZNTYA5bekWdP0sr2NGsOpI/HRdDroEi209tEvTcTtxhD0FfED3DhEcw==" + } } }, "proxy-addr": { @@ -9606,12 +8357,13 @@ "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true }, "pstree.remy": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.4.tgz", - "integrity": "sha512-3kSyTN/iTJMxtL87idnFgTyOp2vQ6B/49QcHUO26kh2M2qahlUivFI1zWJ9FRFPoB+KgcP820JMOuIhkBJAP3Q==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.6.tgz", + "integrity": "sha512-NdF35+QsqD7EgNEI5mkI/X+UwaxVEbQaz9f4IooEmMUv6ZPmlTQYGjBPJGgrlzNdjSvIy4MWMg6Q6vCgBO2K+w==", "dev": true }, "public-encrypt": { @@ -9626,14 +8378,6 @@ "parse-asn1": "^5.0.0", "randombytes": "^2.0.1", "safe-buffer": "^5.1.2" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - } } }, "pug": { @@ -9751,9 +8495,9 @@ "integrity": "sha1-wA1cUSi6xYBr7BXSt+fNq+QlMfM=" }, "pump": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", - "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", "requires": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -9767,13 +8511,23 @@ "duplexify": "^3.6.0", "inherits": "^2.0.3", "pump": "^2.0.0" + }, + "dependencies": { + "pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } } }, "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, "qs": { "version": "6.5.2", @@ -9798,9 +8552,9 @@ "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=" }, "randombytes": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.6.tgz", - "integrity": "sha512-CIQ5OFxf4Jou6uOKe9t1AOgqpeU5fd70A8NPdHSGeYXqXsPe6peOwI0cUl88RWZ6sP1vPMV3avd/R6cZ5/sP1A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "dev": true, "requires": { "safe-buffer": "^5.1.0" @@ -9821,6 +8575,35 @@ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" }, + "raw-body": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", + "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", + "requires": { + "bytes": "3.0.0", + "http-errors": "1.6.3", + "iconv-lite": "0.4.23", + "unpipe": "1.0.0" + }, + "dependencies": { + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + } + } + }, "rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -9835,55 +8618,48 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } } }, "react": { - "version": "16.6.3", - "resolved": "https://registry.npmjs.org/react/-/react-16.6.3.tgz", - "integrity": "sha512-zCvmH2vbEolgKxtqXL2wmGCUxUyNheYn/C+PD1YAjfxHC54+MhdruyhO7QieQrYsYeTxrn93PM2y0jRH1zEExw==", + "version": "16.8.3", + "resolved": "https://registry.npmjs.org/react/-/react-16.8.3.tgz", + "integrity": "sha512-3UoSIsEq8yTJuSu0luO1QQWYbgGEILm+eJl2QN/VLDi7hL+EN18M3q3oVZwmVzzBJ3DkM7RMdRwBmZZ+b4IzSA==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", "prop-types": "^15.6.2", - "scheduler": "^0.11.2" + "scheduler": "^0.13.3" } }, "react-dom": { - "version": "16.6.3", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.6.3.tgz", - "integrity": "sha512-8ugJWRCWLGXy+7PmNh8WJz3g1TaTUt1XyoIcFN+x0Zbkoz+KKdUyx1AQLYJdbFXjuF41Nmjn5+j//rxvhFjgSQ==", + "version": "16.8.3", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.8.3.tgz", + "integrity": "sha512-ttMem9yJL4/lpItZAQ2NTFAbV7frotHk5DZEHXUOws2rMmrsvh1Na7ThGT0dTzUIl6pqTOi5tYREfL8AEna3lA==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", "prop-types": "^15.6.2", - "scheduler": "^0.11.2" + "scheduler": "^0.13.3" } }, "react-event-listener": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/react-event-listener/-/react-event-listener-0.6.4.tgz", - "integrity": "sha512-t7VSjIuUFmN+GeyKb+wm025YLeojVB85kJL6sSs0wEBJddfmKBEQz+CNBZ2zBLKVWkPy/fZXM6U5yvojjYBVYQ==", + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/react-event-listener/-/react-event-listener-0.6.6.tgz", + "integrity": "sha512-+hCNqfy7o9wvO6UgjqFmBzARJS7qrNoda0VqzvOuioEpoEXKutiKuv92dSz6kP7rYLmyHPyYNLesi5t/aH1gfw==", "requires": { - "@babel/runtime": "7.0.0", + "@babel/runtime": "^7.2.0", "prop-types": "^15.6.0", "warning": "^4.0.1" } }, - "react-jss": { - "version": "8.6.1", - "resolved": "https://registry.npmjs.org/react-jss/-/react-jss-8.6.1.tgz", - "integrity": "sha512-SH6XrJDJkAphp602J14JTy3puB2Zxz1FkM3bKVE8wON+va99jnUTKWnzGECb3NfIn9JPR5vHykge7K3/A747xQ==", - "requires": { - "hoist-non-react-statics": "^2.5.0", - "jss": "^9.7.0", - "jss-preset-default": "^4.3.0", - "prop-types": "^15.6.0", - "theming": "^1.3.0" - } + "react-is": { + "version": "16.8.3", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.3.tgz", + "integrity": "sha512-Y4rC1ZJmsxxkkPuMLwvKvlL1Zfpbcu+Bf4ZigkHup3v9EfdYhAlWAaVyA19olXq2o2mGn0w+dFKvk3pVVlYcIA==" }, "react-lifecycles-compat": { "version": "3.0.4", @@ -9891,22 +8667,23 @@ "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" }, "react-redux": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-5.0.7.tgz", - "integrity": "sha512-5VI8EV5hdgNgyjfmWzBbdrqUkrVRKlyTKk1sGH3jzM2M2Mhj/seQgPXaz6gVAj2lz/nz688AdTqMO18Lr24Zhg==", - "requires": { - "hoist-non-react-statics": "^2.5.0", - "invariant": "^2.0.0", - "lodash": "^4.17.5", - "lodash-es": "^4.17.5", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-5.1.1.tgz", + "integrity": "sha512-LE7Ned+cv5qe7tMV5BPYkGQ5Lpg8gzgItK07c67yHvJ8t0iaD9kPFPAli/mYkiyJYrs2pJgExR2ZgsGqlrOApg==", + "requires": { + "@babel/runtime": "^7.1.2", + "hoist-non-react-statics": "^3.1.0", + "invariant": "^2.2.4", "loose-envify": "^1.1.0", - "prop-types": "^15.6.0" + "prop-types": "^15.6.1", + "react-is": "^16.6.0", + "react-lifecycles-compat": "^3.0.0" } }, "react-transition-group": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.5.0.tgz", - "integrity": "sha512-qYB3JBF+9Y4sE4/Mg/9O6WFpdoYjeeYqx0AFb64PTazVy8RPMiE3A47CG9QmM4WJ/mzDiZYslV+Uly6O1Erlgw==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.6.0.tgz", + "integrity": "sha512-VzZ+6k/adL3pJHo4PU/MHEPjW59/TGQtRsXC+wnxsx2mxjQKNHnDdJL/GpYuPJIsyHGjYbBQfIJ2JNOAdPc8GQ==", "requires": { "dom-helpers": "^3.3.1", "loose-envify": "^1.4.0", @@ -9950,19 +8727,64 @@ "requires": { "find-up": "^2.0.0", "read-pkg": "^2.0.0" + }, + "dependencies": { + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + } } }, "readable-stream": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.5.tgz", - "integrity": "sha512-tK0yDhrkygt/knjowCUiWP9YdV7c5R+8cR0r/kt9ZhBU906Fs6RpQJCEilamRJj1Nx2rWI6LkW9gKqjTkshhEw==", + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", - "string_decoder": "~1.0.3", + "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, @@ -9996,6 +8818,13 @@ "hoist-non-react-statics": "^2.3.1", "react-lifecycles-compat": "^3.0.2", "symbol-observable": "^1.0.4" + }, + "dependencies": { + "hoist-non-react-statics": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz", + "integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==" + } } }, "reduce-extract": { @@ -10071,11 +8900,11 @@ } }, "redux": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/redux/-/redux-4.0.0.tgz", - "integrity": "sha512-NnnHF0h0WVE/hXyrB6OlX67LYRuaf/rJcbWvnHHEPCF/Xa/AZpwhs/20WyqzQae5x4SD2F9nPObgBh2rxAgLiA==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.0.1.tgz", + "integrity": "sha512-R7bAtSkk7nY6O/OYMVR9RiBI+XghjF9rlbl5806HJbQph0LJVHZrU5oaO4q70eUKiqMRqm4y07KLTlMZ2BlVmg==", "requires": { - "loose-envify": "^1.1.0", + "loose-envify": "^1.4.0", "symbol-observable": "^1.2.0" } }, @@ -10095,14 +8924,14 @@ } }, "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", + "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==" }, "regenerator-transform": { - "version": "0.13.3", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.13.3.tgz", - "integrity": "sha512-5ipTrZFSq5vU2YoGoww4uaRVAK4wyYC4TSICibbfEPOruUu8FFP7ErV0BjmbIOEpn3O/k9na9UEdYR/3m7N6uA==", + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.13.4.tgz", + "integrity": "sha512-T0QMBjK3J0MtxjPmdIMXm72Wvj2Abb0Bd4HADdfijwMdoIsyQZ6fWC7kDFhk2YinBBEMZDL7Y7wh0J1sGx3S4A==", "dev": true, "requires": { "private": "^0.1.6" @@ -10117,10 +8946,16 @@ "safe-regex": "^1.1.0" } }, + "regexp-tree": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.5.tgz", + "integrity": "sha512-nUmxvfJyAODw+0B13hj8CFVAxhe7fDEAgJgaotBu3nnR+IgGgZq59YedJP5VYTlkEfqjuK6TuRpnymKdatLZfQ==", + "dev": true + }, "regexpp": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-1.1.0.tgz", - "integrity": "sha512-LOPw8FpgdQF9etWMaAfG/WRthIdXJGYp4mJ2Jgn/2lpkbod9jPn0t9UqN7AxBOKNfzRbYyVfgc7Vk4t/MpnXgw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", "dev": true }, "regexpu-core": { @@ -10173,7 +9008,7 @@ "dependencies": { "jsesc": { "version": "0.5.0", - "resolved": "http://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", "dev": true } @@ -10186,9 +9021,9 @@ "dev": true }, "repeat-element": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", - "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=" + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==" }, "repeat-string": { "version": "1.6.1", @@ -10207,16 +9042,6 @@ "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", "dev": true }, - "require-uncached": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", - "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", - "dev": true, - "requires": { - "caller-path": "^0.1.0", - "resolve-from": "^1.0.0" - } - }, "requizzle": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.1.tgz", @@ -10240,11 +9065,11 @@ "integrity": "sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA==" }, "resolve": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.5.0.tgz", - "integrity": "sha512-hgoSGrc3pjzAPHNBg+KnFcK2HwlHTs/YrAGUr6qgTVUZmXv1UEXXl0bZNBKMA9fud6lRYFdPGz0xXxycPzmmiw==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz", + "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==", "requires": { - "path-parse": "^1.0.5" + "path-parse": "^1.0.6" } }, "resolve-cwd": { @@ -10274,9 +9099,9 @@ } }, "resolve-from": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", - "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true }, "resolve-url": { @@ -10313,11 +9138,11 @@ } }, "rimraf": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", - "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", "requires": { - "glob": "^7.0.5" + "glob": "^7.1.3" } }, "ripemd160": { @@ -10347,30 +9172,24 @@ "aproba": "^1.1.1" } }, - "rx-lite": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz", - "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=", - "dev": true - }, - "rx-lite-aggregates": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz", - "integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=", + "rxjs": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz", + "integrity": "sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==", "dev": true, "requires": { - "rx-lite": "*" + "tslib": "^1.9.0" } }, "safe-buffer": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safe-json-stringify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.1.0.tgz", - "integrity": "sha512-EzBtUaFH9bHYPc69wqjp0efJI/DPNHdFbGE3uIMn4sVbO0zx8vZ8cG4WKxQfOpUOKsQyGBiT2mTqnCw+6nLswA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz", + "integrity": "sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==", "optional": true }, "safe-regex": { @@ -10387,20 +9206,21 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "scheduler": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.11.2.tgz", - "integrity": "sha512-+WCP3s3wOaW4S7C1tl3TEXp4l9lJn0ZK8G3W3WKRWmw77Z2cIFUW2MiNTMHn5sCjxN+t7N43HAOOgMjyAg5hlg==", + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.13.3.tgz", + "integrity": "sha512-UxN5QRYWtpR1egNWzJcVLk8jlegxAugswQc984lD3kU7NuobsO37/sRfbpTdBjtnD5TBNFA2Q2oLV5+UmPSmEQ==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" } }, "schema-utils": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.5.tgz", - "integrity": "sha512-yYrjb9TX2k/J1Y5UNy3KYdZq10xhYcF8nMpAW6o3hy6Q8WSIEf9lJHG/ePnOBfziPM3fvQwfOwa13U/Fh8qTfA==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", "requires": { "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", "ajv-keywords": "^3.1.0" } }, @@ -10410,10 +9230,9 @@ "integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=" }, "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", - "dev": true + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==" }, "semver-diff": { "version": "2.1.0", @@ -10444,13 +9263,26 @@ "statuses": "~1.4.0" }, "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", "requires": { - "ms": "2.0.0" + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" } + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + }, + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" } } }, @@ -10465,9 +9297,9 @@ } }, "serialize-javascript": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.5.0.tgz", - "integrity": "sha512-Ga8c8NjAAp46Br4+0oZ2WxJCwIzwP60Gq1YPgU+39PiTVxyed/iKE/zyZI6+UlVYH5Q4PaQdHhcegIFPZTUfoQ==" + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.6.1.tgz", + "integrity": "sha512-A5MOagrPFga4YaKQSWHryl7AXvbQkEqpw4NNYMTNYUNV51bA8ABHgYFpqKx+YFFrw59xMV1qGH1R4AgoNIVgCw==" }, "serve-favicon": { "version": "2.5.0", @@ -10485,6 +9317,11 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + }, + "safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" } } }, @@ -10532,15 +9369,14 @@ "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" }, "setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" }, "sha.js": { "version": "2.4.11", - "resolved": "http://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "dev": true, "requires": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" @@ -10568,17 +9404,18 @@ "dev": true }, "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", - "dev": true + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=" }, "slice-ansi": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", - "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", "dev": true, "requires": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", "is-fullwidth-code-point": "^2.0.0" } }, @@ -10606,14 +9443,6 @@ "use": "^3.1.0" }, "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, "define-property": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", @@ -10730,11 +9559,11 @@ "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" }, "source-map-resolve": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.1.tgz", - "integrity": "sha512-0KW2wvzfxm8NCTb30z0LMNyPqWCdDGE2viwzUaucqJdkTRXtZiSY3I+2A6nVAjmdOy0I4gU8DwnVVGsk9jvP2A==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", + "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", "requires": { - "atob": "^2.0.0", + "atob": "^2.1.1", "decode-uri-component": "^0.2.0", "resolve-url": "^0.2.1", "source-map-url": "^0.4.0", @@ -10742,9 +9571,9 @@ } }, "source-map-support": { - "version": "0.5.9", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.9.tgz", - "integrity": "sha512-gR6Rw4MvUlYy83vP0vxoVNzM6t8MUXqNuRsuBmBHQDu1Fh6X015FrLdgoDKcNdkwGubozq0P4N0Q37UyFVr1EA==", + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.10.tgz", + "integrity": "sha512-YfQ3tQFTK/yzlGJuX8pTwa4tifQj4QS2Mj7UegOu8jAz59MqIiMGPXxQhVQiIMNzayuUSF/jEuVnfFF5JqybmQ==", "dev": true, "requires": { "buffer-from": "^1.0.0", @@ -10765,9 +9594,9 @@ "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=" }, "spdx-correct": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", - "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", + "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", "dev": true, "requires": { "spdx-expression-parse": "^3.0.0", @@ -10775,9 +9604,9 @@ } }, "spdx-exceptions": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz", - "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", "dev": true }, "spdx-expression-parse": { @@ -10791,9 +9620,9 @@ } }, "spdx-license-ids": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz", - "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.3.tgz", + "integrity": "sha512-uBIcIl3Ih6Phe3XHK1NqboJLdGfwr1UN3k6wSD1dZpmPsIkb8AGNbZYJ1fOBk834+Gxy8rpfDxrS6XLEMZMY2g==", "dev": true }, "split": { @@ -10819,11 +9648,11 @@ "dev": true }, "ssri": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-5.3.0.tgz", - "integrity": "sha512-XRSIPqLij52MtgoQavH/x/dU1qVKtWUAAZeOHsR9c2Ddi4XerFy3mc1alf+dLJKl9EUIm/Ht+EowFkTUOA6GAQ==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", + "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", "requires": { - "safe-buffer": "^5.1.1" + "figgy-pudding": "^3.5.1" } }, "static-extend": { @@ -10846,14 +9675,14 @@ } }, "statuses": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" }, "stream-browserify": { - "version": "2.0.1", - "resolved": "http://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", - "integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", + "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", "dev": true, "requires": { "inherits": "~2.0.1", @@ -10900,32 +9729,6 @@ "readable-stream": "^2.3.6", "to-arraybuffer": "^1.0.0", "xtend": "^4.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } } }, "stream-shift": { @@ -10960,9 +9763,9 @@ "integrity": "sha1-q6Nt4I3O5qWjN9SbLqHaGyj8Ds8=" }, "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" } @@ -10974,14 +9777,6 @@ "dev": true, "requires": { "ansi-regex": "^3.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - } } }, "strip-bom": { @@ -11025,17 +9820,17 @@ } }, "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "requires": { "has-flag": "^3.0.0" } }, "svg-pan-zoom": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/svg-pan-zoom/-/svg-pan-zoom-3.5.3.tgz", - "integrity": "sha512-jlHEOXRW4IF0W2e2DTk+hjKPKassF4TpJSSC7ilHJoZHX9hthFzU63WhluEwzSbTQF4akrWcjmbWxiCoGPa6PA==" + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/svg-pan-zoom/-/svg-pan-zoom-3.6.0.tgz", + "integrity": "sha512-ZBEL2z/n/W2fLLFzn3NTQgd+7QEfbrKvKmu29U3qvMflmJgLaWkwKbOzWJYESFidTiGYMHkijAKmY6m64moyYg==" }, "swap-case": { "version": "1.1.2", @@ -11053,36 +9848,42 @@ "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==" }, "table": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz", - "integrity": "sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA==", + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/table/-/table-5.2.3.tgz", + "integrity": "sha512-N2RsDAMvDLvYwFcwbPyF3VmVSSkuF+G1e+8inhBLtHpvwXGw4QRPEZhihQNeEN0i1up6/f6ObCJXNdlRG3YVyQ==", "dev": true, "requires": { - "ajv": "^5.2.3", - "ajv-keywords": "^2.1.0", - "chalk": "^2.1.0", - "lodash": "^4.17.4", - "slice-ansi": "1.0.0", - "string-width": "^2.1.1" + "ajv": "^6.9.1", + "lodash": "^4.17.11", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" }, "dependencies": { - "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "ansi-regex": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.0.0.tgz", + "integrity": "sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w==", + "dev": true + }, + "string-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.0.0.tgz", + "integrity": "sha512-rr8CUxBbvOZDUvc5lNIJ+OC1nPVpz+Siw9VBtUjB9b6jZehZLFt0JMCZzShFHIsI8cbhm0EsNIfWJMFV3cu3Ew==", "dev": true, "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.0.0" } }, - "ajv-keywords": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz", - "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=", - "dev": true + "strip-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.0.0.tgz", + "integrity": "sha512-Uu7gQyZI7J7gn5qLn1Np3G9vcYGTVqB+lFTytnDJv83dd8T22aGH451P3jueT2/QemInJDfxHB5Tde5OzgG1Ow==", + "dev": true, + "requires": { + "ansi-regex": "^4.0.0" + } } } }, @@ -11106,9 +9907,9 @@ "dev": true }, "tapable": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.0.0.tgz", - "integrity": "sha512-dQRhbNQkRnaqauC7WqSJ21EEksgT0fYZX2lqXzGkpo8JNig9zGZTYoMGvyI2nWmXlE2VSVXVDu7wLVGu/mQEsg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.1.tgz", + "integrity": "sha512-9I2ydhj8Z9veORCw5PRm4u9uebCn0mcCa6scWoNcbZ6dAtoo2618u9UUzxgmsCOreJpqDDuv61LvwofW7hLcBA==", "dev": true }, "tarn": { @@ -11129,19 +9930,75 @@ "dev": true, "requires": { "execa": "^0.7.0" + }, + "dependencies": { + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", + "dev": true, + "requires": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", + "dev": true + }, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + } } }, "terser": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-3.11.0.tgz", - "integrity": "sha512-5iLMdhEPIq3zFWskpmbzmKwMQixKmTYwY3Ox9pjtSklBLnHiuQ0GKJLhL1HSYtyffHM3/lDIFBnb82m9D7ewwQ==", + "version": "3.16.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-3.16.1.tgz", + "integrity": "sha512-JDJjgleBROeek2iBcSNzOHLKsB/MdDf+E/BOAJ0Tk9r7p9/fVobfv7LMJ/g/k3v9SXdmjZnIlFd5nfn/Rt0Xow==", "dev": true, "requires": { "commander": "~2.17.1", "source-map": "~0.6.1", - "source-map-support": "~0.5.6" + "source-map-support": "~0.5.9" }, "dependencies": { + "commander": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", + "dev": true + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -11151,9 +10008,9 @@ } }, "terser-webpack-plugin": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.1.0.tgz", - "integrity": "sha512-61lV0DSxMAZ8AyZG7/A4a3UPlrbOBo8NIQ4tJzLPAdGOQ+yoNC7l5ijEow27lBAL2humer01KLS6bGIMYQxKoA==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.2.3.tgz", + "integrity": "sha512-GOK7q85oAb/5kE12fMuLdn2btOS9OBZn4VsecpHDywoUC/jLhSAKOiYo0ezx7ss2EXPMzyEWFoE0s1WLE+4+oA==", "dev": true, "requires": { "cacache": "^11.0.2", @@ -11161,159 +10018,16 @@ "schema-utils": "^1.0.0", "serialize-javascript": "^1.4.0", "source-map": "^0.6.1", - "terser": "^3.8.1", + "terser": "^3.16.1", "webpack-sources": "^1.1.0", "worker-farm": "^1.5.2" }, "dependencies": { - "cacache": { - "version": "11.3.1", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-11.3.1.tgz", - "integrity": "sha512-2PEw4cRRDu+iQvBTTuttQifacYjLPhET+SYO/gEFMy8uhi+jlJREDAjSF5FWSdV/Aw5h18caHA7vMTw2c+wDzA==", - "dev": true, - "requires": { - "bluebird": "^3.5.1", - "chownr": "^1.0.1", - "figgy-pudding": "^3.1.0", - "glob": "^7.1.2", - "graceful-fs": "^4.1.11", - "lru-cache": "^4.1.3", - "mississippi": "^3.0.0", - "mkdirp": "^0.5.1", - "move-concurrently": "^1.0.1", - "promise-inflight": "^1.0.1", - "rimraf": "^2.6.2", - "ssri": "^6.0.0", - "unique-filename": "^1.1.0", - "y18n": "^4.0.0" - } - }, - "find-cache-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.0.0.tgz", - "integrity": "sha512-LDUY6V1Xs5eFskUVYtIwatojt6+9xC9Chnlk/jYOOvn3FAFfSaWddxahDGyNHh0b2dMXa6YW2m0tk8TdVaXHlA==", - "dev": true, - "requires": { - "commondir": "^1.0.1", - "make-dir": "^1.0.0", - "pkg-dir": "^3.0.0" - } - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "dev": true, - "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, - "mississippi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", - "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", - "dev": true, - "requires": { - "concat-stream": "^1.5.0", - "duplexify": "^3.4.2", - "end-of-stream": "^1.1.0", - "flush-write-stream": "^1.0.0", - "from2": "^2.1.0", - "parallel-transform": "^1.1.0", - "pump": "^3.0.0", - "pumpify": "^1.3.3", - "stream-each": "^1.1.0", - "through2": "^2.0.0" - } - }, - "p-limit": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.0.0.tgz", - "integrity": "sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-try": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", - "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", - "dev": true - }, - "pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "dev": true, - "requires": { - "find-up": "^3.0.0" - } - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", - "dev": true, - "requires": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" - } - }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "ssri": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", - "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", - "dev": true, - "requires": { - "figgy-pudding": "^3.5.1" - } + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true } } }, @@ -11333,17 +10047,6 @@ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, - "theming": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/theming/-/theming-1.3.0.tgz", - "integrity": "sha512-ya5Ef7XDGbTPBv5ENTwrwkPUexrlPeiAg/EI9kdlUAZhNlRbCdhMKRgjNX1IcmsmiPcqDQZE6BpSaH+cr31FKw==", - "requires": { - "brcast": "^3.0.1", - "is-function": "^1.0.1", - "is-plain-object": "^2.0.1", - "prop-types": "^15.5.8" - } - }, "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -11356,30 +10059,6 @@ "requires": { "readable-stream": "~2.3.6", "xtend": "~4.0.1" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } } }, "tildify": { @@ -11406,9 +10085,9 @@ } }, "tiny-emitter": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.0.2.tgz", - "integrity": "sha512-2NM0auVBGft5tee/OxP4PI3d8WItkDM+fPnaRAVo6xTDI2knbz9eC5ArWGqtGlYqiH3RU5yMpdyTTO7MguC4ow==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", + "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" }, "title-case": { "version": "2.1.1", @@ -11436,10 +10115,9 @@ "dev": true }, "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", + "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=" }, "to-object-path": { "version": "0.3.0", @@ -11509,10 +10187,18 @@ "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", "dev": true }, + "ts-invariant": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/ts-invariant/-/ts-invariant-0.2.1.tgz", + "integrity": "sha512-Z/JSxzVmhTo50I+LKagEISFJW3pvPCqsMWLamCTX8Kr3N5aMrnGOqcflbe5hLUzwjvgPfnLzQtHZv0yWQ+FIHg==", + "requires": { + "tslib": "^1.9.3" + } + }, "ts-loader": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-5.3.1.tgz", - "integrity": "sha512-fDDgpBH3SR8xlt2MasLdz3Yy611PQ/UY/KGyo7TgXhTRU/6sS8uGG0nJYnU1OdFBNKcoYbId1UTNaAOUn+i41g==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-5.3.3.tgz", + "integrity": "sha512-KwF1SplmOJepnoZ4eRIloH/zXL195F51skt7reEsS6jvDqzgc/YSbz9b8E07GxIUwLXdcD4ssrJu6v8CwaTafA==", "dev": true, "requires": { "chalk": "^2.3.0", @@ -11522,15 +10208,57 @@ "semver": "^5.0.1" } }, + "ts-node": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.0.2.tgz", + "integrity": "sha512-MosTrinKmaAcWgO8tqMjMJB22h+sp3Rd1i4fdoWY4mhBDekOwIAKI/bzmRi7IcbCmjquccYg2gcF6NBkLgr0Tw==", + "dev": true, + "requires": { + "arg": "^4.1.0", + "diff": "^3.1.0", + "make-error": "^1.1.1", + "source-map-support": "^0.5.6", + "yn": "^3.0.0" + } + }, "tslib": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", - "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", - "dev": true + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==" + }, + "tslint": { + "version": "5.13.1", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.13.1.tgz", + "integrity": "sha512-fplQqb2miLbcPhyHoMV4FU9PtNRbgmm/zI5d3SZwwmJQM6V0eodju+hplpyfhLWpmwrDNfNYU57uYRb8s0zZoQ==", + "dev": true, + "requires": { + "babel-code-frame": "^6.22.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^3.2.0", + "glob": "^7.1.1", + "js-yaml": "^3.7.0", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.8.0", + "tsutils": "^2.27.2" + } + }, + "tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } }, "tty-browserify": { "version": "0.0.0", - "resolved": "http://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", "dev": true }, @@ -11543,6 +10271,12 @@ "prelude-ls": "~1.1.2" } }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, "type-is": { "version": "1.6.16", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", @@ -11558,9 +10292,9 @@ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, "typescript": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.2.2.tgz", - "integrity": "sha512-VCj5UiSyHBjwfYacmDuc/NOk4QQixbE+Wn7MFJuS0nRuPQbof132Pw4u53dm264O8LPc2MVsc7RJNml5szurkg==", + "version": "3.3.3333", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.3.3333.tgz", + "integrity": "sha512-JjSKsAfuHBE/fB2oZ8NxtRTk5iGcg6hkYXMnZ3Wc+b2RSqejEqTaem11mHASMnFilHrax3sLK0GDzcJrekZYLw==", "dev": true }, "typical": { @@ -11570,9 +10304,9 @@ "dev": true }, "ua-parser-js": { - "version": "0.7.18", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.18.tgz", - "integrity": "sha512-LtzwHlVHwFGTptfNSgezHp7WUlwiqb0gA9AALRbKaERfxwJoiX0A73QbTToxteIAuIaFshhgIZfqK8s7clqgnA==" + "version": "0.7.19", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.19.tgz", + "integrity": "sha512-T3PVJ6uz8i0HzPxOF9SWzWAlfN/DavlpQqepn22xgve/5QecC+XMCAtmUNnY7C9StehaV6exjUCI801lOI7QlQ==" }, "uglify-js": { "version": "2.8.29", @@ -11610,17 +10344,6 @@ "dev": true, "requires": { "debug": "^2.2.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } } }, "underscore": { @@ -11817,6 +10540,14 @@ "upper-case": "^1.1.1" } }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "requires": { + "punycode": "^2.1.0" + } + }, "urix": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", @@ -11841,33 +10572,19 @@ } }, "url-loader": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-0.6.2.tgz", - "integrity": "sha512-h3qf9TNn53BpuXTTcpC+UehiRrl0Cv45Yr/xWayApjw6G8Bg2dGke7rIwDQ39piciWCWrC+WiqLjOh3SUp9n0Q==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-1.1.2.tgz", + "integrity": "sha512-dXHkKmw8FhPqu8asTc1puBfe3TehOCo2+RmOOev5suNCIYBcT626kxiWg1NBVkwc4rO8BGa7gP70W7VXuqHrjg==", "requires": { - "loader-utils": "^1.0.2", - "mime": "^1.4.1", - "schema-utils": "^0.3.0" + "loader-utils": "^1.1.0", + "mime": "^2.0.3", + "schema-utils": "^1.0.0" }, "dependencies": { - "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", - "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" - } - }, - "schema-utils": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.3.0.tgz", - "integrity": "sha1-9YdyIs4+kx7a4DnxfrNxbnE3+M8=", - "requires": { - "ajv": "^5.0.0" - } + "mime": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.0.tgz", + "integrity": "sha512-ikBcWwyqXQSHKtciCcctu9YfPbFYZ4+gbHEmE0Q8jzcTYQg5dHCr3g2wwAZjPoJfQVXZq6KXAjpXOTf5/cjT7w==" } } }, @@ -11881,12 +10598,9 @@ } }, "use": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.0.tgz", - "integrity": "sha512-6UJEQM/L+mzC3ZJNM56Q4DFGLX/evKGRg15UJHGB9X5j5Z3AFbgZvjUh2yq/UJUY4U5dh7Fal++XbNg1uzpRAw==", - "requires": { - "kind-of": "^6.0.2" - } + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" }, "util": { "version": "0.10.4", @@ -11916,9 +10630,9 @@ "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" }, "uuid": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", - "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" }, "v8-compile-cache": { "version": "2.0.2", @@ -11927,17 +10641,17 @@ "dev": true }, "v8flags": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.1.1.tgz", - "integrity": "sha512-iw/1ViSEaff8NJ3HLyEjawk/8hjJib3E7pvG4pddVXfUg1983s3VGsiClDjhK64MQVDGqc1Q8r18S4VKQZS9EQ==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.1.2.tgz", + "integrity": "sha512-MtivA7GF24yMPte9Rp/BWGCYQNaUj86zeYxV/x2RRJMKagImbbv3u8iJC57lNhWLPcGLJmHcHmFWkNsplbbLWw==", "requires": { "homedir-polyfill": "^1.0.1" } }, "validate-npm-package-license": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz", - "integrity": "sha512-63ZOUnL4SIXj4L0NixR3L1lcjO38crAbgrTpl28t8jjrfuiOBL5Iygm+60qPs/KsZGzPNg6Smnc/oY16QTjF0g==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", "dev": true, "requires": { "spdx-correct": "^3.0.0", @@ -11984,7 +10698,7 @@ }, "vm-browserify": { "version": "0.0.4", - "resolved": "http://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=", "dev": true, "requires": { @@ -11997,15 +10711,15 @@ "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=" }, "walk-back": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/walk-back/-/walk-back-3.0.0.tgz", - "integrity": "sha1-I1h4ejXakQMtrV6S+AsSNw2HlcU=", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/walk-back/-/walk-back-3.0.1.tgz", + "integrity": "sha512-umiNB2qLO731Sxbp6cfZ9pwURJzTnftxE4Gc7hq8n/ehkuXC//s9F65IEIJA2ZytQZ1ZOsm/Fju4IWx0bivkUQ==", "dev": true }, "warning": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.2.tgz", - "integrity": "sha512-wbTp09q/9C+jJn4KKJfJfoS6VleK/Dti0yqWSm6KMvJ4MRCXFQNapHuJXutJIrWV0Cf4AhTdeIe4qdKHR1+Hug==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", "requires": { "loose-envify": "^1.0.0" } @@ -12022,17 +10736,17 @@ } }, "webpack": { - "version": "4.27.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.27.1.tgz", - "integrity": "sha512-WArHiLvHrlfyRM8i7f+2SFbr/XbQ0bXqTkPF8JpHOzub5482Y3wx7rEO8stuLGOKOgZJcqcisLhD7LrM/+fVMw==", + "version": "4.29.6", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.29.6.tgz", + "integrity": "sha512-MwBwpiE1BQpMDkbnUUaW6K8RFZjljJHArC6tWQJoFm0oQtfoSebtg4Y7/QHnJ/SddtjYLHaKGX64CFjG5rehJw==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.7.11", - "@webassemblyjs/helper-module-context": "1.7.11", - "@webassemblyjs/wasm-edit": "1.7.11", - "@webassemblyjs/wasm-parser": "1.7.11", - "acorn": "^5.6.2", - "acorn-dynamic-import": "^3.0.0", + "@webassemblyjs/ast": "1.8.5", + "@webassemblyjs/helper-module-context": "1.8.5", + "@webassemblyjs/wasm-edit": "1.8.5", + "@webassemblyjs/wasm-parser": "1.8.5", + "acorn": "^6.0.5", + "acorn-dynamic-import": "^4.0.0", "ajv": "^6.1.0", "ajv-keywords": "^3.1.0", "chrome-trace-event": "^1.0.0", @@ -12046,7 +10760,7 @@ "mkdirp": "~0.5.0", "neo-async": "^2.5.0", "node-libs-browser": "^2.0.0", - "schema-utils": "^0.4.4", + "schema-utils": "^1.0.0", "tapable": "^1.1.0", "terser-webpack-plugin": "^1.1.0", "watchpack": "^1.5.0", @@ -12054,45 +10768,40 @@ }, "dependencies": { "acorn": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", - "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz", + "integrity": "sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==", "dev": true }, "eslint-scope": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.0.tgz", - "integrity": "sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.2.tgz", + "integrity": "sha512-5q1+B/ogmHl8+paxtOKx38Z8LtWkVGuNt3+GQNErqwLl6ViNp/gdJGMCjZNxZ8j/VYjDNZ2Fo+eQc1TAVPIzbg==", "dev": true, "requires": { "esrecurse": "^4.1.0", "estraverse": "^4.1.1" } - }, - "tapable": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.1.tgz", - "integrity": "sha512-9I2ydhj8Z9veORCw5PRm4u9uebCn0mcCa6scWoNcbZ6dAtoo2618u9UUzxgmsCOreJpqDDuv61LvwofW7hLcBA==", - "dev": true } } }, "webpack-cli": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.1.2.tgz", - "integrity": "sha512-Cnqo7CeqeSvC6PTdts+dywNi5CRlIPbLx1AoUPK2T6vC1YAugMG3IOoO9DmEscd+Dghw7uRlnzV1KwOe5IrtgQ==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.2.3.tgz", + "integrity": "sha512-Ik3SjV6uJtWIAN5jp5ZuBMWEAaP5E4V78XJ2nI+paFPh8v4HPSwo/myN0r29Xc/6ZKnd2IdrAlpSgNOu2CDQ6Q==", "dev": true, "requires": { "chalk": "^2.4.1", "cross-spawn": "^6.0.5", "enhanced-resolve": "^4.1.0", - "global-modules-path": "^2.3.0", + "findup-sync": "^2.0.0", + "global-modules": "^1.0.0", "import-local": "^2.0.0", "interpret": "^1.1.0", "loader-utils": "^1.1.0", "supports-color": "^5.5.0", "v8-compile-cache": "^2.0.2", - "yargs": "^12.0.2" + "yargs": "^12.0.4" }, "dependencies": { "cliui": { @@ -12106,140 +10815,14 @@ "wrap-ansi": "^2.0.0" } }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "decamelize": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-2.0.0.tgz", - "integrity": "sha512-Ikpp5scV3MSYxY39ymh45ZLEecsTdv/Xj2CaQfI8RLMuwi7XvjX9H/fhraiSuU+C5w5NTDu4ZU72xNiZnurBPg==", - "dev": true, - "requires": { - "xregexp": "4.0.0" - } - }, - "execa": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.10.0.tgz", - "integrity": "sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==", - "dev": true, - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^3.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "invert-kv": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", - "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", - "dev": true - }, - "lcid": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", - "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", - "dev": true, - "requires": { - "invert-kv": "^2.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "mem": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-4.0.0.tgz", - "integrity": "sha512-WQxG/5xYc3tMbYLXoXPm81ET2WDULiU5FxbuIoNbJqLOOI8zehXFdZuiUEgfdrU2mVB1pxBZUGlYORSrpuJreA==", - "dev": true, - "requires": { - "map-age-cleaner": "^0.1.1", - "mimic-fn": "^1.0.0", - "p-is-promise": "^1.1.0" - } - }, - "os-locale": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.0.1.tgz", - "integrity": "sha512-7g5e7dmXPtzcP4bgsZ8ixDVqA7oWYuEz4lOSujeWyliPai4gfVDiFIcwBg3aGCPnmSGfzOKTK3ccPn0CKv3DBw==", - "dev": true, - "requires": { - "execa": "^0.10.0", - "lcid": "^2.0.0", - "mem": "^4.0.0" - } - }, - "p-limit": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.0.0.tgz", - "integrity": "sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-try": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", - "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, "yargs": { - "version": "12.0.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.2.tgz", - "integrity": "sha512-e7SkEx6N6SIZ5c5H22RTZae61qtn3PYUE8JYbBFlK9sYmh3DMQ6E5ygtaG/2BW0JZi4WGgTR2IV5ChqlqrDGVQ==", + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", + "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", "dev": true, "requires": { "cliui": "^4.0.0", - "decamelize": "^2.0.0", + "decamelize": "^1.2.0", "find-up": "^3.0.0", "get-caller-file": "^1.0.1", "os-locale": "^3.0.0", @@ -12249,7 +10832,7 @@ "string-width": "^2.0.0", "which-module": "^2.0.0", "y18n": "^3.2.1 || ^4.0.0", - "yargs-parser": "^10.1.0" + "yargs-parser": "^11.1.1" } } } @@ -12265,6 +10848,15 @@ "pify": "^3.0.0" } }, + "webpack-log": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-2.0.0.tgz", + "integrity": "sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg==", + "requires": { + "ansi-colors": "^3.0.0", + "uuid": "^3.3.2" + } + }, "webpack-node-externals": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/webpack-node-externals/-/webpack-node-externals-1.7.2.tgz", @@ -12290,14 +10882,14 @@ } }, "whatwg-fetch": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz", - "integrity": "sha1-nITsLc9oGH/wC8ZOEnS0QhduHIQ=" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz", + "integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==" }, "which": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", - "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "requires": { "isexe": "^2.0.0" } @@ -12308,6 +10900,15 @@ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, "widest-line": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz", @@ -12329,20 +10930,12 @@ "requires": { "acorn": "^3.1.0", "acorn-globals": "^3.0.0" - }, - "dependencies": { - "acorn": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", - "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=" - } } }, "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", - "dev": true + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=" }, "wordwrapjs": { "version": "3.0.0", @@ -12373,6 +10966,12 @@ "strip-ansi": "^3.0.1" }, "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, "is-fullwidth-code-point": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", @@ -12410,18 +11009,18 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "write": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", - "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", + "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", "dev": true, "requires": { "mkdirp": "^0.5.1" } }, "write-file-atomic": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.3.0.tgz", - "integrity": "sha512-xuPeK4OdjWqtfi59ylvVL0Yn35SF3zgcAcv7rBPFHVaEapaDr4GdGgm3j7ckTwH9wHL7fGmgfAnb0+THrHb8tA==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.2.tgz", + "integrity": "sha512-s0b6vB3xIVRLWywa6X9TOMA7k9zio0TMOsl9ZnDkliA/cfJlpHXAscj0gbHVJiTdIuAYpIyqS5GW91fqm6gG5g==", "dev": true, "requires": { "graceful-fs": "^4.1.11", @@ -12430,9 +11029,9 @@ } }, "ws": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.1.2.tgz", - "integrity": "sha512-rfUqzvz0WxmSXtJpPMX2EeASXabOrSMk1ruMOV3JBTBjo4ac2lDjGGsbQSyxj8Odhw5fBib8ZKEjDNvgouNKYw==", + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.1.4.tgz", + "integrity": "sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA==", "requires": { "async-limiter": "~1.0.0" } @@ -12449,12 +11048,6 @@ "integrity": "sha1-+mv3YqYKQT+z3Y9LA8WyaSONMI8=", "dev": true }, - "xregexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.0.0.tgz", - "integrity": "sha512-PHyM+sQouu7xspQQwELlGwwd05mXUFqwFYfqPO0cC7x4fxyHnnuetmQr6CjJiafIDoH4MogHb9dOoJzR/Y4rFg==", - "dev": true - }, "xtend": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", @@ -12466,9 +11059,9 @@ "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" }, "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==" }, "yargs": { "version": "3.10.0", @@ -12479,33 +11072,85 @@ "cliui": "^2.1.0", "decamelize": "^1.0.0", "window-size": "0.1.0" + } + }, + "yargs-parser": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", + "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" }, "dependencies": { "camelcase": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", - "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=" + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz", + "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==", + "dev": true } } }, - "yargs-parser": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", - "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", + "yargs-unparser": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.5.0.tgz", + "integrity": "sha512-HK25qidFTCVuj/D1VfNiEndpLIeJN78aqgR23nL3y4N0U/91cOAzqfHlF8n2BvoNDcZmJKin3ddNSvOxSr8flw==", "dev": true, "requires": { - "camelcase": "^4.1.0" + "flat": "^4.1.0", + "lodash": "^4.17.11", + "yargs": "^12.0.5" + }, + "dependencies": { + "cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "dev": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + } + }, + "yargs": { + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", + "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", + "dev": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^11.1.1" + } + } } }, + "yn": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.0.0.tgz", + "integrity": "sha512-+Wo/p5VRfxUgBUGy2j/6KX2mj9AYJWOHuhMjMcbBFc3y54o9/4buK1ksBvuiK01C3kby8DH9lSmJdSxw+4G/2Q==", + "dev": true + }, "zen-observable": { - "version": "0.8.9", - "resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.9.tgz", - "integrity": "sha512-Y9kPzjGvIZ5jchSlqlCpBW3I82zBBL4z+ulXDRVA1NwsKzjt5kwAi+gOYIy0htNkfuehGZZtP5mRXHRV6TjDWw==" + "version": "0.8.13", + "resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.13.tgz", + "integrity": "sha512-fa+6aDUVvavYsefZw0zaZ/v3ckEtMgCFi30sn91SEZea4y/6jQp05E3omjkX91zV6RVdn15fqnFZ6RKjRGbp2g==" }, "zen-observable-ts": { - "version": "0.8.9", - "resolved": "https://registry.npmjs.org/zen-observable-ts/-/zen-observable-ts-0.8.9.tgz", - "integrity": "sha512-KJz2O8FxbAdAU5CSc8qZ1K2WYEJb1HxS6XDRF+hOJ1rOYcg6eTMmS9xYHCXzqZZzKw6BbXWyF4UpwSsBQnHJeA==", + "version": "0.8.15", + "resolved": "https://registry.npmjs.org/zen-observable-ts/-/zen-observable-ts-0.8.15.tgz", + "integrity": "sha512-sXKPWiw6JszNEkRv5dQ+lQCttyjHM2Iks74QU5NP8mMPS/NrzTlHDr780gf/wOBqmHkPO6NCLMlsa+fAQ8VE8w==", "requires": { "zen-observable": "^0.8.0" } diff --git a/package.json b/package.json index 128f20b1bcb31dbc9cb0688f2bd1973186f20596..79f87cb4d457fdf50473d7082131ee8e04011618 100644 --- a/package.json +++ b/package.json @@ -8,8 +8,13 @@ "watch": "webpack --watch --mode development", "start": "nodemon --watch build ./build/bundle.js", "start_prod": "node ./build/bundle.js", - "doc": "jsdoc --configure configfile_doc.json", - "lint": "eslint --ext .js --ext .ts src/ " + "doc": "jsdoc --configure jsdoc_config.json", + "lint": "echo 'Ambiguous! Use `npm run jslint` (lint .js files using ESLint) or `npm run tslint` (lint .ts and .js files using TSLint) instead'", + "eslint": "eslint --ext .js src/ db/", + "tslint": "tslint --project tsconfig.json", + "tsfix": "tslint --project tsconfig.json --fix", + "tsc": "tsc --project tsconfig.json", + "test": "mocha --require mocha-graphql-register --require ts-node/register test/**/*.test.ts --timeout 20000" }, "repository": { "type": "git", @@ -18,70 +23,79 @@ "author": "Binet Réseau", "license": "ISC", "dependencies": { - "apollo-server-express": "^2.3.1", + "apollo-server-express": "^2.4.8", "body-parser": "^1.18.3", "colors": "^1.3.3", "connect-ensure-login": "^0.1.1", "connect-flash": "^0.1.1", - "cookie-parser": "^1.4.3", - "copy-webpack-plugin": "^4.6.0", + "cookie-parser": "^1.4.4", + "copy-webpack-plugin": "^5.0.0", "cors": "^2.8.5", "dotenv": "^6.2.0", "express": "^4.16.4", "express-jwt": "^5.3.1", "express-session": "^1.15.6", - "file-loader": "^1.1.11", + "file-loader": "^3.0.1", "fs": "0.0.1-security", - "fuse.js": "^3.3.0", - "graphql": "^14.0.2", - "graphql-tools": "^3.1.1", + "fuse.js": "^3.4.2", + "graphql": "^14.1.1", + "graphql-tools": "^4.0.4", "graphql-voyager": "^1.0.0-rc.26", - "jsonwebtoken": "^8.4.0", - "knex": "^0.15.2", + "jsonwebtoken": "^8.5.0", + "knex": "^0.16.3", "ldap-escape": "^1.1.5", "ldapjs": "^1.0.2", "lodash": "^4.17.11", "morgan": "^1.9.1", "package-lock": "^1.0.0", "passport": "^0.4.0", - "passport-ldapauth": "^2.1.1", + "passport-ldapauth": "^2.1.2", "path": "^0.12.7", - "pg": "^7.7.1", + "pg": "^7.8.1", "pug": "^2.0.3", - "react": "^16.6.3", - "react-dom": "^16.6.3", + "react": "^16.8.3", + "react-dom": "^16.8.3", "serve-favicon": "^2.5.0", - "url-loader": "^0.6.2" + "url-loader": "^1.1.2" }, "devDependencies": { - "@babel/cli": "^7.2.0", - "@babel/core": "^7.2.0", - "@babel/plugin-proposal-class-properties": "^7.2.1", - "@babel/plugin-proposal-object-rest-spread": "^7.2.0", - "@babel/preset-env": "^7.2.0", - "@babel/preset-typescript": "^7.1.0", + "@babel/cli": "^7.2.3", + "@babel/core": "^7.3.4", + "@babel/plugin-proposal-class-properties": "^7.3.4", + "@babel/plugin-proposal-object-rest-spread": "^7.3.4", + "@babel/preset-env": "^7.3.4", + "@babel/preset-typescript": "^7.3.3", + "@types/chai": "^4.1.7", "@types/connect-ensure-login": "^0.1.4", "@types/connect-flash": "0.0.34", - "@types/graphql": "^0.13.4", - "@types/knex": "^0.14.26", - "@types/node": "^10.12.14", - "@types/passport": "^0.4.7", - "babel-eslint": "^8.2.6", - "eslint": "^4.19.1", - "eslint-config-standard": "^11.0.0", - "eslint-loader": "^2.1.1", - "eslint-plugin-import": "^2.14.0", - "eslint-plugin-node": "^6.0.1", - "eslint-plugin-promise": "^3.8.0", - "eslint-plugin-standard": "^3.1.0", + "@types/graphql": "^14.0.7", + "@types/knex": "^0.15.2", + "@types/mocha": "^5.2.6", + "@types/node": "^11.9.5", + "@types/passport": "^1.0.0", + "apollo-server-testing": "^2.4.8", + "babel-eslint": "^10.0.1", + "chai": "^4.2.0", + "eslint": "^5.14.1", + "eslint-config-standard": "^12.0.0", + "eslint-loader": "^2.1.2", + "eslint-plugin-import": "^2.16.0", + "eslint-plugin-node": "^8.0.1", + "eslint-plugin-promise": "^4.0.1", + "eslint-plugin-standard": "^4.0.0", + "graphql-request": "^1.8.2", "jsdoc": "^3.5.5", "jsdoc-babel": "^0.5.0", "jsdoc-to-markdown": "^4.0.1", - "nodemon": "^1.18.8", - "ts-loader": "^5.3.1", - "typescript": "^3.2.2", - "webpack": "^4.27.1", - "webpack-cli": "^3.1.2", + "mocha": "^6.0.2", + "mocha-graphql-register": "^1.0.0", + "nodemon": "^1.18.10", + "ts-loader": "^5.3.3", + "ts-node": "^8.0.2", + "tslint": "^5.13.0", + "typescript": "^3.3.3333", + "webpack": "^4.29.5", + "webpack-cli": "^3.2.3", "webpack-graphql-loader": "^1.0.0", "webpack-node-externals": "^1.7.2" } diff --git a/src/adminview/admin_router.ts b/src/adminview/admin_router.ts index 388e943ffa79d1093b996470503debe5d87b8191..c8b582ff8560365b8b6561a251062e9757ef773a 100644 --- a/src/adminview/admin_router.ts +++ b/src/adminview/admin_router.ts @@ -32,12 +32,23 @@ import flash from 'connect-flash'; // packages pour l'API REST et pour GraphQL Voyager import knex from '../../db/knex_router'; import { express as graphqlVoyager } from 'graphql-voyager/middleware'; +//loads environment variables from (hidden) .env file +import dotenv from 'dotenv'; +dotenv.config(); + +let port = process.env.PORT; const whitelist = [ - "magi.karp", - "mew.two", - "lippou.tou", + "gregoire.grzeckowicz", + "anatole.romon", + "hadrien.renaud", + "wilson.jallet", + "quentin.chevalier", "guillaume.wang", + "oliver.facklam", + "octave.hazard", + "guilhem.roy", + "elia.azar" ]; /** * @function ensureIsAdmin @@ -49,8 +60,10 @@ function ensureIsAdmin(returnTo) { // ensure that the request was authenticated by passport ensureLoggedIn(returnTo); // lookup req.user against whitelist of admin users - if (req.user.uid in whitelist) { + if (req.user && whitelist.includes(req.user.uid)) { console.log("is an admin"); + // go on + next(); } else { console.log("is NOT an admin"); req.flash('error: not an admin'); @@ -58,7 +71,7 @@ function ensureIsAdmin(returnTo) { res.redirect(returnTo); } // go on - next(); + //next(); }; } @@ -77,8 +90,6 @@ const router = Router(); //"The flash is a special area of the session used for storing messages. Messages are written to the flash and cleared after being displayed to the user." router.use(flash()); -let port = process.env.PORT || 3000; - /** * @desc Paths pour l'authentification : login, logout, page d'accueil * =================================================================== @@ -131,20 +142,13 @@ router.post('/avlogin', failureFlash: true }), (req, res) => { - // If this function gets called, authentication was successful. - // `req.user` contains the authenticated user. + // If this function gets called, authentication was successful. + // `req.user` contains the authenticated user. console.log("POST request to /adminview/avlogin: OK, authenticated by LDAP. req.user: "); console.log(req.user); - // lookup req.user against whitelist of admin users - if (req.user.uid in whitelist) { - console.log("is an admin"); - res.redirect("/adminview/admin"); - } else { - console.log("is NOT an admin"); - req.flash('error: not an admin'); - req.logout(); - res.redirect('/adminview/admin'); - } + // redirect to /admin + // in /admin, user will be looked up against whitelist anyway + res.redirect('/adminview/admin'); } ); @@ -203,6 +207,7 @@ router.get('/db/:table_name?', } else { columns = null; } + console.log(req.params.table_name); console.log(columns); knex.select(columns).from(req.params.table_name).then( @@ -241,7 +246,8 @@ router.use((req, res, next) => { */ router.use((err, req, res, next) => { console.log("adminview: Entering error handler"); - console.log(err.message); + console.log(err); + //console.log(err.message); //res.status(err.status || 500); res.render('error', { diff --git a/src/app.ts b/src/app.ts index b00bad015525a4ce72001ec51373e54447029afe..be35769c470f7c49083d0527be18e7cee0f74d1e 100644 --- a/src/app.ts +++ b/src/app.ts @@ -8,7 +8,7 @@ * On peut considérer que les app.use (et app.get et app.post) sont pattern-matchés et * exécutés séquentiellement. http://expressjs.com/en/guide/using-middleware.html * - * @author manifold, kadabra + * @author manifold, kadabra, ofacklam */ import express from 'express'; import bodyParser from 'body-parser'; @@ -19,18 +19,18 @@ import schema from './graphql/schema'; // definition du schéma et des resolvers import router from './adminview/admin_router'; // packages pour l'authentification import passport from 'passport'; +import './config_passport'; // configure passport, pour l'authentification ldap et pour comment gérer les sessions (serializeUser/deserializeUser) import expressSession from 'express-session'; -import cookieParser from 'cookie-parser'; import cors from 'cors'; // HTTP request logger import morgan from 'morgan'; // packages pour pouvoir importer des fichiers de config import path from 'path'; // config des paramètres de connexion au LDAP -import { ldapConfig, credentialsLdapConfig } from './ldap/config'; - -// configure passport, pour l'authentification ldap et pour comment gérer les sessions (serializeUser/deserializeUser) -import './config_passport'; +import { ldapConfig, credentialsLdapConfig } from './ldap/internal/config'; +//loads environment variables from (hidden) .env file +import dotenv from 'dotenv'; +dotenv.config(); /** * @desc Création de l'application Express et setup de middlewares basiques @@ -42,14 +42,12 @@ app.use(bodyParser.json()); //parses bodies of media type "application/json" app.use(bodyParser.urlencoded({ //parses bodies of media type "application/x-www-form-urlencoded" extended: true //use qs library (see https://www.npmjs.com/package/body-parser#bodyparserurlencodedoptions) })); -// parse Cookie header and populate req.cookies with an object keyed by the cookie names -//app.use(cookieParser()); <-- req.cookies n'est pas utilisé. // ne *pas* inclure de header HTTP publicisant que l'application tourne sous Express app.disable('x-powered-by'); // use morgan (HTTP request logger middleware) app.use(morgan('dev')); -const FRONTEND_SERVER_URL = process.env.FRONTEND_SERVER_URL || 'http://localhost:8888'; +const FRONTEND_SERVER_URL = process.env.FRONTEND_SERVER_URL; // Options de configuration pour le _middleware_ `cors`. // CORS = Cross Origin Resource Sharing const corsOptions = { @@ -64,7 +62,7 @@ app.use(cors(corsOptions)); // favicon middleware is placed near the top of the middleware stack, to answer favicon requests faster, as they are relatively frequent // (plus, they should not have to trigger any authentication middleware) import favicon from 'serve-favicon'; // tres tres important :p -app.use(favicon(path.resolve(__dirname, 'assets', 'favicon.ico'))); +app.use(favicon(path.resolve(__dirname, '..', 'assets', 'favicon.ico'))); /** * @desc Authentification de la requête contre le session-store (cookie) @@ -89,13 +87,21 @@ app.use(favicon(path.resolve(__dirname, 'assets', 'favicon.ico'))); * @todo [critical] configure express-session (session store and other options) * @todo choose a session secret and where to store it * https://www.npmjs.com/package/express-session + * Sur l'utilité des flags dans les cookies : https://www.information-security.fr/securite-sites-web-lutilite-flags-secure-httponly/ */ // load data from the session identified by the cookie (if one exists), into req.session // on ne manipulera pas req.session directement, on laisser toujours passport le faire pour nous app.use(expressSession({ - //secret: ldapConfig.sessionSecret, <-- wtf? nothing to do with ldap! secret: "asdfjklkjfdsasdfjklkljfdsa", - + resave: false, // 'resave: true' forces the session to be saved back to the session store (false OK if store implements touch()) + rolling: false, // 'rolling: true' resets expiration at every response + saveUninitialized: true, // 'saveUninitialized: true' forces empty sessions to also be stored in a cookie + //store: ,// default is NOT good => @ofacklam est d'avis d'utiliser 'connect-session-knex', comme ca on peut le plug directement dans notre BDD. + cookie: { + maxAge: 3600000, // Une heure avant expiration du cookie (en millisecondes) + //secure: true, // Le cookie ne peut transiter qu'en HTTPS. ATTENTION : If you have your node.js behind a proxy and are using secure: true, you need to set "trust proxy" in express + httpOnly: true + } })); app.use(passport.initialize()); //passport.session(): load the user object onto req.user if a serialised user object was found in the req.session @@ -175,39 +181,47 @@ app.post('/login', (req, res, next) => { // Define GraphQL request's context object, through a callback, with authorization. // See: https://www.apollographql.com/docs/apollo-server/features/authentication.html - -const context = async ({ req }) => { - let uid; - let password; +import { Context } from './graphql/typeDefs/queries'; +import { AuthorizationModel } from './graphql/models/authorization'; +import { UserModel } from './graphql/models/userModel'; +import { GroupModel } from './graphql/models/groupModel'; +import { MessageModel } from './graphql/models/messageModel'; +import { RequestModel } from './graphql/models/requestModel'; +const context = async ({ req }): Promise<Context> => { + // set a special uid for non-authenticated requests + // /!\ FOR DEVELOPMENT ONLY: use the one in the ldap_credentials.json file (imported by config.ts) + // for production, replace with a "publicUser" or "notLoggedInUser" or something. + let uid = credentialsLdapConfig.dn.split("=")[1].split(",")[0]; console.log("Responding to graphql request..."); console.log(` | User: ${req.user ? req.user.uid : "none"} - | Authorization: ${req.headers.authorization} | Authenticated: ${req.isAuthenticated()} `.trim()); - if(req.isAuthenticated()) { + if(req.isAuthenticated() && req.user) { console.log("graphql API is receiving a request from an authenticated user! \\o/"); try { uid = req.user.uid; - password = "mythe"; } catch (err) { console.log("Error: req is authenticated, but pb when trying to extract uid from req.user. Probably user was either not serialized or not deserialized properly"); console.log(err); } - } else { - // set a special uid for non-authenticated requests - // /!\ FOR DEVELOPMENT ONLY: use the one in the ldap config .json file - // for production, replace with a "publicUser" or "notLoggedInUser" or something. - uid = credentialsLdapConfig.dn.split("=")[1].split(",")[0]; - password = credentialsLdapConfig.passwd; } - - return { + console.log(`Constructing context with uid = ${uid}`); + let c = { request: req, - user: { uid, password } + user: { uid: uid }, + models: { + auth: await AuthorizationModel.create(uid), + user: new UserModel(uid), + group: new GroupModel(uid), + message: new MessageModel(uid), + request: new RequestModel(uid) + } }; + console.log("finished constructing context"); + return c; }; const server = new ApolloServer({ @@ -216,7 +230,8 @@ const server = new ApolloServer({ playground: { settings: { "editor.theme": "dark", - "editor.cursorShape": 'line' + "editor.cursorShape": 'line', + "request.credentials": 'include' } } }); diff --git a/src/config_passport.js b/src/config_passport.js index 050b5e932fae24ff015f06de14786806c0ac855e..761ef5a45a273e9f59694d05a2e696a459124100 100644 --- a/src/config_passport.js +++ b/src/config_passport.js @@ -1,7 +1,7 @@ /** * @file Configuration de passport pour utiliser l'authentification LDAP * @author kadabra - * + * @desc * http://toon.io/on-passportjs-specific-use-cases/#howtosplituppassportjsconfigurationbetweenmultiplefiles * * On utilise passport avec la strategie 'ldapauth' pour transmettre une tentative d'authentification au LDAP. @@ -32,7 +32,9 @@ */ import passport from 'passport'; import LdapStrategy from 'passport-ldapauth'; -import { ldapConfig } from './ldap/config'; +import { ldapConfig } from './ldap/internal/config'; + +console.log("Configuring passportjs... " + ldapConfig.server + " " + ldapConfig.dn.user); // specifies options for 'ldapauth' strategy, to customize the behaviour of subsequent passport.authenticate('ldapauth') calls passport.use(new LdapStrategy({ @@ -40,10 +42,11 @@ passport.use(new LdapStrategy({ url: ldapConfig.server, //bindDn: '.............', //bindCredentials: '..........', - // searchBase: ldapConfig.searchBase, TODO: this cannot be left empty. - // searchFilter: ldapConfig.searchFilter, TODO: this cannot be left empty. - //searchAttributes: ['givenName', 'sn'], + searchBase: ldapConfig.dn.user, // this field cannot be left empty. + searchFilter: '(uid={{username}})', // this field cannot be left empty. + searchAttributes: ['uid', 'urlPhoto'], // only fetch the uid, no need for any other field //tlsOptions: '..........', + //https://www.npmjs.com/package/passport-ldapauth for more }, //usernameField: 'username', // Field name where the username is found, defaults to username //passwordField: 'password', // Field name where the password is found, defaults to password @@ -85,6 +88,6 @@ passport.serializeUser(function (user, done) { * > This user object is attached to the request as req.user making it accessible in our request handling. (available in all subsequent middleware) */ passport.deserializeUser(function (userUid, done) { - // console.log(`passport.deserializeUser(): deserializing user ${userUid}`); // DEBUG + console.log(`passport.deserializeUser(): deserializing user ${userUid}`); // DEBUG done(null, { uid: userUid }); }); diff --git a/src/graphql/connectors/authentifiers.js b/src/graphql/connectors/authentifiers.js deleted file mode 100644 index 4ad92cdbee0f0f693cd974452f7eb7d533274471..0000000000000000000000000000000000000000 --- a/src/graphql/connectors/authentifiers.js +++ /dev/null @@ -1,116 +0,0 @@ -/** - * @file Fonctions d'authentification - * @author akka vodol - */ - -import knex from '../../../db/knex_router.js'; -import * as connectors from './connectors.js'; -import * as selectors from './selectors.js'; -import * as list_selectors from './list_selectors.js'; -import {Open as LDAPOpen, User as LDAPUser} from '../../ldap/users'; -import {Admin as LDAPAdmin, Supervisor as LDAPSupervisor} from '../../ldap/admins'; - -/** - * @summary Place-holder permettant de concaténer utilisateur et permissions dans un seul objet - * @arg {Object} user - Représente l'utilisateur qui a effectué la requête. - * @arg {Object} ldap_access - Représente les permissions de l'utilisateur en question. - * @return {Object} Prototype de la fonction contenant la concaténation des deux arguments. - */ -function User(user, ldap_access){ //hawkspar->akka ; ceci pourrait avantageusement être une classe - this.anonymous = Boolean(user.anonymous), - this.uid = user.uid, - this.password = user.password, - this.ldap_access = ldap_access; -} - -/** - * @summary Authentifie un utilisateur anonyme en appelant {@link User} et {@link LDAPOpen} - * @arg {Object} user - Représente l'utilisateur qui a effectué la requête. - * * @return {Promise(Object)} Un objet user si l'utilisateur possède les droits indiqués, - * null sinon - * @rights user - */ -export function anonymous(user){ - return new User(user, new LDAPOpen()); -} - -/** - * @summary Authentifie un utilisateur comme viewer(groupUID) en appelant {@link User} et {@link LDAPUser} - * @arg {Object} user - Représente l'utilisateur qui a effectué la requête. - * * @return {Promise(Object)} Un objet user si l'utilisateur possède les droits indiqués, - * null sinon - * @rights user - */ -export function loggedIn(user, groupUID){ //hawkspar: WTF ? Pq garder son groupUID ? - return new User(user, new LDAPUser(user)); -} - -/** - * @summary Authentifie un utilisateur comme viewer(groupUID) - * @arg {Object} user - Représente l'utilisateur qui a effectué la requête. - * * @return {null} - * @rights user - */ -export function viewer(user, groupUID){ - return null; // Du coup useless non ? -} - -/** - * @summary Authentifie un utilisateur anonyme en appelant {@link User} et {@link LDAPUser} - * @arg {Object} user - Représente l'utilisateur qui a effectué la requête. - * * @return {Promise(Object)} Un objet user si l'utilisateur possède les droits indiqués, - * null sinon - * @rights user - */ -export async function member(user, groupUID){ - let group_list = await selectors.groupsWithMember(user); - let test = await knex.with('group_list', group_list).select().from('group_list').where('uid', groupUID); - if(test[0]) - return new User(user, new LDAPUser(user)); - return await admin(user, groupUID); -} - -/** - * @summary Authentifie un utilisateur comme viewer(groupUID) en appelant {@link admin} - * @arg {Object} user - Représente l'utilisateur qui a effectué la requête. - * * @return {Promise(Object)} Un objet user si l'utilisateur possède les droits indiqués, - * null sinon - * @rights user - */ -export async function speaker(user, groupUID){ - return await admin(user, groupUID); -} - -/** - * @summary Authentifie un utilisateur anonyme eneffectuant une requête knex {@link list_selectors.usersWithAdminRights} et en appelant {@link User} et {@link LDAPAdmin} - * @arg {Object} user - Représente l'utilisateur qui a effectué la requête. - * * @return {Promise(Object)} Un objet user si l'utilisateur possède les droits indiqués, - * null sinon - * @rights user - */ -export async function admin(user, groupUID){ - let adminList = await list_selectors.usersWithAdminRights(user, groupUID); - if (typeof adminList != "undefined" && adminList.indexOf(user.uid) != -1) - return new User(user, new LDAPAdmin(user)); -} - -/** - * @summary Authentifie un utilisateur comme viewer(groupUID) à l'aide de {@link User} et {@link LDAPSupervisor} - * @arg {Object} user - Représente l'utilisateur qui a effectué la requête. - * @return {Promise(uid)} L'utilisateur possède-t-il les droits de superviseur ? - * Si oui, renvoie la justification. Sinon, renvoie false. - * Attention : renvoie true si l'utilisateur a les droits mais sans justification. - * @rights user - */ -export async function superviser(user, groupUID){ - let supervised_groups = await list_selectors.supervisedGroups(user); - console.log(supervised_groups); - for(let i = 0; i < supervised_groups.length; i++){ - if(supervised_groups[i].uid == groupUID){ - let user_obj = new User(user, new LDAPSupervisor(user)); - user_obj.justification = (supervised_groups.justification && supervised_groups.justification[i]); - return user_obj; - } - } - return false; -} \ No newline at end of file diff --git a/src/graphql/connectors/connectors.js b/src/graphql/connectors/connectors.js deleted file mode 100644 index f1d017e6d57f0670401dc01fdd2c16a8ef02ec4b..0000000000000000000000000000000000000000 --- a/src/graphql/connectors/connectors.js +++ /dev/null @@ -1,698 +0,0 @@ -/** - * @file Fonctions pour interagir avec la BDD sigma et le LDAP. - * @author akka vodol - */ -import knex from '../../../db/knex_router'; -import * as selectors from './selectors'; -import * as list_selectors from './list_selectors'; -import {Open as LDAPOpen, User as LDAPUser} from '../../ldap/users'; - - -const utilisateur = new LDAPOpen(); - -let result = utilisateur.getMembers("br").then(res => { - console.log("Got it"); - return res; -}); - -export { utilisateur }; -/* Ce n'est pas comme ça qu'on est censé fonctionner. Toute utilisation de utilisateur - a vocation à être temporaire, et sera remplacé par l'usage des fonctions - d'authentification correctes -*/ - -/* - Le tag @rights est la gestion des autorisations. - - Le système GraphQL est pensé comme l'interface par laquelle les utilisateurs - intéragissent avec sigma, les graphismes en moins. - Le client peut envoyer tout type de requête. C'est au niveau des resolvers - que les permissions sont gérées. D'où le @rights - - Commençons par un rappel sur le fonctionnement des droits. - Chaque utilisateur a un certain niveau de droit sur chaque groupe. Ce niveau de droit indique - ce qu'il a le droit de savoir et de faire. Chaque niveau est inclus dans les niveaus supérieur. - Les différents niveaux sont : - none - aucun droit - viewer : l'utilisateur a visibilité sur le groupe. Il sait que le groupe existe, et a accès à un certain nombre d'infos. - member : l'utilisateur est membre du groupe - speaker : l'utilisateur peut parler au nom du groupe. Il a le droit de publier des annonces et d'organiser des évènements - admin : l'utilisateur a tous les droits sur le groupe - - Certaines fonctions de connectors effectuent des vérifications d'authorisations avant - de renvoyer une réponse, d'autres non. Pour être sur qu'on ne renvoie jamais de réponse - sans avoir au préalable éffectué les bonnes vérifications, chaque fonction possède dans sa - description un attribut droit, qui décrit les droits que fournit cette fonction. - - La valeur de @rights peut être : - super - la fonction n'effectue aucune véri-fication, et renvoie le resultat demandé - admin( groupUID ) - la fonction ne fait que ce qu'un admin du groupe indiqué aurait le droit de faire - speaker( groupUID ), member( groupUID ), veiwer( groupUID ) - même chose - user - la fonction ne fait que ce que l'utiliateur a le droit de faire (vérifications via l'argument user) - - La procédure a suivre est la suivante : quand une fonction possède un certain niveau de droit, - elle ne peut appeler une fonction possédant un niveau de droit plus large que si - 1 ) on a au préalable vérifié que l'utilisateur possédait effectivement ces droits. - ou - 2 ) on s'est assuré que l'opération effectuée par cet appel particulier de la fonction était dans les droits - de l'utilisateur - - Les resolvers de base de mutation et query ont des droits user. - - Les fonctions qui ne modifient pas la BDD et ne renvoient pas de données sur la BDD n'ont pas de rights. -*/ - -/** - * @summary Génère une promise. - * @function - * @desc Les fonctions ici sont toutes supposées renvoyer une promise. - * Si on veut renvoyer une valeur directement, cette fonction permet de construire - * une Promise qui renvoie cette valeur facilement. - * @arg {Object} val - La valeur qu'on veut renvoyer. - * @return {Promise(Object)} Une promise qui renvoi val - */ -const quickPromise = (val) => { //hawkspar->akka ; selon VSCode pas utilisé donc useless - return new Promise( (resolve, reject) => { - resolve(val); - }); -}; - -/** - * @summary Renvoie le nom de la table dans laquelle il faut chercher en fonction de ce qu'on veut - * @desc Ceci est obsolète, et devra être supprimé quand le code sera RAS - * @arg {String} wantedType - Un string indiquant le type de groupe qu'on veut. Peut être `"simple"`, `"meta"` ou `"all"`. - * @return {String} Le nom de la table dans laquelle la requète doit être effectuée. - */ -function getGroupTableName(wantedType){ - switch(wantedType){ - case "simple": - return "simple_groups"; - case "SimpleGroup": - return "simple_groups"; - case "meta": - return "meta_groups"; - case "MetaGroup": - return "meta_groups"; - case "all": - return "groups"; - default: - throw "invalid type request : " + wantedType + "is not a valid group type"; - } -} - -export function rasifyGroupUID(uid){ //hawkspar->akka ; je plussoie le nom mais pas très lisible - return String(uid).replace(' ', '_').replace(/\W/g, '').toLowerCase(); -} - -/** - * @summary Renvoie un unique groupe, ssi ce groupe est visible par l'utilisateur - * @desc Pour l'instant, la fonction effectue la même requête que `getAllVisibleGroups` - * et restreint au groupe demandé. Cette fonction peut être implémentée de manière - * plus efficace et plus chiante. - * @arg {Object} user - Utilisateur effectuant la requête. - * @arg {String} uid - Identifiant du groupe voulu. - * @arg {String} type - Type de groupe voulu. `"simple"`, `"meta"` ou `"all"`. - * @return {Promise(group)} Retour de requête knex. Le groupe demandé, si l'utilisateur a le droit de la voire. - * @rights user - */ -export async function getGroupIfVisible(user, groupUID, type="all"){ - let group_table_name = getGroupTableName(type); - let visible_groups = await selectors.visibleGroups(user); - return knex.with('visible_groups', visible_groups).select() - .from(group_table_name).innerJoin('visible_groups', function (){ - this.on('visible_groups.uid', '=', group_table_name + '.uid'); - }).where(group_table_name + '.uid', groupUID).then(res => res[0]); -} - -export const getSimpleGroupIfVisible = (user, groupUID) => getGroupIfVisible(user, groupUID, "simple"); -export const getMetaGroupIfVisible = (user, groupUID) => getGroupIfVisible(user, groupUID, "meta"); - -/** - * @summary Renvoie tous les groupes simples visibles par l'utilisateur user - * @desc Cette fonction effectue une requête knex. - * @arg {Object} user - Représente l'utilisateur qui a effectué la requête. - * @arg {String} wantedType - Type de groupe voulu : `"simple"`, `"meta"` ou `"all"`. - * @return {Promise} Retour de requête knex. Liste de tous les groupes que l'utilisateur a le droit de voire. - * @rights user - */ -export async function getAllVisibleSimpleGroups (user){ - let visible_groups = await selectors.visibleGroups(user); - return getSimpleGroupsFromCallbacks(user, visible_groups); -} - -/** - * @summary Renvoie tous les meta groupes visibles par l'utilisateur user - * @desc Cette fonction effectue une requête knex. - * @arg {Object} user - Représente l'utilisateur qui a effectué la requête. - * @arg {String} wantedType - Type de groupe voulu : `"simple"`, `"meta"` ou `"all"`. - * @return {Promise} Retour de requête knex. Liste de tous les groupes que l'utilisateur a le droit de voire. - * @rights user - */ -export async function getAllVisibleMetaGroups (user){ - let visible_groups = await selectors.visibleGroups(user); - return getMetaGroupsFromCallbacks(user, visible_groups); -} - -/** - * @summary Renvoie tous les groupes visibles par l'utilisateur user - * @desc Cette fonction effectue une requête knex. Elle gère l'arête de parenté. - * @arg {Object} user - Représente l'utilisateur qui a effectué la requête. - * @arg {String} wantedType - Type de groupe voulu : `"simple"`, `"meta"` ou `"all"`. - * @return {Promise} Retour de requête knex. Liste de tous les groupes que l'utilisateur a le droit de voire. - * @rights user - */ -export async function getAllVisibleGroups(user){ - let visible_groups = await selectors.visibleGroups(user); - return getGroupsFromCallbacks(user, visible_groups); -} - -/** - * @summary Teste si un utilisateur est membre d'un groupe - * @arg {Object} user - Représente l'utilisateur qui a effectué la requête. - * @arg {Object} groupUID - L'id du groupe dont on veu savoir si l'utilisateur est membre. - * @return {Promise(Boolean)} Boolean indiquant si l'utilisateur est membre du groupe. - * @rights user - */ -export async function isMember(user, groupUID){ - let member_list = await getGroupMemberUsers(user, groupUID); - return member_list && (member_list.indexOf(groupUID) != -1); -} - -/** - * @summary Attribue un UID qui n'a pas encore été utilisé à un groupe - * @desc RASifie le string initialUID si necessaire (ramené à de l'ASCCI sans espace), puis si l'uid est deja pris rajoute un n a la fin et reteste - * @arg {String} uid - L'uid du groupe dont on veut les administrateurs. - * @return {Promise} Retour de requête knex. Promise qui renvera une liste de tous les utilisateurs ayant droit d'admin sur le groupe - * @rights user - * remarque : n'importe qui peut tester si un groupe existe en demandant a créer un groupe avec ce nom la et en regardant si - * son UID a été modifié. Je ne vois pas comment contourner ce problème, c'est donc une faille permanente de sigma. - */ -export function getAvailablegroupUID(initialUID){ - let rasUID = rasifyGroupUID(initialUID); - return knex.from('groups').where('uid', rasUID).then(res => { - if (res.length == 0) { - return (rasUID); - } else { - return (getAvailablegroupUID(rasUID + 'n')); - } - }); -} - -/** - * @summary Créé un groupe si les arguments sont tous valides - * @desc Les arguments doivent être valides, sauf pour uid. Une clé uid valide sera générée dans tous les cas. - * Les authorisations de l'utilisateur ne sont pas vérifiées - * On teste si l'utilisateur qui envoie la requête a des droits d'admin sur le parent du groupe qui doit être créé, avec la fonction - * `getUsersWithAdminRights`. - * Si un argument est invalide ou si l'utilisateur n'a pas les droits, la fonction renvoie une erreur - * @arg {Object} user - L'utilisateur qui effectue la requête. - * @arg {Object} args - Les arguments envoyés à la mutation. Cf le schéma GraphQL - * @return {Promise} Retour de requête knex. Le groupe qui vient d'être créé. En cas d'echec, renvoie une erreur. - * @rights admin (args.parent_uid) - */ -export async function createSubgroup(user, args){ - if (typeof args.parent_uid != 'string') - throw "Illegal argument : parent_uid must be a non null string"; - if (typeof args.name != 'string') - throw "Illegal argument : name must be a non null string"; - - let rasUID = await getAvailablegroupUID(args.uid); - - // TODO : appeller une fonction de LDAPUser pour y créer un groupe. - await knex('simple_groups').insert({ - uid: rasUID, - parent_uid: args.parent_uid, - createdAt: knex.fn.now(), - updatedAt: this.createdAt, - name: args.name, - website: args.website, - description: args.description, - school: args.school, - type : "simple" - }); - - return getGroupIfVisible(user, rasUID); -} - -/** - * @summary Créé un groupe si les arguments sont tous valides et l'utilisateur est authorisé - * @desc Les arguments doivent être valides, sauf pour uid. Une clé uid valide sera générée dans tous les cas. - * On teste si l'utilisateur qui envoie la requête a des droits d'admin sur le parent du groupe qui doit être créé, avec la fonction `getUsersWithAdminRights` - * Si un argument est invalide ou si l'utilisateur n'a pas les droits, la fonction renvoie une erreur - * @arg {Object} user - L'utilisateur qui effectue la requête. - * @arg {Object} args - Les arguments envoyés à la mutation. Cf le schéma GraphQL - * @return {Promise} Retour de requête knex. Le groupe qui vient d'être créé. En cas d'echec, renvoie une erreur. - * @rights user - */ -export async function createGroupIfLegal(user, args){ - if( await LDAPOpen.isGroupAdmin(utilisateur, args.parentuid) ){ - return createSubgroup(user, args); - }else{ - throw "illegal request : you must have admin rights over a group to create a subgroup of that group"; - } -} - -/** - * @summary Renvoie toues les requêtes de type UserJoinGroup - * @desc Une requête UserJoinGroup est envoyée par un utilisateur à un groupe, - * pour demander à rejoindre ce groupe - * @arg {Object} user - L'utilisateur qui effectue la requête. - * @arg {String} args - L'identifiant du groupe qui reçoit la requête. - * @return {Promise(Object)} Retour de requête knex. Toutes les requêtes destinées au groupe. - * @rights admin(recipientUID) - */ -export async function getUserJoinGroupRequests(user, recipientUID){ - let result = knex.select('id', 'useruid', 'message').from('user_join_group') - .where('recipient', recipientUID); - return result.map( obj => { - obj.type = "UserJoinGroup"; - return obj; - }); -} - -/** - * @summary Renvoie toues les requêtes de type GroupJoinEvent - * @desc Une requête UserJoinGroup est envoyée par un groupe à un évènement (donc aux administrateurs de l'évènement), - * pour demander à rejoindre cet évènement. - * Remarque : toutes les requêtes ont pour le moment un attribut recipient, - * mais ici il ne sera a terme pas utilisé. - * @arg {Object} user - L'utilisateur qui effectue la requête. - * @arg {String} args - L'identifiant du groupe qui reçoit la requête. - * @return {Promise(Object)} Retour de requête knex. Toutes les requêtes destinées au groupe. - * @rights speaker(recipientUID) - */ -export async function getGroupJoinEventRequests(user, recipientUID){ - let result = await knex.select('id', 'senderuid', 'eventuid', 'message').from('group_join_event') - .where('recipient', recipientUID); - return result.map( obj => { - obj.type = "GroupJoinEvent"; - return obj; - }); -} - - -/** - * @summary Renvoie toues les requêtes de type GroupJoinEvent - * @desc Une requête UserJoinGroup est envoyée par un groupe à un évènement (donc aux administrateurs de l'évènement), - * pour demander à rejoindre cet évènement. - * Remarque : toutes les requêtes ont pour le moment un attribut recipient, - * mais ici il ne sera a terme pas utilisé. - * @arg {Object} user - L'utilisateur qui effectue la requête. - * @arg {String} args - L'identifiant du groupe qui reçoit la requête. - * @return {Promise(Object)} Retour de requête knex. Toutes les requêtes destinées au groupe. - * @rights speaker(recipientUID) - */ -export async function getYourGroupHostEventRequests(user, recipientUID){ - let result = await knex.select('id', 'senderuid', 'eventuid', 'message').from('your_group_host_event') - .where('recipient', recipientUID); - return result.map( obj => { - obj.type = "YourGroupHostEvent"; - return obj; - }); -} - - -//Don't forget the argument user is the guy who makes the request, not the user we want -export const getUser = (user, uid, db) => { - const refactorer = (data) => { - if (typeof data.brRoom == 'string') data.brRoom = [data.brRoom]; - - return { - uid: uid, - lastName: data.sn, - givenName: data.givenName, - nickname: data.displayName, - nationality: data.country, - birthdate: data.brBirthdate, - groups: data.brMemberOf, - mail: data.mail, - phone: data.telephoneNumber, - address: data.brRoom, - promotion: data.brPromo - }; - }; - - const result = utilisateur.getUser(uid).then(res => { - return refactorer(res[0]); - }); - - return result; -}; - -// All these messages are returned if they are visible - -export async function getAnnouncement(user, messageID){ - let res = await knex.select().from('announcements').where('id', messageID) - .whereIn('id', await selectors.visibleAnnouncements(user)); - if(res[0]){ - res[0].type = 'Announcement'; - return res[0]; - } - res = await knex.select().from('events').where('id', messageID) - .whereIn('id', await selectors.visibleEvents(user)); - if(res[0]){ - res[0].type = 'Announcement'; - return res[0]; - } - return undefined; -} - -export async function getEvent(user, messageID){ - let res = await knex.select().from('events').where('id', messageID) - .whereIn('id', await selectors.visibleEvents(user)); - if(res[0]){ - res[0].type = 'Event'; - return res[0]; - } - return undefined; -} - -export async function getPrivatePost(user, messageID){ - let res = await knex.select().from('private_posts').where('id', messageID) - .whereIn('id', await selectors.visiblePrivatePosts(user)); - if(res[0]){ - res[0].type = 'PrivatePost'; - return res[0]; - } - return undefined; -} - -export async function getQuestion(user, messageID){ - let res = await knex.select().from('questions').where('id', messageID) - .whereIn('id', await selectors.visibleQuestions(user)); - if(res[0]){ - res[0].type = 'Question'; - return res[0]; - } - return undefined; -} - -export async function getAnswer(user, messageID){ - let res = await knex.select().from('answers').where('id', messageID) - .whereIn('id', await selectors.visibleAnswers(user)); - if(res[0]){ - res[0].type = 'Answer'; - return res[0]; - } - return undefined; -} - -/** - * @summary Renvoie un message en fonction de son identifiant. - * @param {*} user - Utilisateur effectuant la requête. - * @param {*} eventID - Identifiant unique de l'événement. - * @rights super - */ -export async function getMessage(user, messageID){ - return getEvent(user, messageID) | - getAnnouncement(user, messageID) | - getPrivatePost(user, messageID) | - getQuestion(user, messageID) | - getAnswer(user, messageID); -} - -export async function allVisibleEvents(user){ - let selection = await selectors.visibleEvents(user); - let result = await knex.select().from('events').whereIn('id', selection); - for(let r of result){ - r.type = 'Announcement'; - } - return result; -} - -export async function allVisibleAnnouncements(user){ - let selection = await selectors.visibleAnnouncements(user); - let result = await knex.select().from('announcements').whereIn('id', selection); - result = result.concat( - await knex.select().from('events').whereIn('id', selection) - ); - for(let r of result){ - r.type = 'Announcement'; - } - return result; -} - -export async function receivedPrivatePosts(user, groupUID){ - let received_messages = await selectors.recievedMessages(user, groupUID); - let result = await knex('private_posts').select().whereIn('id', received_messages); - for(let entry of result){ - entry.type = "PrivatePost"; - } - return result; -} - -export async function receivedQuestions(user, groupUID){ - let received_messages = await selectors.recievedMessages(user, groupUID); - let result = await knex('questions').select().whereIn('id', received_messages); - for(let entry of result){ - entry.type = "Question"; - } - return result; -} - -export async function receivedAnswers(user, groupUID){ - let received_messages = await selectors.recievedMessages(user, groupUID); - let result = await knex('answers').select().whereIn('id', received_messages); - for(let entry of result){ - entry.type = "Answer"; - } - return result; -} - - -export async function visibleMessages(user, messageID){ - -} - -export async function getMessageGroupAuthors(user, messageID){ - return getGroupsFromCallbacks(user, qb => { - return qb.select({uid: 'group'}).from('group_message_relationships') - .where('message', messageID).whereIn('status', ['host', 'publish']); - }); -} - -export async function getMessageGroupRecipients(user, messageID){ - return getGroupsFromCallbacks(user, qb => { - return qb.select({uid: 'group'}).from('group_message_relationships') - .where('message', messageID).where('status', 'recieve'); - }); -} - -/** - * @summary Renvoie simplement un groupe en fonction de son identifiant. - * @param {Object} user - Utilisateur effectuant la requête. - * @param {String} groupUID - Identifiant unique du groupe. - * @author manifold - * @rights super - */ -export const getGroup = (user, groupUID) => { - // Une sélection sur une table renvoie un tableau. - // Knex renvoie une promesse, qui se résout en le tableau sélectionné. - // On récupère son unique valeur, puisqu'on filtre sur l'identifiant unique. - return knex.select().from('groups').where('uid',groupUID).then(results => results [0]); -}; - -/** - * @summary Renvoie simplement un groupe simple en fonction de son identifiant. - * @param {Object} user - Utilisateur effectuant la requête. - * @param {String} groupUID - Identifiant unique du groupe. - * @author manifold - * @rights super - */ -export const getSimpleGroup = (user, groupUID) => { - return knex.select().from('simple_groups').where('uid',groupUID).then(results => results [0]); -}; - -/** - * @summary Renvoie simplement un meta groupe en fonction de son identifiant. - * @param {Object} user - Utilisateur effectuant la requête. - * @param {String} groupUID - Identifiant unique du groupe. - * @author manifold - * @rights super - */ -export const getMetaGroup = (user, groupUID) => { - return knex.select().from('meta_groups').where('uid',groupUID).then(results => results [0]); -}; - -/** - * @summary Refuse une requête d'un groupe voulant rejoindre un évènement - * @arg {Object} user - Représente l'utilisateur qui a effectué la requête. - * @arg {Int} requestID - L'id de la requête à refuser. - * @return {Promise(Boolean)} Vrai si l'opération a réussie; - * @rights admin(request.recipient) - */ -export async function denyGroupJoinEventRequest(user, requestID){ - await knex('group_join_event').where('id', requestID).del(); - return true; -} - -/** - * @summary Refuse une requête d'un groupe voulant rejoindre un évènement - * @arg {Object} user - Représente l'utilisateur qui a effectué la requête. - * @arg {Int} requestID - L'id de la requête à refuser. - * @return {Promise(Boolean)} Vrai si l'opération a réussie; - * @rights admin(request.recipient) - */ -export async function acceptGroupJoinEventRequest(user, requestID){ - let request = await knex('group_join_event').select().where('id', requestID); - if( !request) - return false; - await knex('group_join_event').where('id', requestID).del(); - let group = request[0].senderuid; - let event = request[0].eventuid; - await knex('group_participation').insert({ - group : group, - message : event, - status : "join" - }); - return; - -} - - -/** - * @summary Refuse une requête d'un groupe voulant rejoindre un évènement - * @arg {Object} user - Représente l'utilisateur qui a effectué la requête. - * @arg {Int} requestID - L'id de la requête à refuser. - * @return {Promise(Boolean)} Vrai si l'opération a réussie; - * @rights admin(request.recipient) - */ -export async function denyYourGroupHostEventRequest(user, requestID){ - await knex('your_group_host_event').where('id', requestID).del(); - return true; -} - -/** - * @summary Refuse une requête d'un groupe voulant rejoindre un évènement - * @arg {Object} user - Représente l'utilisateur qui a effectué la requête. - * @arg {Int} requestID - L'id de la requête à refuser. - * @return {Promise(Boolean)} Vrai si l'opération a réussie; - * @rights admin(request.recipient) - */ -export async function acceptYourGroupHostEventRequest(user, requestID){ - let request = await knex('your_group_host_event').select().where('id', requestID); - if( !request) - return false; - await knex('group_join_event').where('id', requestID).del(); - let group = request[0].recipient; - let event = request[0].eventuid; - await knex('group_message_relationships').insert({ - group : group, - message : event, - status : "host" - }); - return; - -} - -export function takeAdminRights(user, groupUID, justification){ - return knex('taken_rights').insert({ - user_uid : user.uid, - group_uid : groupUID, - justification : justification - }); -} - - -export function releaseAdminRights(user, groupUID){ - return knex('taken_rights').del().where('user_uid', user.uid).where('group_uid', groupUID); -} - -/** - * @summary Renvoie les membres d'un groupe quelquonque. - * @param {Object} user - Utilisateur effectuant la requête. - * @param {String} metaGroupUID - Identifiant unique du groupe. - * @return {Promise(List)} Une liste des uid de tous les membres du groupe - * @author akka vodol - * @rights member(metaGroupUID) - */ -export async function getGroupMemberUsers(user, GroupUID){ - let type = await list_selectors.getGroupType(user, GroupUID); - switch( type ){ - case "SimpleGroup": - return utilisateur.getMembers(GroupUID); - // return ["anatole.romon"]; - case "MetaGroup": - return getMetaGroupMemberUsers(user, GroupUID); - default: - return undefined; - } -} -/** - * @summary Renvoie les membres d'un meta groupe. - * @param {Object} user - Utilisateur effectuant la requête. - * @param {String} metaGroupUID - Identifiant unique du groupe. - * @return {Promise(List)} Une liste des uid de tous les membres du groupe - * @author akka vodol - * @rights member(metaGroupUID) - */ -export async function getMetaGroupMemberUsers(user, metaGroupUID){ - let member_group_list = await selectors.metaGroupMembers(user, metaGroupUID).then(cb => cb(knex)); - let members = []; - for(let memberGroup of await member_group_list){ - members = members.concat(getGroupMemberUsers(user, metaGroupUID)); - } -} - -export async function getSimpleGroupsFromCallbacks (user, selection){ - return knex.with('selection', selection).select("simple_groups.*").from("simple_groups") - .innerJoin('selection', function (){ - this.on('selection.uid', '=', 'simple_groups.uid'); - }); -} - -export async function getMetaGroupsFromCallbacks (user, selection){ - return knex.with('selection', selection).select().from("meta_groups") - .innerJoin('selection', function (){ - this.on('selection.uid', '=', 'meta_groups.uid'); - }); -} - -/** - * @summary Renvoie tous les groupes dans l'intersection définie par les callbacks - * @desc Cette fonction effectue une requête knex. Elle gère l'arête de parenté. - * @arg {Object} user - Représente l'utilisateur qui a effectué la requête. - * @arg {String} wantedType - Type de groupe voulu : `"simple"`, `"meta"` ou `"all"`. - * @return {Promise} Retour de requête knex. Liste de tous les groupes que l'utilisateur a le droit de voire. - * @rights user - */ -export async function getGroupsFromCallbacks(user, cbList){ - // console.log(cbList); - let all_simple_groups = await getSimpleGroupsFromCallbacks(user, cbList); - let all_meta_groups = await getMetaGroupsFromCallbacks(user, cbList); - return all_simple_groups.concat(all_meta_groups); -} - -/* - * réflexion sur une façon possible de gérer les utilisateurs sans en demander trop à LDAP - * Sans utilité pour le moment, ne faites pas attention - */ - -/* -function smartUserObject(user, uid){ - this.user = user; - this.uid = uid; - - this.resolution = {}; - let resolutionAlias = this.resolution; - - let attributeDictionnary = { - givenName : "givenName" - }; - - for(let attribute in attributeDictionnary){ - this[attribute] = function(){ - return new Promise((resolve, reject) => { - resolutionAlias[attributeDictionnary[attribute]] = resolve; - }); - }; - } - - this.resolve = async function(){ - let userObject = await renseignerSurUtilisateur(this.user, this.uid); - for(let attribute in this.resolution){ - this.resolution[attribute](userObject.attribute); - } - }; -} -*/ \ No newline at end of file diff --git a/src/graphql/connectors/list_selectors.js b/src/graphql/connectors/list_selectors.js deleted file mode 100644 index fdabecfe8823845c8d0233b13839021719575d32..0000000000000000000000000000000000000000 --- a/src/graphql/connectors/list_selectors.js +++ /dev/null @@ -1,87 +0,0 @@ -import knex from '../../../db/knex_router'; -import * as selectors from './selectors'; - -/** - * @summary Renvoie le type d'un groupe. - * @function - * @desc Parcours les BDD pour savoir dans laquelle se trouve le groupe ayant l'UID donné. - * Cette opération nécéssite un parcours de base de donnée, et il est préférable de ne pas - * sans servir si on a un autre moyend de connaitre le typed d'un groupe - * (Par exemple, si on dispose d'un retour de BDD pour ce groupe. Le champ 'type' indique alors son type.) - * @arg {Object} user - Objet contenant un attribut `uid` de type `string`. - * User représente l'utilisateur qui a effectué la requête. - * @arg {Object} groupUID - L'id du groupe dont on veut connaître le type. - * @return {Promise(String)} Un string représentant le type du groupe. - * Peut être "SimpleGroup" ou "MetaGroup". Renvoie `Undefined` si le groupe n'existe pas - * @rights super - */ -export const getGroupType = (user, groupUID) => { - return knex('simple_groups').select('uid').where('uid', groupUID).then( sg_res => { - if(sg_res) - return "SimpleGroup"; - return knex('meta_groups').select('uid').where('uid', groupUID).then(mg_res => { - if(mg_res) - return "MetaGroup"; - return undefined; - }); - }); -}; - -/** - * @summary renvoie tous les groupes sur lesquels un utilisateur a des droits de supervision - * @desc Cette fonction effectue des requêtes knex. Elle parcoure recursivement l'arbre de parenté, - * Et cela aurait été trop lourd de la faire renvoyer un callback définissant une requête. - * @arg {Object} user - Objet contenant un attribut `uid` de type `string`. - * User représente l'utilisateur qui a effectué la requête. - * @return {Promise(array)} Une liste des uid de tous les groupes que user supervise. - * @rights user - */ -export async function supervisedGroups(user){ - function difference(arr1, arr2){ - return arr1.filter( e => (arr2.indexOf(e) < 0)); - } - async function recursive_explorer(visited_groups, start_groups){ - let child_simple_groups = await knex.select('uid').distinct().from('simple_groups') - .whereIn('parent_uid', start_groups); - let child_meta_groups = await knex.select('union_uid').as('uid').distinct() - .from('meta_group_membership').where('status', 'admin') - .whereIn('member_uid', child_simple_groups); - let new_start_groups = difference(child_simple_groups.concat(child_meta_groups), visited_groups); - let new_visited_groups = visited_groups.concat(new_start_groups); - if(new_start_groups.length > 0) - return recursive_explorer(new_visited_groups, new_start_groups); - else - return new_visited_groups; - - } - let groups_with_admin = (await selectors.groupsWithAdmin(user))(knex); - return recursive_explorer([], groups_with_admin); -} - -/** - * @summary Renvoie tous les utilisateurs ayant des droits d'administrateur sur un groupe. - * @function - * @desc Les utilisateurs qui ont un droit d'administrateur sur un groupe simple sont ses administrateurs - * et les utilisateurs ayant droit d'admin sur son parent. - * Les utilisateurs ayant droit d'admin sur un meta-groupe sont les utilisateurs - * ayant droit d'admin sur un des groupes membres - * @arg {String} uid - L'uid du groupe dont on veut les administrateurs. - * @return {Promise} Retour de requête knex. Promise qui renvera une liste - * de tous les utilisateurs ayant droit d'admin sur le groupe - * rights member(groupUID) - */ -export async function usersWithAdminRights(user, groupUID){ - let group_type = await getGroupType(user, groupUID); - if(group_type == "SimpleGroup"){ - let added_admins = await knex.select('user_uid').from('taken_rights').where('group_uid', groupUID); - let admin_list = ["wilson.jallet", "louis.vanneau"]; // await listerAdministrateurs(user, groupUID); - return admin_list.concat( added_admins.map( o => o.user_uid) ); - }else{ - let mg_members = await selectors.metaGroupAdminMembers(user, groupUID)(knex); - let admin_users = []; - while(mg_members){ - admin_users = admin_users.concat(await usersWithAdminRights(mg_members.pop())); - } - return admin_users; - } -} diff --git a/src/graphql/connectors/selectors.js b/src/graphql/connectors/selectors.js deleted file mode 100644 index 3458d89d53517cd58e1390660e10681c2f278e1c..0000000000000000000000000000000000000000 --- a/src/graphql/connectors/selectors.js +++ /dev/null @@ -1,143 +0,0 @@ -/** - * @file Des callbacks qui sélectionnent un sous-ensemble de groupes. IN PROGRESS la doc aussi, ça se voit ^^ - * @author akka vodol - */ - -//hawkspar->akka grosses redondance avec le travail fait dans ldap/users.js, classe Open. -// Pas envisageable de redoubler le LDAP sur une BDD extérieure - -/** - * @summary Renvoie une liste des id de tous les groupes visibles par l'utilisateur - * @desc Cette fonction génère un callback qui créé une table contenant les uid de tous les groupes visibles - * @arg {Object} user - Objet contenant un attribut `uid` de type `string`. - * User représente l'utilisateur qui a effectué la requête. - * @return {Promise(Callback)} callback contruisant une requête knex pour une table de tous les id visibles. - * @rights user - */ -export async function visibleGroups(user){ - let group_ids = await user.ldap_access.listGroups(user.uid); - return function (global_query_builder){ - if (typeof group_ids == "undefined") - throw "invalid user"; - var membered_groups = qb => qb.select('uid').from('simple_groups').whereIn('uid', group_ids.concat(['kes'])); - var directly_visible_simple_groups = qb => qb.with('membered_groups', membered_groups) - .select('simple_groups.uid').from('simple_groups').distinct() - .innerJoin('membered_groups', - function () { - this.on('simple_groups.uid', '=', 'membered_groups.uid') - .orOn('simple_groups.parent_uid', '=', 'membered_groups.uid'); - } - ); - return directly_visible_simple_groups(global_query_builder); - }; -} - -export async function groupsWithMember(user){ - return null; -} - -export async function groupsWithSpeaker(user){ - return function (query_builder){ - return groupsWithAdmin(user); - }; -} - -export async function groupsWithAdmin(user){ - return function (query_builder){ - return ["kes"]; - }; -} - - -// Give a user, get the messages linked to that user - -export async function visibleAnnouncements(user){ - return query_builder => { - return query_builder; - }; -} - -export async function visibleEvents(user){ - return query_builder => { - return query_builder; - }; -} - -export async function messageHosts(user, messageID){ - return function(query_builder){ - return query_builder.select({uid : 'group'}).from('group_message_relationships') - .where('message', messageID).where('status', 'host'); - }; -} - -/** - * @summary Renvoie un callback qui génère tous les membres d'un meta-groupe. - * @param {Object} user - Utilisateur effectuant la requête. - * @param {String} metaGroupUID - Identifiant unique du groupe. - * @return {Promise(callback(groups))} a callback to build a query for the members of a group - * It doesn't need to be a promise, but I figure having all of my functions return promises is - * easier than keeping track of which functions do and do not return promises. - * @author akka vodol - * @rights member(metaGroupUID) - */ -export async function metaGroupMembers(user, metaGroupUID){ - return function(query_builder){ - return query_builder.distinct().select().from('groups') - .innerJoin('meta_group_membership', 'groups.uid', 'meta_group_membership.member_uid') - .where('meta_group_membership.union_uid', '=', metaGroupUID); - }; -} - -/** - * @summary Renvoie un callback qui génère tous les membres admin d'un meta-groupe. - * @param {Object} user - Utilisateur effectuant la requête. - * @param {String} metaGroupUID - Identifiant unique du groupe. - * @return {Promise(callback)} a callback to build a query for the admin members of a group - * @author akka vodol - * @rights member(metaGroupUID) - */ -export async function metaGroupAdminMembers(user, metaGroupUID){ - return function(query_builder){ - return query_builder.distinct().select().from('groups') - .innerJoin('meta_group_membership', 'groups.uid', 'meta_group_membership.member_uid') - .where('meta_group_membership.union_uid', '=', metaGroupUID) - .where('meta_group_membership.status', '=', 'admin'); - }; -} - -/** - * @summary Renvoie un callback de tous les messages reçus par le groupe - * @param {Object} user - Utilisateur effectuant la requête. - * @param {String} groupUID - Identifiant du groupe. - * @return {Promise(callback)} a callback to build a query for the admin members of a group - * @author akka vodol - * @rights admin(groupUID) - */ -export async function recievedMessages(user, groupUID){ - return function(qb){ - return qb.column({id : 'message'}).select().from('group_message_relationships').where('group', groupUID).where('status', 'receive'); - }; -} - -export async function callbackIntersection(callbackList){ - return function(query_builder){ - let cb = callbackList.pop(); - if(callbackList){ - let intersection = callbackIntersection(callbackList); - return function(query_builder){ - return query_builder.with('callback_set', cb) - .with('intersection', intersection) - .select('intersection.uid').from('intersection') - .innerJoin('callback_set', function(){ - this.on('callback_set.uid', '=', 'intersection.uid'); - }); - }; - }else{ - return cb; - } - }; -} - -export async function callbackUnion(callbackList){ - return null; -} \ No newline at end of file diff --git a/src/graphql/models/authorization.ts b/src/graphql/models/authorization.ts new file mode 100644 index 0000000000000000000000000000000000000000..97a18d7fc2e0cca908d445eac4191fc6a88301f4 --- /dev/null +++ b/src/graphql/models/authorization.ts @@ -0,0 +1,252 @@ +/** + * @file Fonctions qui gerent l'autorisation des utilisateurs + * @author ofacklam + * @memberof GraphQL + */ + +import { Tools, GroupSet, GroupCollection } from "./tools"; +import { User as UT } from '../../ldap/export/user' + +/* + * There are 6 levels of authorization + * none : doesn't know the group / the user exists + * connectedOrOnplatal : knows the group exists, can use TOL + * viewer : can see the group + * member : part of the group + * speaker : allowed to speak for the group + * admin : admin of the group + */ + +export class AuthorizationModel { + + /** + * @memberof GraphQL + * @class AuthorizationModel + * @summary Autorisation des utilisateurs. + * @classdesc Cette classe contient les méthodes d'autorisation de l'utilisateur dans le 'context'. + * @arg {string} uid - L'identifiant de l'utilisateur. + */ + constructor(uid: string) { + this.uid = uid; + } + + /** + * @memberof GraphQL + * @summary Fonction qui crée une nouvelle instance de AuthorizationModel et charge les données + * @arg {string} uid - Identifiant de l'utilisateur. + * @returns {Promise(AuthorizationModel)} Renvoie un nouveau AuthorizationModel avec cet utilisateur + * @async + * @static + */ + static async create(uid: string): Promise<AuthorizationModel> { + let model = new AuthorizationModel(uid); + console.log("constructing AuthorizationModel..."); + if(model.isAuthenticated()) { + console.log("model is authenticated. fetching data..."); + await model.fetchData(); + } + console.log("finished constructing AuthorizationModel"); + return model; + } + + protected uid: string; + protected viewerOf: GroupCollection; + protected memberOf: GroupCollection; + protected speakerOf: GroupCollection; + protected adminOf: GroupCollection; + + static PUBLICUSER = "public_user"; + static ONPLATALUSER = "public_onplatal_user"; + + /** + * @memberof GraphQL.AuthorizationModel# + * @function fetchData + * @summary Fonction qui recupere toutes les données pour populer les champs a partir de l'uid. + * @async + */ + async fetchData(): Promise<void> { + console.log("calling UT.peek from ldap connector (User Tool)...") + let data = await UT.peek(this.uid); + console.log("UT.peek returned with data:"); + //console.log(data); + + this.memberOf = await Tools.memberOf(data); + this.speakerOf = await Tools.speakerOf(data); + this.adminOf = await Tools.adminOf(data); + this.viewerOf = await Tools.viewerOf(this.memberOf); + //console.log(this.viewerOf); + } + + /** + * @memberof GraphQL.AuthorizationModel# + * @function getUid + * @summary Fonction qui renvoit l'identifiant de l'utilisateur + * @return {string} Renvoie l'uid de l'utilisateur du 'context' + */ + getUid(): string { + return this.uid; + } + + /** + * @memberof GraphQL.AuthorizationModel# + * @function isConnectedOrOnplatal + * @summary Fonction qui renvoit si l'utilisateur est connecté ou on-platal + * @return {boolean} Renvoie true si l'utilisateur est connecté ou on-platal + */ + isConnectedOrOnplatal(): boolean { + return (this.uid != AuthorizationModel.PUBLICUSER); + } + + /** + * @memberof GraphQL.AuthorizationModel# + * @function isAuthenticated + * @summary Fonction qui renvoit si l'utilisateur est authentifié + * @return {boolean} Renvoie true si l'utilisateur est authentifié + */ + isAuthenticated(): boolean { + console.log("running model.isAuthenticated..."); + return (this.uid != AuthorizationModel.PUBLICUSER && this.uid != AuthorizationModel.ONPLATALUSER); + } + + /** + * @memberof GraphQL.AuthorizationModel# + * @function isViewer + * @summary Fonction qui renvoit si l'utilisateur est viewer du groupe. + * @arg {string} gid - Identifiant du groupe. + * @return {boolean} Renvoie true si l'utilisateur est viewer du groupe. + */ + isViewer(gid: string): boolean { + //ensure uid is valid !!!!!!!! + if (this.isAuthenticated()) { + let groups = this.viewerOf; + return (groups.simpleGroups.has(gid) || groups.metaGroups.has(gid)); + } + return false; + } + + /** + * @memberof GraphQL.AuthorizationModel# + * @function groupsViewer + * @summary Fonction qui renvoit les groupes dont l'utilisateur est viewer. + * @return {GroupCollection} Renvoie la collection de groupes dont l'utilisateur est viewer. + */ + groupsViewer(): GroupCollection { + //ensure uid is valid !!!!!!!! + if (this.isAuthenticated()) { + return this.viewerOf; + } + return null; + } + + /** + * @memberof GraphQL.AuthorizationModel# + * @function isMember + * @summary Fonction qui renvoit si l'utilisateur est membre du groupe. + * @arg {string} gid - Identifiant du groupe. + * @return {boolean} Renvoie true si l'utilisateur est membre du groupe. + */ + isMember(gid: string): boolean { + //ensure uid is valid !!!!!!!! + if (this.isAuthenticated()) { + let groups = this.memberOf; + return (groups.simpleGroups.has(gid) || groups.metaGroups.has(gid)); + } + return false; + } + + /** + * @memberof GraphQL.AuthorizationModel# + * @function isMemberOr + * @summary Fonction qui renvoit si l'utilisateur est membre d'au moins un de ces groupes. + * @arg {GroupSet} groups - Ensemble de groupes. + * @return {boolean} Renvoie true si l'utilisateur est membre d'un des groupes + */ + isMemberOr(groups: GroupSet): boolean { + //ensure uid is valid !!!!!!!! + if (this.isAuthenticated()) { + let member = this.memberOf; + + for (let gid of groups) { + if (member.simpleGroups.has(gid) || member.metaGroups.has(gid)) { + return true; + } + } + } + return false; + } + + /** + * @memberof GraphQL.AuthorizationModel# + * @function groupsMember + * @summary Fonction qui renvoit les groupes dont l'utilisateur est member. + * @return {GroupCollection} Renvoie la collection de groupes dont l'utilisateur est member. + */ + groupsMember(): GroupCollection { + //ensure uid is valid !!!!!!!! + if (this.isAuthenticated()) { + return this.memberOf; + } + return null; + } + + /** + * @memberof GraphQL.AuthorizationModel# + * @function isSpeaker + * @summary Fonction qui renvoit si l'utilisateur est speaker du groupe. + * @arg {string} gid - Identifiant du groupe. + * @return {boolean} Renvoie true si l'utilisateur est speaker du groupe. + */ + isSpeaker(gid: string): boolean { + //ensure uid is valid !!!!!!!! + if (this.isAuthenticated()) { + let groups = this.speakerOf; + return (groups.simpleGroups.has(gid) || groups.metaGroups.has(gid)); + } + return false; + } + + /** + * @memberof GraphQL.AuthorizationModel# + * @function groupsSpeaker + * @summary Fonction qui renvoit les groupes dont l'utilisateur est speaker. + * @return {GroupCollection} Renvoie la collection de groupes dont l'utilisateur est speaker. + */ + groupsSpeaker(): GroupCollection { + //ensure uid is valid !!!!!!!! + if (this.isAuthenticated()) { + return this.speakerOf; + } + return null; + } + + /** + * @memberof GraphQL.AuthorizationModel# + * @function isAdmin + * @summary Fonction qui renvoit si l'utilisateur est admin du groupe. + * @arg {string} gid - Identifiant du groupe. + * @return {boolean} Renvoie true si l'utilisateur est admin du groupe. + */ + isAdmin(gid: string): boolean { + //ensure uid is valid !!!!!!!! + if (this.isAuthenticated()) { + let groups = this.adminOf; + return (groups.simpleGroups.has(gid) || groups.metaGroups.has(gid)); + } + return false; + } + + /** + * @memberof GraphQL.AuthorizationModel# + * @function groupsAdmin + * @summary Fonction qui renvoit les groupes dont l'utilisateur est admin. + * @return {GroupCollection} Renvoie la collection de groupes dont l'utilisateur est admin. + */ + groupsAdmin(): GroupCollection { + //ensure uid is valid !!!!!!!! + if (this.isAuthenticated()) { + return this.adminOf; + } + return null; + } + +} \ No newline at end of file diff --git a/src/graphql/models/groupModel.ts b/src/graphql/models/groupModel.ts new file mode 100644 index 0000000000000000000000000000000000000000..fe5467bffbbb067e58103968ab70956167c6221b --- /dev/null +++ b/src/graphql/models/groupModel.ts @@ -0,0 +1,408 @@ +/** + * @file Fonctions qui implémentent les requetes relatives aux groupes + * @author ofacklam + * @memberof GraphQL + */ + +import { Group, SimpleGroup, MetaGroup } from "../object_resolvers/groups"; +import { Request } from "../object_resolvers/requests"; +import { User } from "../object_resolvers/users"; +import knex from "../../../db/knex_router" +import { GroupCollection, GroupSet, Tools } from "./tools"; +import { createSubgroupArgs, editGroupArgs } from "../typeDefs/queries"; + +export class GroupModel { + + /** + * @memberof GraphQL + * @class GroupModel + * @summary Requetes relatives aux groupes. + * @classdesc Cette classe contient les méthodes implémentant les requetes relatives aux groupes. + * @arg {string} contextUser - L'identifiant de l'utilisateur du 'context' + */ + constructor(contextUser: string) { + this.contextUser = contextUser; + } + + protected contextUser: string; + + /** + * @memberof GraphQL.GroupModel# + * @function getGroup + * @summary Fonction qui renvoie un groupe donné. + * @arg {string} gid - Identifiant demandé. + * @return {Promise(Group)} Renvoie le groupe dont l'identifiant est 'gid' + * @async + * @rights connectedOrOnplatal + */ + async getGroup(gid: string): Promise<Group> { + let data = await knex.select('type').from('groups').where('gid', gid); + + if(data.length > 0) { + let type = data[0].type; + switch(type) { + case 'simple': + return new SimpleGroup(gid); + case 'meta': + return new MetaGroup(gid); + default: + return null; + } + } + return null; + } + + /** + * @memberof GraphQL.GroupModel# + * @function getSimpleGroup + * @summary Fonction qui renvoie un groupe simple donné. + * @arg {string} gid - Identifiant demandé. + * @return {Promise(SimpleGroup)} Renvoie le groupe dont l'identifiant est 'gid' + * @async + * @rights connectedOrOnplatal + */ + async getSimpleGroup(gid: string): Promise<SimpleGroup> { + return SimpleGroup.tryCreate(gid); + } + + /** + * @memberof GraphQL.GroupModel# + * @function getMetaGroup + * @summary Fonction qui renvoie un méta-groupe donné. + * @arg {string} gid - Identifiant demandé. + * @return {Promise(MetaGroup)} Renvoie le groupe dont l'identifiant est 'gid' + * @async + * @rights connectedOrOnplatal + */ + async getMetaGroup(gid: string): Promise<MetaGroup> { + return MetaGroup.tryCreate(gid); + } + + /** + * @memberof GraphQL.GroupModel# + * @function getAllGroupsByCollection + * @summary Fonction qui renvoie tous les groupes donnés en argument. + * @arg {GroupCollection} groups - Collection d'identifiants, supposés valides. + * @return {Group[]} Renvoie le tableau de groupes correspondant + * @rights connectedOrOnplatal + */ + getAllGroupsByCollection(groups: GroupCollection): Group[] { + let res: Group[] + + for(let s of groups.simpleGroups) { + res.push(new SimpleGroup(s)); + } + for(let m of groups.metaGroups) { + res.push(new MetaGroup(m)); + } + + return res; + } + + /** + * @memberof GraphQL.GroupModel# + * @function getAllGroupsBySet + * @summary Fonction qui renvoie tous les groupes donnés en argument. + * @arg {GroupSet} groups - Ensemble d'identifiants, supposés valides. + * @return {Promise(Group[])} Renvoie le tableau de groupes correspondant + * @async + * @rights connectedOrOnplatal + */ + async getAllGroupsBySet(groups: GroupSet): Promise<Group[]> { + let res: Group[] + + for(let g of groups) { + res.push(await this.getGroup(g)); + } + + return res; + } + + /** + * @memberof GraphQL.GroupModel# + * @function getAllSimpleGroups + * @summary Fonction qui renvoie tous les groupes simples donnés en argument. + * @arg {GroupSet} groups - Ensemble d'identifiants, supposés valides. + * @return {SimpleGroup[]} Renvoie le tableau de groupes correspondant + * @rights connectedOrOnplatal + */ + getAllSimpleGroups(groups: GroupSet): SimpleGroup[] { + let res: SimpleGroup[] + + for (let g of groups) { + res.push(new SimpleGroup(g)); + } + + return res; + } + + /** + * @memberof GraphQL.GroupModel# + * @function likeGroup + * @summary Fonction pour devenir sympathisant + * @arg {string} gid - Identifiant du groupe. + * @return {Promise(boolean)} Renvoie true si modification réussie. + * @async + * @rights viewer du groupe + */ + async likeGroup(gid: string): Promise<boolean> { + // Pour l'instant, ce n'est pas a implémenter... + throw "Not implemented"; + } + + /** + * @memberof GraphQL.GroupModel# + * @function unlikeGroup + * @summary Fonction pour devenir non-sympathisant + * @arg {string} gid - Identifiant du groupe. + * @return {Promise(boolean)} Renvoie true si modification réussie. + * @async + * @rights viewer du groupe + */ + async unlikeGroup(gid: string): Promise<boolean> { + // Pour l'instant, ce n'est pas a implémenter... + throw "Not implemented"; + } + + /** + * @memberof GraphQL.GroupModel# + * @function userLeaveGroup + * @summary Fonction pour quitter un groupe + * @arg {string} gid - Identifiant du groupe. + * @return {Promise(boolean)} Renvoie true si modification réussie. + * @async + * @rights member du groupe + */ + async userLeaveGroup(gid: string): Promise<boolean> { + throw "Not implemented"; + } + + /** + * @memberof GraphQL.GroupModel# + * @function writePostsSummary + * @summary Fonction pour écrire le résumé des posts. + * @arg {string} gid - Identifiant du groupe. + * @arg {string} content - Contenu du résumé. + * @return {Promise(boolean)} Renvoie true si modification réussie. + * @async + * @rights speaker du groupe + */ + async writePostsSummary(gid: string, content: string): Promise<boolean> { + throw "Not implemented"; + } + + /** + * @memberof GraphQL.GroupModel# + * @function createSubgroup + * @summary Fonction pour créer un sous-groupe + * @arg {createSubgroupArgs} args - Les arguments pour la création. + * @return {Promise(Group)} Renvoie le groupe créé. + * @async + * @rights admin du groupe + */ + async createSubgroup(args: createSubgroupArgs): Promise<Group> { + throw "Not implemented"; + //TODO : Vérifier que 'parent' est un groupe _simple_. + // TODO : finish + + /*if (typeof args.fromGroup != 'string') + throw "Illegal argument : parent_uid must be a non null string"; + if (typeof args.subName != 'string') + throw "Illegal argument : name must be a non null string"; + + let rasGID = await getAvailableGID(args.subGid); + + // TODO : appeller une fonction de LDAPUser pour y créer un groupe. + await knex('simple_groups').insert({ + uid: rasGID, + parent_uid: args.parent_uid, + createdAt: knex.fn.now(), + updatedAt: this.createdAt, + name: args.name, + website: args.website, + description: args.description, + school: args.school, + type: "simple" + }); + + return getGroup(rasGID);*/ + } + + /** + * @memberof GraphQL.GroupModel# + * @function getAvailableGID + * @summary Attribue un GID qui n'a pas encore été utilisé à un groupe + * @desc Escape le string initialGID si necessaire (ramené à de l'ASCII sans espace), puis si le gid est deja pris rajoute un n a la fin et reteste + * @arg {string} initialGID - Le gid initial du groupe, qu'il faut tester pour l'unicité. + * @return {Promise(string)} Renvoie le gid unique pour ce groupe. + * @rights authentified + * remarque : n'importe qui peut tester si un groupe existe en demandant a créer un groupe avec ce nom la et en regardant si + * son GID a été modifié. Je ne vois pas comment contourner ce problème, c'est donc une faille permanente (mineure) de sigma. + * // ofacklam -> savoir qu'un groupe existe est autorisé pour toute personne connectée ou on-platal et il faut etre connecté pour + * // utiliser cette fonction-ci donc il n'y a pas de probleme. + */ + async getAvailableGID(initialGID: string): Promise<string> { + let rasGID = Tools.escapeID(initialGID); + const res = knex.from('groups').where('gid', rasGID) + if (res.length == 0) { + return (rasGID); + } else { + return (this.getAvailableGID(rasGID + 'n')); + } + } + + /** + * @memberof GraphQL.GroupModel# + * @function makeAdmin + * @summary Fonction pour rendre un membre admin + * @arg {string} gid - Identifiant du groupe. + * @arg {string} uid - Identifiant de l'utilisateur. + * @return {Promise(User)} Renvoie l'utilisateur promu. + * @async + * @rights admin du groupe + */ + async makeAdmin(gid: string, uid: string): Promise<User> { + //TODO : vérifier que l'utilisateur est bien déja membre !! + throw "Not implemented"; + } + + /** + * @memberof GraphQL.GroupModel# + * @function unmakeAdmin + * @summary Fonction pour rendre un membre non-admin + * @arg {string} gid - Identifiant du groupe. + * @arg {string} uid - Identifiant de l'utilisateur. + * @return {Promise(User)} Renvoie l'utilisateur dé-promu. + * @async + * @rights admin du groupe + */ + async unmakeAdmin(gid: string, uid: string): Promise<User> { + //TODO : vérifier que l'utilisateur est bien déja membre !! + //TODO : relacher les droits admin (des groupes enfants) pris en étant admin. + throw "Not implemented"; + } + + /** + * @memberof GraphQL.GroupModel# + * @function makeSpeaker + * @summary Fonction pour rendre un membre speaker + * @arg {string} gid - Identifiant du groupe. + * @arg {string} uid - Identifiant de l'utilisateur. + * @return {Promise(User)} Renvoie l'utilisateur promu. + * @async + * @rights admin du groupe + */ + async makeSpeaker(gid: string, uid: string): Promise<User> { + //TODO : vérifier que l'utilisateur est bien déja membre !! + throw "Not implemented"; + } + + /** + * @memberof GraphQL.GroupModel# + * @function unmakeSpeaker + * @summary Fonction pour rendre un membre non-speaker + * @arg {string} gid - Identifiant du groupe. + * @arg {string} uid - Identifiant de l'utilisateur. + * @return {Promise(User)} Renvoie l'utilisateur dé-promu. + * @async + * @rights admin du groupe + */ + async unmakeSpeaker(gid: string, uid: string): Promise<User> { + //TODO : vérifier que l'utilisateur est bien déja membre !! + throw "Not implemented"; + } + + /** + * @memberof GraphQL.GroupModel# + * @function editGroup + * @summary Fonction pour modifier un groupe. + * @arg {editGroupArgs} args - Les arguments de la modification. + * @return {Promise(Group)} Renvoie le groupe modifié. + * @async + * @rights admin du groupe + */ + async editGroup(args: editGroupArgs): Promise<Group> { + throw "Not implemented"; + } + + /** + * @memberof GraphQL.GroupModel# + * @function addVisibilityEdge + * @summary Fonction pour ajouter une arete de visibilité. + * @arg {string} forGroup - Le groupe a rendre visible. + * @arg {string} visibleBy - Le groupe qui pourra voir. + * @return {Promise(boolean)} Renvoie true si modification réussie. + * @async + * @rights admin du groupe 'forGroup' + */ + async addVisibilityEdge(forGroup: string, visibleBy: string): Promise<boolean> { + throw "Not implemented"; + } + + /** + * @memberof GraphQL.GroupModel# + * @function removeVisibilityEdge + * @summary Fonction pour retirer une arete de visibilité. + * @arg {string} forGroup - Le groupe a rendre invisible. + * @arg {string} visibleBy - Le groupe qui ne pourra plus voir. + * @return {Promise(boolean)} Renvoie true si modification réussie. + * @async + * @rights admin du groupe 'forGroup' + */ + async removeVisibilityEdge(forGroup: string, visibleBy: string): Promise<boolean> { + throw "Not implemented"; + } + + /** + * @memberof GraphQL.GroupModel# + * @function removeUser + * @summary Fonction pour enlever un membre du groupe. + * @arg {string} gid - Identifiant du groupe. + * @arg {string} uid - Identifiant de l'utilisateur. + * @return {Promise(User)} Renvoie l'utilisateur. + * @async + * @rights admin du groupe + */ + async removeUser(gid: string, uid: string): Promise<User> { + throw "Not implemented"; + } + + /** + * @memberof GraphQL.GroupModel# + * @function takeAdminRights + * @summary Fonction pour prendre les droits admin du groupe. + * @arg {string} gid - Identifiant du groupe. + * @arg {string} uid - Identifiant de l'utilisateur qui prend les droits. + * @return {Promise(boolean)} Renvoie true si modification réussie. + * @async + * @rights supervisor du groupe + */ + async takeAdminRights(gid: string, uid: string): Promise<boolean> { + throw "Not implemented"; + + /*await knex('taken_rights').insert({ + user_uid: user.uid, + group_uid: groupUID, + justification: justification + }); + return true;*/ + } + + /** + * @memberof GraphQL.GroupModel# + * @function releaseAdminRights + * @summary Fonction pour relacher les droits admin du groupe. + * @arg {string} gid - Identifiant du groupe. + * @arg {string} uid - Identifiant de l'utilisateur qui relache les droits. + * @return {Promise(boolean)} Renvoie true si modification réussie. + * @async + * @rights supervisor du groupe + */ + async releaseAdminRights(gid: string, uid: string): Promise<boolean> { + //TODO : relacher récursivement + throw "Not implemented"; + + /*return knex('taken_rights').del().where('user_uid', user.uid).where('group_uid', groupUID);*/ + } + +} \ No newline at end of file diff --git a/src/graphql/models/messageModel.ts b/src/graphql/models/messageModel.ts new file mode 100644 index 0000000000000000000000000000000000000000..cde6f59b54b3d6e671168675bd08614e338b0bc5 --- /dev/null +++ b/src/graphql/models/messageModel.ts @@ -0,0 +1,512 @@ +/** + * @file Fonctions qui implémentent les requetes relatives aux messages + * @author ofacklam + * @memberof GraphQL + */ + +import { Announcement, Event, PrivatePost, Question, Answer, Message } from "../object_resolvers/messages"; +import knex from "../../../db/knex_router" +import { GroupCollection, GroupSet } from "./tools"; + +export class MessageModel { + + /** + * @memberof GraphQL + * @class MessageModel + * @summary Requetes relatives aux messages. + * @classdesc Cette classe contient les méthodes implémentant les requetes relatives aux messages. + * @arg {string} contextUser - L'identifiant de l'utilisateur du 'context' + */ + constructor(contextUser: string) { + this.contextUser = contextUser; + } + + protected contextUser: string; + + /** + * @memberof GraphQL.MessageModel# + * @function getAnnouncement + * @summary Fonction qui renvoie une annonce donnée. + * @arg {number} mid - Identifiant demandé. + * @return {Promise(Announcement)} Renvoie l'annonce dont l'identifiant est 'mid' + * @async + * @rights membre d'un groupe author ou d'un groupe recipient + */ + async getAnnouncement(mid: number): Promise<Announcement> { + return Announcement.tryCreate(mid); + } + + /** + * @memberof GraphQL.MessageModel# + * @function getEvent + * @summary Fonction qui renvoie un evenement donné. + * @arg {number} mid - Identifiant demandé. + * @return {Promise(Event)} Renvoie l'évenement dont l'identifiant est 'mid' + * @async + * @rights membre d'un groupe author ou d'un groupe recipient + */ + async getEvent(mid: number): Promise<Event> { + return Event.tryCreate(mid); + } + + /** + * @memberof GraphQL.MessageModel# + * @function getPrivatePost + * @summary Fonction qui renvoie un post privé donné. + * @arg {number} mid - Identifiant demandé. + * @return {Promise(PrivatePost)} Renvoie le post privé dont l'identifiant est 'mid' + * @async + * @rights membre du groupe recipient + */ + async getPrivatePost(mid: number): Promise<PrivatePost> { + return PrivatePost.tryCreate(mid); + } + + /** + * @memberof GraphQL.MessageModel# + * @function getQuestion + * @summary Fonction qui renvoie une question donnée. + * @arg {number} mid - Identifiant demandé. + * @return {Promise(Question)} Renvoie la question dont l'identifiant est 'mid' + * @async + * @rights viewer du groupe recipient + */ + async getQuestion(mid: number): Promise<Question> { + return Question.tryCreate(mid); + } + + /** + * @memberof GraphQL.MessageModel# + * @function getAnswer + * @summary Fonction qui renvoie une réponse donnée. + * @arg {number} mid - Identifiant demandé. + * @return {Promise(Answer)} Renvoie la réponse dont l'identifiant est 'mid' + * @async + * @rights viewer du groupe author + */ + async getAnswer(mid: number): Promise<Answer> { + return Answer.tryCreate(mid); + } + + /** + * @memberof GraphQL.MessageModel# + * @function getAllMessages + * @summary Fonction qui renvoie tous les messages visibles. + * @arg {GroupCollection} groups - Un ensemble d'identifiants, supposés valides. + * @return {Promise(Message[])} Renvoie tous les messages émis ou reçus par ces groupes + * @async + * @rights member of groups + */ + async getAllMessages(groups: GroupCollection): Promise<Message[]> { + throw "Not implemented"; + } + + /** + * @memberof GraphQL.MessageModel# + * @function getAllAnnouncements + * @summary Fonction qui renvoie toutes les annonces visibles. + * @arg {GroupCollection} groups - Un ensemble d'identifiants, supposés valides. + * @return {Promise(Announcement[])} Renvoie toutes les annonces émises ou reçues par ces groupes + * @async + * @rights member of groups + */ + async getAllAnnouncements(groups: GroupCollection): Promise<Announcement[]> { + throw "Not implemented"; + + /*let result = await knex.select().from('announcements').whereIn('id', selection); + result = result.concat( + await knex.select().from('events').whereIn('id', selection) + ); + for (let r of result) { + r.type = 'Announcement'; + } + return result;*/ + } + + /** + * @memberof GraphQL.MessageModel# + * @function getAllEvents + * @summary Fonction qui renvoie tous les évenements visibles. + * @arg {GroupCollection} groups - Un ensemble d'identifiants, supposés valides. + * @return {Promise(Event[])} Renvoie tous les évenements émis ou reçus par ces groupes + * @async + * @rights member of groups + */ + async getAllEvents(groups: GroupCollection): Promise<Event[]> { + throw "Not implemented"; + + /*let result = await knex.select().from('events').whereIn('id', selection); + for (let r of result) { + r.type = 'Announcement'; + } + return result;*/ + } + + /** + * @memberof GraphQL.MessageModel# + * @function getAllPrivatePosts + * @summary Fonction qui renvoie tous les posts privés visibles. + * @arg {GroupCollection} groups - Un ensemble d'identifiants, supposés valides. + * @return {Promise(PrivatePost[])} Renvoie tous les posts privés de ces groupes + * @async + * @rights member of groups + */ + async getAllPrivatePosts(groups: GroupCollection): Promise<PrivatePost[]> { + throw "Not implemented"; + + // let result = await knex('private_posts').select().whereIn('id', received_messages); + // for(let entry of result){ + // entry.type = "PrivatePost"; + // } + // return result; + } + + /** + * @memberof GraphQL.MessageModel# + * @function userParticipate + * @summary Fonction pour participer a un evenement. + * @arg {string} uid - Identifiant de l'utilisateur. + * @arg {number} mid - Identifiant de l'évenement. + * @return {Promise(boolean)} Renvoie true si modification réussie + * @async + * @rights member d'un groupe author ou recipient + */ + async userParticipate(uid: string, mid: number): Promise<boolean> { + throw "Not implemented"; + } + + /** + * @memberof GraphQL.MessageModel# + * @function userUnparticipate + * @summary Fonction pour ne plus participer a un evenement. + * @arg {string} uid - Identifiant de l'utilisateur. + * @arg {number} mid - Identifiant de l'évenement. + * @return {Promise(boolean)} Renvoie true si modification réussie + * @async + * @rights member d'un groupe author ou recipient + */ + async userUnparticipate(uid: string, mid: number): Promise<boolean> { + throw "Not implemented"; + } + + /** + * @memberof GraphQL.MessageModel# + * @function groupParticipate + * @summary Fonction pour participer a un evenement. + * @arg {string} gid - Identifiant du groupe. + * @arg {number} forEvent - Identifiant de l'évenement. + * @return {Promise(boolean)} Renvoie true si modification réussie + * @async + * @rights member d'un groupe author ou recipient + */ + async groupParticipate(gid: string, forEvent: number): Promise<boolean> { + throw "Not implemented"; + } + + /** + * @memberof GraphQL.MessageModel# + * @function groupUnparticipate + * @summary Fonction pour ne plus participer a un evenement. + * @arg {string} gid - Identifiant du groupe. + * @arg {number} forEvent - Identifiant de l'évenement. + * @return {Promise(boolean)} Renvoie true si modification réussie + * @async + * @rights member d'un groupe author ou recipient + */ + async groupUnparticipate(gid: string, forEvent: number): Promise<boolean> { + throw "Not implemented"; + } + + /** + * @memberof GraphQL.MessageModel# + * @function createQuestion + * @summary Fonction pour créer une question. + * @arg {string} gid - Identifiant du groupe. + * @arg {string} title - Titre. + * @arg {string} content - Contenu. + * @return {Promise(Question)} Renvoie la question créée. + * @async + * @rights viewer du groupe + */ + async createQuestion(gid: string, title: string, content: string): Promise<Question> { + throw "Not implemented"; + } + + /** + * @memberof GraphQL.MessageModel# + * @function editQuestion + * @summary Fonction pour modifier une question. + * @arg {number} mid - Identifiant de la question. + * @arg {string} title - Titre. + * @arg {string} content - Contenu. + * @return {Promise(Question)} Renvoie la question modifiée. + * @async + * @rights viewer du groupe et author de la question + */ + async editQuestion(mid: number, title: string, content: string): Promise<Question> { + throw "Not implemented"; + } + + /** + * @memberof GraphQL.MessageModel# + * @function removeQuestion + * @summary Fonction pour supprimer une question. + * @arg {number} mid - Identifiant de la question. + * @return {Promise(boolean)} Renvoie true si suppression réussie. + * @async + * @rights viewer du groupe et author de la question + */ + async removeQuestion(mid: number): Promise<boolean> { + throw "Not implemented"; + } + + /** + * @memberof GraphQL.MessageModel# + * @function createPrivatePost + * @summary Fonction pour créer un post privé. + * @arg {string} gid - Identifiant du groupe. + * @arg {string} title - Titre. + * @arg {string} content - Contenu. + * @return {Promise(PrivatePost)} Renvoie le post créé. + * @async + * @rights member du groupe + */ + async createPrivatePost(gid: string, title: string, content: string): Promise<PrivatePost> { + throw "Not implemented"; + } + + /** + * @memberof GraphQL.MessageModel# + * @function editPrivatePost + * @summary Fonction pour modifier un post privé. + * @arg {number} mid - Identifiant du post. + * @arg {string} title - Titre. + * @arg {string} content - Contenu. + * @return {Promise(PrivatePost)} Renvoie le post modifié. + * @async + * @rights member du groupe et author du post + */ + async editPrivatePost(mid: number, title: string, content: string): Promise<PrivatePost> { + throw "Not implemented"; + } + + /** + * @memberof GraphQL.MessageModel# + * @function removePrivatePost + * @summary Fonction pour supprimer un post privé. + * @arg {number} mid - Identifiant du post. + * @return {Promise(boolean)} Renvoie true si suppression réussie. + * @async + * @rights member du groupe et author du post + */ + async removePrivatePost(mid: number): Promise<boolean> { + throw "Not implemented"; + } + + /** + * @memberof GraphQL.MessageModel# + * @function createAnnouncement + * @summary Fonction pour créer une annonce. + * @arg {string} from_gid - Identifiant du groupe émetteur. + * @arg {GroupSet} to_groups - Les groupes destinataires. + * @arg {string} title - Titre. + * @arg {string} content - Contenu. + * @arg {number} event_mid - Identifiant de l'évenement. + * @return {Promise(Announcement)} Renvoie l'annonce créée. + * @async + * @rights speaker du groupe émetteur + */ + async createAnnouncement(from_gid: string, to_groups: GroupSet, title: string, content: string, event_mid: number): Promise<Announcement> { + throw "Not implemented"; + } + + /** + * @memberof GraphQL.MessageModel# + * @function editAnnouncement + * @summary Fonction pour modifier une annonce. + * @arg {number} mid - Identifiant de l'annonce. + * @arg {string} title - Titre. + * @arg {string} content - Contenu. + * @arg {number} event_mid - Identifiant de l'évenement. + * @return {Promise(Announcement)} Renvoie l'annonce modifiée. + * @async + * @rights speaker du groupe émetteur + */ + async editAnnouncement(mid: number, title: string, content: string, event_mid: number): Promise<Announcement> { + throw "Not implemented"; + } + + /** + * @memberof GraphQL.MessageModel# + * @function removeAnnouncement + * @summary Fonction pour supprimer une annonce. + * @arg {number} mid - Identifiant de l'annonce. + * @return {Promise(boolean)} Renvoie true si suppression réussie. + * @async + * @rights speaker du groupe + */ + async removeAnnouncement(mid: number): Promise<boolean> { + throw "Not implemented"; + } + + /** + * @memberof GraphQL.MessageModel# + * @function createEvent + * @summary Fonction pour créer un évenement. + * @arg {string} from_gid - Identifiant du groupe émetteur. + * @arg {GroupSet} to_groups - Les groupes destinataires. + * @arg {string} title - Titre. + * @arg {string} content - Contenu. + * @arg {string} location - Lieu. + * @arg {string} startTime - Date de début. + * @arg {string} endTime - Date de fin. + * @arg {number} announcement_mid - Identifiant de l'annonce. + * @return {Promise(Event)} Renvoie l'évenement créé. + * @async + * @rights speaker du groupe émetteur + */ + async createEvent(from_gid: string, to_groups: GroupSet, title: string, content: string, location: string, startTime: string, endTime: string, announcement_mid: number): Promise<Event> { + throw "Not implemented"; + } + + /** + * @memberof GraphQL.MessageModel# + * @function editEvent + * @summary Fonction pour modifier un evenement. + * @arg {number} mid - Identifiant de l'évenement. + * @arg {string} title - Titre. + * @arg {string} content - Contenu. + * @arg {string} location - Lieu. + * @arg {string} startTime - Date de début. + * @arg {string} endTime - Date de fin. + * @arg {number} announcement_mid - Identifiant de l'annonce. + * @return {Promise(Event)} Renvoie l'evenement modifié. + * @async + * @rights speaker du groupe émetteur + */ + async editEvent(mid: number, title: string, content: string, location: string, startTime: string, endTime: string, announcement_mid: number): Promise<Event> { + throw "Not implemented"; + } + + /** + * @memberof GraphQL.MessageModel# + * @function removeEvent + * @summary Fonction pour supprimer un évenement. + * @arg {number} mid - Identifiant de l'évenement. + * @return {Promise(boolean)} Renvoie true si suppression réussie. + * @async + * @rights speaker du groupe + */ + async removeEvent(mid: number): Promise<boolean> { + throw "Not implemented"; + } + + /** + * @memberof GraphQL.MessageModel# + * @function createAnswer + * @summary Fonction pour créer une réponse. + * @arg {number} mid - Identifiant de la question. + * @arg {string} title - Titre. + * @arg {string} content - Contenu. + * @return {Promise(Answer)} Renvoie la réponse créée. + * @async + * @rights speaker du groupe + */ + async createAnswer(mid: number, title: string, content: string): Promise<Answer> { + throw "Not implemented"; + } + + /** + * @memberof GraphQL.MessageModel# + * @function editAnswer + * @summary Fonction pour modifier une réponse. + * @arg {number} mid - Identifiant de la réponse. + * @arg {string} title - Titre. + * @arg {string} content - Contenu. + * @return {Promise(Answer)} Renvoie la réponse modifiée. + * @async + * @rights speaker du groupe + */ + async editAnswer(mid: number, title: string, content: string): Promise<Answer> { + throw "Not implemented"; + } + + /** + * @memberof GraphQL.MessageModel# + * @function removeAnswer + * @summary Fonction pour supprimer une réponse. + * @arg {number} mid - Identifiant de la réponse. + * @return {Promise(boolean)} Renvoie true si suppression réussie. + * @async + * @rights speaker du groupe + */ + async removeAnswer(mid: number): Promise<boolean> { + throw "Not implemented"; + } + + /** + * @memberof GraphQL.MessageModel# + * @function censorQuestion + * @summary Fonction pour censurer une question. + * @arg {number} mid - Identifiant du message. + * @return {Promise(boolean)} Renvoie true si suppression réussie. + * @async + * @rights admin du groupe + */ + async censorQuestion(mid: number): Promise<boolean> { + throw "Not implemented"; + } + + /** + * @memberof GraphQL.MessageModel# + * @function censorAnswer + * @summary Fonction pour censurer une réponse. + * @arg {number} mid - Identifiant du message. + * @return {Promise(boolean)} Renvoie true si suppression réussie. + * @async + * @rights admin du groupe + */ + async censorAnswer(mid: number): Promise<boolean> { + throw "Not implemented"; + } + + /** + * @memberof GraphQL.MessageModel# + * @function censorPrivatePost + * @summary Fonction pour censurer un post privé. + * @arg {number} mid - Identifiant du message. + * @return {Promise(boolean)} Renvoie true si suppression réussie. + * @async + * @rights admin du groupe + */ + async censorPrivatePost(mid: number): Promise<boolean> { + throw "Not implemented"; + } + + /** + * @memberof GraphQL.MessageModel# + * @function censorAnnouncement + * @summary Fonction pour censurer une annonce. + * @arg {number} mid - Identifiant du message. + * @return {Promise(boolean)} Renvoie true si suppression réussie. + * @async + * @rights admin du groupe ???? (lequel ??? -> le groupe author?) + */ + async censorAnnouncement(mid: number): Promise<boolean> { + throw "Not implemented"; + } + + /** + * @memberof GraphQL.MessageModel# + * @function censorEvent + * @summary Fonction pour censurer un évenement. + * @arg {number} mid - Identifiant du message. + * @return {Promise(boolean)} Renvoie true si suppression réussie. + * @async + * @rights admin du groupe ???? (lequel ??? -> le groupe author?) + */ + async censorEvent(mid: number): Promise<boolean> { + throw "Not implemented"; + } + +} \ No newline at end of file diff --git a/src/graphql/models/requestModel.ts b/src/graphql/models/requestModel.ts new file mode 100644 index 0000000000000000000000000000000000000000..8a2568e5f2e6c9b0d0b8caeef4a095ef75a9322a --- /dev/null +++ b/src/graphql/models/requestModel.ts @@ -0,0 +1,302 @@ +/** + * @file Fonctions qui implémentent les requetes relatives aux requetes + * @author ofacklam + * @memberof GraphQL + */ + +import { Group, SimpleGroup, MetaGroup } from "../object_resolvers/groups"; +import { Request, UserJoinGroup, GroupJoinMetagroup, GroupCoauthorEvent } from "../object_resolvers/requests"; +import { User } from "../object_resolvers/users"; +import knex from "../../../db/knex_router" +import { GroupCollection, GroupSet } from "./tools"; +import { createSubgroupArgs, editGroupArgs } from "../typeDefs/queries"; + +export class RequestModel { + + /** + * @memberof GraphQL + * @class RequestModel + * @summary Requetes relatives aux requetes. + * @classdesc Cette classe contient les méthodes implémentant les requetes relatives aux requetes. + * @arg {string} contextUser - L'identifiant de l'utilisateur du 'context' + */ + constructor(contextUser: string) { + this.contextUser = contextUser; + } + + protected contextUser: string; + + /** + * @memberof GraphQL.RequestModel# + * @function getUserJoinGroupRequest + * @summary Fonction qui renvoie une requete UserJoinGroup donnée. + * @arg {number} rid - Identifiant demandé. + * @return {Promise(UserJoinGroup)} Renvoie la requete dont l'identifiant est 'rid' + * @async + * @rights admin du groupe destinaire ou le user émetteur + */ + async getUserJoinGroupRequest(rid: number): Promise<UserJoinGroup> { + return UserJoinGroup.tryCreate(rid); + } + + /** + * @memberof GraphQL.RequestModel# + * @function getGroupJoinMetagroupRequest + * @summary Fonction qui renvoie une requete GroupJoinMetagroup donnée. + * @arg {number} rid - Identifiant demandé. + * @return {Promise(GroupJoinMetagroup)} Renvoie la requete dont l'identifiant est 'rid' + * @async + * @rights admin du groupe émetteur ou destinataire + */ + async getGroupJoinMetagroupRequest(rid: number): Promise<GroupJoinMetagroup> { + return GroupJoinMetagroup.tryCreate(rid); + } + + /** + * @memberof GraphQL.RequestModel# + * @function getGroupCoauthorEventRequest + * @summary Fonction qui renvoie une requete GroupCoauthorEvent donnée. + * @arg {number} rid - Identifiant demandé. + * @return {Promise(GroupCoauthorEvent)} Renvoie la requete dont l'identifiant est 'rid' + * @async + * @rights admin du groupe émetteur ou destinataire + */ + async getGroupCoauthorEventRequest(rid: number): Promise<GroupCoauthorEvent> { + return GroupCoauthorEvent.tryCreate(rid); + } + + /** + * @memberof GraphQL.RequestModel# + * @function getRequestsToGroup + * @summary Fonction qui renvoie toutes les requetes destinées a ce groupe. + * @arg {string} gid - Identifiant du groupe. + * @return {Promise(Request[])} Renvoie la liste des requetes destinées a ce groupe. + * @async + * @rights admin du groupe + */ + async getRequestsToGroup(gid: string): Promise<Request[]> { + let r1 = await this.getUserJoinGroupRequestsToGroup(gid); + let r2 = await this.getGroupJoinMetagroupRequestsToGroup(gid); + let r3 = await this.getGroupCoauthorEventRequestsToGroup(gid); + + let r: Request[]; + return r.concat(r1, r2, r3); + } + + /** + * @memberof GraphQL.RequestModel# + * @function getUserJoinGroupRequestsToGroup + * @summary Fonction qui renvoie toutes les requetes UserJoinGroup destinées a ce groupe. + * @arg {string} gid - Identifiant du groupe. + * @return {Promise(UserJoinGroup[])} Renvoie la liste des requetes destinées a ce groupe. + * @async + * @rights admin du groupe + */ + async getUserJoinGroupRequestsToGroup(gid: string): Promise<UserJoinGroup[]> { + throw "Not implemented"; + + // CODE A MODIFIER + /*let result = knex.select('id', 'useruid', 'message').from('user_join_group') + .where('recipient', recipient.gid); + return result.map(obj => { + obj.type = "UserJoinGroup"; + return obj; + });*/ + } + + /** + * @memberof GraphQL.RequestModel# + * @function getGroupJoinMetagroupRequestsToGroup + * @summary Fonction qui renvoie toutes les requetes GroupJoinMetagroup destinées a ce groupe. + * @arg {string} gid - Identifiant du groupe. + * @return {Promise(GroupJoinMetagroup[])} Renvoie la liste des requetes destinées a ce groupe. + * @async + * @rights admin du groupe + */ + async getGroupJoinMetagroupRequestsToGroup(gid: string): Promise<GroupJoinMetagroup[]> { + throw "Not implemented"; + } + + /** + * @memberof GraphQL.RequestModel# + * @function getGroupCoauthorEventRequestsToGroup + * @summary Fonction qui renvoie toutes les requetes GroupCoauthorEvent destinées a ce groupe. + * @arg {string} gid - Identifiant du groupe. + * @return {Promise(GroupCoauthorEvent[])} Renvoie la liste des requetes destinées a ce groupe. + * @async + * @rights admin du groupe + */ + async getGroupCoauthorEventRequestsToGroup(gid: string): Promise<GroupCoauthorEvent[]> { + throw "Not implemented"; + + //CODE A MODIFIER + /*let result = await knex.select('id', 'senderuid', 'eventuid', 'message').from('group_join_event') + .where('recipient', recipient.gid); + return result.map(obj => { + obj.type = "GroupJoinEvent"; + return obj; + });*/ + + /*let result = await knex.select('id', 'senderuid', 'eventuid', 'message').from('your_group_host_event') + .where('recipient', recipient.gid); + return result.map(obj => { + obj.type = "YourGroupHostEvent"; + return obj; + });*/ + } + + /** + * @memberof GraphQL.RequestModel# + * @function userRequestJoinGroup + * @summary Fonction pour demander a devenir membre + * @arg {string} gid - Identifiant du groupe. + * @arg {string} comment - Commentaire supplémentaire + * @return {Promise(UserJoinGroup)} Renvoie la requete créée. + * @async + * @rights viewer du groupe + */ + async userRequestJoinGroup(gid: string, comment: string): Promise<UserJoinGroup> { + throw "Not implemented"; + } + + /** + * @memberof GraphQL.RequestModel# + * @function groupRequestCoauthorEvent + * @summary Fonction pour demander a co-organiser un évenement + * @arg {string} from_gid - Identifiant du groupe émetteur. + * @arg {string} to_gid - Identifiant du groupe destinataire. + * @arg {number} event_mid - Identifiant de l'évenement considéré. + * @arg {string} comment - Commentaire supplémentaire. + * @return {Promise(GroupCoauthorEvent)} Renvoie la requete créée. + * @async + * @rights speaker du groupe émetteur + */ + async groupRequestCoauthorEvent(from_gid: string, to_gid: string, event_mid: number, comment: string): Promise<GroupCoauthorEvent> { + //TODO : Vérifier que l'évenement est bien organisé par le groupe destinataire (to_gid) !!! => sinon erreur + throw "Not implemented"; + } + + /** + * @memberof GraphQL.RequestModel# + * @function groupRequestJoinMetagroup + * @summary Fonction pour demander a rejoindre un méta-groupe + * @arg {string} from_gid - Identifiant du groupe émetteur. + * @arg {string} to_gid - Identifiant du méta-groupe destinataire. + * @arg {string} comment - Commentaire supplémentaire. + * @return {Promise(GroupJoinMetagroup)} Renvoie la requete créée. + * @async + * @rights admin du groupe émetteur + */ + async groupRequestJoinMetagroup(from_gid: string, to_gid: string, comment: string): Promise<GroupJoinMetagroup> { + throw "Not implemented"; + } + + /** + * @memberof GraphQL.RequestModel# + * @function acceptUserJoinRequest + * @summary Fonction pour accepter une UserJoinGroup + * @arg {UserJoinGroup} req - L'objet requete. + * @arg {string} comment - Commentaire supplémentaire. + * @return {Promise(boolean)} Renvoie true si modification réussie. + * @async + * @rights admin du groupe destinataire + */ + async acceptUserJoinRequest(req: UserJoinGroup, comment: string): Promise<boolean> { + //"comment" a envoyer par mail automatique + throw "Not implemented"; + } + + /** + * @memberof GraphQL.RequestModel# + * @function refuseUserJoinRequest + * @summary Fonction pour refuser une UserJoinGroup + * @arg {UserJoinGroup} req - L'objet requete. + * @arg {string} comment - Commentaire supplémentaire. + * @return {Promise(boolean)} Renvoie true si modification réussie. + * @async + * @rights admin du groupe destinataire + */ + async refuseUserJoinRequest(req: UserJoinGroup, comment: string): Promise<boolean> { + //"comment" a envoyer par mail automatique + throw "Not implemented"; + } + + /** + * @memberof GraphQL.RequestModel# + * @function acceptGroupJoinRequest + * @summary Fonction pour accepter une GroupJoinMetagroup + * @arg {GroupJoinMetagroup} req - L'objet requete. + * @arg {string} comment - Commentaire supplémentaire. + * @return {Promise(boolean)} Renvoie true si modification réussie. + * @async + * @rights admin du groupe destinataire + */ + async acceptGroupJoinRequest(req: GroupJoinMetagroup, comment: string): Promise<boolean> { + //"comment" a envoyer par mail automatique + throw "Not implemented"; + } + + /** + * @memberof GraphQL.RequestModel# + * @function refuseGroupJoinRequest + * @summary Fonction pour refuser une GroupJoinMetagroup + * @arg {GroupJoinMetagroup} req - L'objet requete. + * @arg {string} comment - Commentaire supplémentaire. + * @return {Promise(boolean)} Renvoie true si modification réussie. + * @async + * @rights admin du groupe destinataire + */ + async refuseGroupJoinRequest(req: GroupJoinMetagroup, comment: string): Promise<boolean> { + //"comment" a envoyer par mail automatique + throw "Not implemented"; + } + + /** + * @memberof GraphQL.RequestModel# + * @function acceptGroupCoauthorEventRequest + * @summary Fonction pour accepter une GroupCoauthorEvent + * @arg {GroupCoauthorEvent} req - L'objet requete. + * @arg {string} comment - Commentaire supplémentaire. + * @return {Promise(boolean)} Renvoie true si modification réussie. + * @async + * @rights admin du groupe destinataire + */ + async acceptGroupCoauthorEventRequest(req: GroupCoauthorEvent, comment: string): Promise<boolean> { + //"comment" a envoyer par mail automatique + throw "Not implemented"; + + /*let request = await knex('group_join_event').select().where('id', requestID); + if (!request.length) + return false; + + let group = request[0].senderuid; + let event = request[0].eventuid; + await knex('group_participation').insert({ + group: group, + message: event, + status: "join" + }); + await knex('group_join_event').where('id', requestID).del(); + return;*/ + } + + /** + * @memberof GraphQL.RequestModel# + * @function refuseGroupCoauthorEventRequest + * @summary Fonction pour refuser une GroupCoauthorEvent + * @arg {GroupCoauthorEvent} req - L'objet requete. + * @arg {string} comment - Commentaire supplémentaire. + * @return {Promise(boolean)} Renvoie true si modification réussie. + * @async + * @rights admin du groupe destinataire + */ + async refuseGroupCoauthorEventRequest(req: GroupCoauthorEvent, comment: string): Promise<boolean> { + //"comment" a envoyer par mail automatique + throw "Not implemented"; + + /*await knex('group_join_event').where('id', requestID).del(); + //KNEX RENVOIE LE NOMBRE DE LIGNES AFFECTÉES -> il faut l'utiliser pour la valeur de retour + return true;*/ + } + +} \ No newline at end of file diff --git a/src/graphql/models/tools.ts b/src/graphql/models/tools.ts new file mode 100644 index 0000000000000000000000000000000000000000..26d0f8b9357507a2abd7c3edc7b7c763c13ecb43 --- /dev/null +++ b/src/graphql/models/tools.ts @@ -0,0 +1,243 @@ +/** + * Namespace qui regroupe toutes les classes et toutes les fonctions qui permettent l'implémentation des resolvers du schéma GraphQL + * @namespace GraphQL + */ + +/** + * @file Fonctions génériques de recherche dans la BDD et le LDAP réutilisables dans l'authorization et/ou dans les resolvers + * @author ofacklam + * @memberof GraphQL + */ + +import { userData } from '../../ldap/export/user' +import { groupData, Group as GT } from '../../ldap/export/group'; +import knex from '../../../db/knex_router'; + +export class GroupSet extends Set<string> { + addList(l: string[]) { + for(let elt of l) this.add(elt); + } +} +export interface GroupCollection { + simpleGroups: GroupSet, + metaGroups: GroupSet +} + +export class Tools { + + /** + * @memberof GraphQL + * @class Tools + * @summary Outils intermédiaires LDAP / BDD. + * @classdesc Cette classe contient des fonctions intermédiaires de recherche dans le LDAP / la BDD. + */ + constructor() { } + + /** + * @memberof GraphQL + * @summary Fonction qui escape l'id donné en argument + * @arg {string} id - L'identifiant a escape. + * @return {Promise(Group)} Renvoie l'identifiant escapé. + * @static + */ + static escapeID(id: string) { + return String(id).replace(' ', '_').replace(/\W/g, '').toLowerCase(); //the REGEXP matches all non-word characters (\W) in a global search (/../g) + } + + /** + * @memberof GraphQL + * @summary Fonction qui renvoit l'union de deux ensembles + * @arg {GroupSet} A - Le premier ensemble. + * @arg {GroupSet} B - Le deuxieme ensemble. + * @return {Promise(GroupSet)} Renvoie un nouveau GroupSet contenant l'union de A et B. + * @static + */ + static union(A: GroupSet, B: GroupSet): GroupSet { + let union = new GroupSet(A); + for(let b of B) { + union.add(b); + } + return union; + } + + /** + * @memberof GraphQL + * @summary Fonction qui renvoit tous les groupes simples dont le user est membre. + * @arg {userData} data - Données de l'utilisateur. + * @return {Promise(GroupSet)} Renvoie un GroupSet contenant le nom des groupes simples. + * @static + * @async + */ + static async memberOfSimple(data: userData): Promise<GroupSet> { + //Do a DFS from data.members to find all parents + //return Tools.DFS(data.members, 'parent'); + + //No need to do DFS + return new GroupSet(data.members); + } + + /** + * @memberof GraphQL + * @summary Fonction qui renvoit tous les groupes simples dont le user est speaker. + * @arg {userData} data - Données de l'utilisateur. + * @return {Promise(GroupSet)} Renvoie un GroupSet contenant le nom des groupes simples. + * @static + * @async + */ + static async speakerOfSimple(data: userData): Promise<GroupSet> { + return new GroupSet(data.speakers); + } + + /** + * @memberof GraphQL + * @summary Fonction qui renvoit tous les groupes simples dont le user est administrateur. + * @arg {userData} data - Données de l'utilisateur. + * @return {Promise(GroupSet)} Renvoie un GroupSet contenant le nom des groupes simples. + * @static + * @async + */ + static async adminOfSimple(data: userData): Promise<GroupSet> { + //Do a DFS from data.admins to find all children + //return Tools.DFS(data.admins, 'child'); + + //No need to do DFS + return new GroupSet(data.admins); + } + + /** + * @memberof GraphQL + * @summary Fonction qui renvoit tous les méta-groupes dont ces groupes sont membres. + * @arg {GroupSet} groups - Un ensemble de gid des groupes a considérer. + * @return {Promise(GroupSet)} Renvoie un GroupSet contenant le nom des méta-groupes. + * @static + * @async + */ + static async metaGroupsOfGroups(groups: GroupSet): Promise<GroupSet> { + let metas = await knex.select('meta_group_gid').from('metagroup_memberships').whereIn('simple_group_gid', [...groups]); + return new GroupSet(metas.map( elt => { + return elt.meta_group_gid; + })); + } + + /** + * @memberof GraphQL + * @summary Fonction qui renvoit tous les groupes (simples ou méta) dont le user est membre. + * @arg {userData} data - Données de l'utilisateur. + * @return {Promise(GroupCollection)} Renvoie une GroupCollection contenant le nom des groupes. + * @static + * @async + */ + static async memberOf(data: userData): Promise<GroupCollection> { + let simple = await Tools.memberOfSimple(data); + return { simpleGroups: simple, metaGroups: await Tools.metaGroupsOfGroups(simple) }; + } + + /** + * @memberof GraphQL + * @summary Fonction qui renvoit tous les groupes (simples ou méta) dont le user est speaker. + * @arg {userData} data - Données de l'utilisateur. + * @return {Promise(GroupCollection)} Renvoie une GroupCollection contenant le nom des groupes. + * @static + * @async + */ + static async speakerOf(data: userData): Promise<GroupCollection> { + let simple = await Tools.speakerOfSimple(data); + return { simpleGroups: simple, metaGroups: await Tools.metaGroupsOfGroups(simple) }; + } + + /** + * @memberof GraphQL + * @summary Fonction qui renvoit tous les groupes (simples ou méta) dont le user est administrateur. + * @arg {userData} data - Données de l'utilisateur. + * @return {Promise(GroupCollection)} Renvoie une GroupCollection contenant le nom des groupes. + * @static + * @async + */ + static async adminOf(data: userData): Promise<GroupCollection> { + let simple = await Tools.adminOfSimple(data); + return { simpleGroups: simple, metaGroups: await Tools.metaGroupsOfGroups(simple) }; + } + + /** + * @memberof GraphQL + * @summary Fonction qui fait un parcours en profondeur du graphe a partir de 'roots' et renvoie la liste de tous les noeuds dans la direction donnée. + * @arg {string[]} roots - Identifiants des groupes racine, supposés valides. + * @arg {'parent'|'child'} direction - Direction de la recherche. 'parent' cherche tous les noeuds ascendants. 'child' cherche tous les noeuds descendants. + * @return {Promise(GroupSet)} Renvoie un ensemble contenant le nom des groupes dans cette direction. + * @static + * @async + */ + static async DFS(roots : string[], direction : 'parent'|'child'): Promise<GroupSet> { + let stack = roots.slice(); + let visited = {}; + let res = new GroupSet(); + + while(stack.length > 0) { + let gid = stack.pop(); + if(visited[gid] !== true) { + visited[gid] = true; + res.add(gid); + + //console.log(gid); + if(direction === 'child') { + let gd = new groupData(); + gd.parents = [gid]; //on cherche les enfants, ie tous les groupes qui ont `gid` comme parent + stack.push(...await GT.search(gd)); + } + else { + let data = await GT.peek(gid); + stack.push(...data.parents) + } + + } + } + + return res; + } + + /** + * @memberof GraphQL + * @summary Fonction qui renvoie la liste des enfants des noeuds de `roots`. + * @arg {GroupSet} roots - Identifiants des groupes racine, supposés valides. + * @return {Promise(string[])} Renvoie une liste contenant le nom des groupes enfants (!! doublons !!). + * @static + * @async + */ + static async oneDownSearch(roots: GroupSet): Promise<string[]> { + let res = []; + + for(let r of roots) { + let gd = new groupData(); + gd.parents = [r]; //on cherche les enfants, ie tous les groupes qui ont `r` comme parent + res.push(...await GT.search(gd)); + } + + return res; + } + + /** + * @memberof GraphQL + * @summary Fonction qui renvoie tous les groupes dont l'utilisateur est viewer. + * @desc Utilise {@link Tools.oneDownSearch} pour avoir la profondeur 1 des arbres enracinés en chacun des groupes dont il est membre. + * @arg {GroupCollection} groups - Groupes dont le user est membre hérité. + * @return {Promise(GroupCollection)} Renvoie une GroupCollection contenant le nom des groupes. + * @static + * @async + */ + static async viewerOf(groups: GroupCollection): Promise<GroupCollection> { + let simple = groups.simpleGroups; + + //Ajouter les groupes enfants + simple.addList(await Tools.oneDownSearch(simple)); + + //Ajouter les groupes simples qui sont dans ses métagroupes + let s = await knex.select('simple_group_gid').from('metagroup_memberships').whereIn('meta_group_gid', [...groups.metaGroups]); + simple.addList(s.map( elt => { + return elt.simple_group_gid; + })); + + //TODO : rajouter les VisibilityEdges + + return { simpleGroups: simple, metaGroups: await Tools.metaGroupsOfGroups(simple) }; + } +} \ No newline at end of file diff --git a/src/graphql/models/userModel.ts b/src/graphql/models/userModel.ts new file mode 100644 index 0000000000000000000000000000000000000000..cad226216294b834df87910e00b5780554730217 --- /dev/null +++ b/src/graphql/models/userModel.ts @@ -0,0 +1,105 @@ +/** + * @file Fonctions qui implémentent les requetes relatives aux utilisateurs + * @author ofacklam + * @memberof GraphQL + */ + +import { User } from "../object_resolvers/users"; +import { User as UT, userData } from "../../ldap/export/user" +import { searchTOLArgs, editProfileArgs } from "../typeDefs/queries"; +import { ApolloError } from "apollo-server-core"; + +export class UserModel { + + /** + * @memberof GraphQL + * @class UserModel + * @summary Requetes relatives aux utilisateurs. + * @classdesc Cette classe contient les méthodes implémentant les requetes relatives aux utilisateurs. + * @arg {string} contextUser - L'identifiant de l'utilisateur du 'context' + */ + constructor(contextUser: string) { + this.contextUser = contextUser; + } + + protected contextUser: string; + + /** + * @memberof GraphQL.UserModel# + * @function getUser + * @summary Fonction qui renvoit un utilisateur donné. + * @arg {string} uid - Identifiant demandé. + * @return {Promise(User)} Renvoie l'utilisateur dont l'identifiant est 'uid' + * @async + * @rights connectedOrOnplatal + */ + async getUser(uid: string): Promise<User> { + return User.tryCreate(uid); + } + + /** + * @memberof GraphQL.UserModel# + * @function searchTOL + * @summary Fonction qui recherche dans le TOL + * @arg {searchTOLArgs} args - les données de recherche + * @return {Promise(User[])} Renvoie une liste d'utilisateurs + * @async + * @rights connectedOrOnplatal + */ + async searchTOL(args: searchTOLArgs): Promise<User[]> { + //TODO : correctly handle groups (in LDAP, a member can be stored as member, speaker or admin...) + + const searchData = new userData(); + searchData.givenName = args.givenName; + searchData.lastName = args.lastName; + searchData.nickname = args.nickname; + searchData.nationality = args.nationality; + searchData.members = args.groups; + searchData.phone = args.phone; + searchData.mail = args.mail; + searchData.address = args.address; + + const userList = await UT.search(searchData); + return userList.map((uid) => new User(uid)); + } + + /** + * @memberof GraphQL.UserModel# + * @function editProfile + * @summary Fonction qui modifie le profil et renvoie l'utilisateur + * @arg {editProfileArgs} args - les données a modifier + * @return {Promise(User)} Renvoie l'utilisateur mis a jour + * @async + * @rights authenticated + */ + async editProfile(args: editProfileArgs): Promise<User> { + let data = await UT.peek(this.contextUser); + + //Modify some fields, keep the others + let editArgs = new userData(); + editArgs.uid = data.uid; + editArgs.password = data.password; + editArgs.givenName = data.givenName; + editArgs.lastName = data.lastName; + editArgs.nickname = args.nickname; // <- this field is modified by user + editArgs.gender = data.gender; + editArgs.photo = data.photo; + editArgs.phone = args.phone; // <- this field is modified + editArgs.address = data.address; // WTF why can't this be changed ???? + editArgs.mail = args.mail; // <- this field is modified + editArgs.birthdate = data.birthdate; + editArgs.nationality = data.nationality; + editArgs.admins = data.admins; + editArgs.speakers = data.speakers; + editArgs.members = data.members; + editArgs.followers = data.followers; + + if(await UT.edit(editArgs)) { + return new User(data.uid); + } + else { + throw new ApolloError("Edit failed"); + } + } + +} \ No newline at end of file diff --git a/src/graphql/object_resolvers/groups.ts b/src/graphql/object_resolvers/groups.ts new file mode 100644 index 0000000000000000000000000000000000000000..59e5511a6780984987bdb7ef2a86a023eb6c452c --- /dev/null +++ b/src/graphql/object_resolvers/groups.ts @@ -0,0 +1,761 @@ +/** + * @file Resolvers des groupes + * @author ofacklam + */ + +import {User} from './users'; +import {Announcement, Event, PrivatePost, Question, Answer} from './messages'; +import {Group as LDAP_Group} from '../../ldap/export/group'; +import knex from '../../../db/knex_router'; +import { Context } from '../typeDefs/queries'; +import { ApolloError, AuthenticationError } from 'apollo-server-core'; +import { Request, UserJoinGroup, GroupJoinMetagroup, GroupCoauthorEvent } from './requests'; + +export abstract class Group { + + /** + * @abstract + * @memberof GraphQL + * @class Group + * @summary Resolvers des groupes + * @classdesc Une classe abstraite représentant l'interface group du schéma. + * Comme Apollo Server, par défaut, appelle la propriété / fonction avec le nom a résoudre, il n'y a pas besoin d'écrire les resolvers explicitement. + * @arg {string} gid - Identifiant du groupe, supposé valide + * @rights connectedOrOnplatal + */ + constructor(gid: string) { + this.gid = gid; + this.m_dataLoaded = false; + } + + /** + * @memberof GraphQL.Group# + * @function fetchData + * @summary Fonction qui va chercher toutes les données sur ce groupe dans le stockage approprié, si ce n'est pas déja fait. + * @returns {Promise(boolean)} Renvoie true si le chargement est réussi. + * @async + * @protected + * @abstract + */ + protected abstract async fetchData(): Promise<boolean> + + /** + * Protected properties. + * Ce sont tous les champs triviaux, ie qui peuvent etre récupérés en une seule requete a la base (que ce soit pour SimpleGroup ou MetaGroup), + * ce qui permet d'etre plus efficace. + * La variable dataLoaded témoigne de si on est déja allés chercher les données. + * ATTENTION. Ce ne sont PAS directement les resolvers, il FAUT donc qu'ils aient un nom DIFFÉRENT du nom des champs dans le schéma. + */ + protected m_dataLoaded: boolean + protected m_createdAt: string + protected m_updatedAt: string + protected m_name: string + protected m_description: string + protected m_mail: string + protected m_website: string + protected m_frontPage: string + protected m_postsSummary: string + + /** + * Ci-dessous les resolvers a proprement parler. + */ + + __typename : string = "Bad group type"; + + /** @rights connectedOrOnplatal */ + gid: string; + + /** + * @memberof GraphQL.Group# + * @function createdAt + * @summary Renvoie la date de création + * @return {Promise(string)} + * @rights connectedOrOnplatal + * @async + */ + async createdAt(args, context: Context, info): Promise<string> { + await this.fetchData(); + return this.m_createdAt; + } + + /** + * @memberof GraphQL.Group# + * @function updatedAt + * @summary Renvoie la date de mise a jour + * @return {Promise(string)} + * @rights connectedOrOnplatal + * @async + */ + async updatedAt(args, context: Context, info): Promise<string> { + await this.fetchData(); + return this.m_updatedAt; + } + + /** + * @memberof GraphQL.Group# + * @function name + * @summary Renvoie le nom du groupe + * @return {Promise(string)} + * @rights connectedOrOnplatal + * @async + */ + async name(args, context: Context, info): Promise<string> { + await this.fetchData(); + return this.m_name; + } + + /** + * @memberof GraphQL.Group# + * @function description + * @summary Renvoie la description du groupe + * @return {Promise(string)} + * @rights viewer + * @async + */ + async description(args, context: Context, info): Promise<string> { + if(context.models.auth.isViewer(this.gid)) { + await this.fetchData(); + return this.m_description; + } + throw new AuthenticationError("Not a viewer"); + } + + /** + * @memberof GraphQL.Group# + * @function mail + * @summary Renvoie l'adresse mail + * @return {Promise(string)} + * @rights viewer + * @async + */ + async mail(args, context: Context, info): Promise<string> { + if (context.models.auth.isViewer(this.gid)) { + await this.fetchData(); + return this.m_mail; + } + throw new AuthenticationError("Not a viewer"); + } + + /** + * @memberof GraphQL.Group# + * @function website + * @summary Renvoie l'adresse du site web + * @return {Promise(string)} + * @rights viewer + * @async + */ + async website(args, context: Context, info): Promise<string> { + if (context.models.auth.isViewer(this.gid)) { + await this.fetchData(); + return this.m_website; + } + throw new AuthenticationError("Not a viewer"); + } + + /** + * @memberof GraphQL.Group# + * @function admins + * @summary Renvoie la liste des admins + * @return {Promise(User[])} + * @rights viewer + * @async + * @abstract + */ + abstract async admins(args, context: Context, info): Promise<User[]> + + /** + * @memberof GraphQL.Group# + * @function frontPage + * @summary Renvoie la page principale + * @return {Promise(string)} + * @rights viewer + * @async + */ + async frontPage(args, context: Context, info): Promise<string> { + if (context.models.auth.isViewer(this.gid)) { + await this.fetchData(); + return this.m_frontPage; + } + throw new AuthenticationError("Not a viewer"); + } + + /** + * @memberof GraphQL.Group# + * @function questions + * @summary Renvoie les questions adressées a ce groupe + * @return {Promise(Question[])} + * @rights viewer + * @async + */ + async questions(args, context: Context, info): Promise<Question[]> { + if (context.models.auth.isViewer(this.gid)) { + throw "Not implemented" + + // let result = await knex('questions').select().whereIn('id', received_messages); + // for(let entry of result){ + // entry.type = "Question"; + // } + // return result; + } + throw new AuthenticationError("Not a viewer"); + } + + /** + * @memberof GraphQL.Group# + * @function answers + * @summary Renvoie les réponses de ce groupe + * @return {Promise(Answer[])} + * @rights viewer + * @async + */ + async answers(args, context: Context, info): Promise<Answer[]> { + if (context.models.auth.isViewer(this.gid)) { + throw "Not implemented" + + // let received_messages = await selectors.recievedMessages(user, groupUID); + // let result = await knex('answers').select().whereIn('id', received_messages); + // for(let entry of result){ + // entry.type = "Answer"; + // } + // return result; + } + throw new AuthenticationError("Not a viewer"); + } + + /** + * @memberof GraphQL.Group# + * @function announcementsFromGroup + * @summary Renvoie les annonces créées par ce groupe + * @return {Promise(Announcement[])} + * @rights member + * @async + */ + async announcementsFromGroup(args, context: Context, info): Promise<Announcement[]> { + if(context.models.auth.isMember(this.gid)) { + throw "Not implemented" + } + throw new AuthenticationError("Not a member"); + } + + /** + * @memberof GraphQL.Group# + * @function announcementsToGroup + * @summary Renvoie les annonces adressées a ce groupe + * @return {Promise(Announcement[])} + * @rights member + * @async + */ + async announcementsToGroup(args, context: Context, info): Promise<Announcement[]> { + if (context.models.auth.isMember(this.gid)) { + throw "Not implemented" + } + throw new AuthenticationError("Not a member"); + } + + /** + * @memberof GraphQL.Group# + * @function eventsFromGroup + * @summary Renvoie les evenements créés par ce groupe + * @return {Promise(Event[])} + * @rights member + * @async + */ + async eventsFromGroup(args, context: Context, info): Promise<Event[]> { + if (context.models.auth.isMember(this.gid)) { + throw "Not implemented" + } + throw new AuthenticationError("Not a member"); + } + + /** + * @memberof GraphQL.Group# + * @function eventsToGroup + * @summary Renvoie les evenements adressées a ce groupe + * @return {Promise(Event[])} + * @rights member + * @async + */ + async eventsToGroup(args, context: Context, info): Promise<Event[]> { + if (context.models.auth.isMember(this.gid)) { + throw "Not implemented" + } + throw new AuthenticationError("Not a member"); + } + + /** + * @memberof GraphQL.Group# + * @function privatePosts + * @summary Renvoie les posts internes du groupe + * @return {Promise(PrivatePost[])} + * @rights member + * @async + */ + async privatePosts(args, context: Context, info): Promise<PrivatePost[]> { + if (context.models.auth.isMember(this.gid)) { + throw "Not implemented" + } + throw new AuthenticationError("Not a member"); + } + + /** + * @memberof GraphQL.Group# + * @function postsSummary + * @summary Renvoie le résumé interne + * @return {Promise(string)} + * @rights member + * @async + */ + async postsSummary(args, context: Context, info): Promise<string> { + if (context.models.auth.isMember(this.gid)) { + await this.fetchData(); + return this.m_postsSummary; + } + throw new AuthenticationError("Not a member"); + } + + /** + * @memberof GraphQL.Group# + * @function requestsToGroup + * @summary Renvoie toutes les requetes adressées au groupe. + * @return {Promise(Request[])} + * @rights admin + * @async + */ + async requestsToGroup(args, context: Context, info): Promise<Request[]> { + if (context.models.auth.isAdmin(this.gid)) { + return context.models.request.getRequestsToGroup(this.gid); + } + throw new AuthenticationError("Not an admin"); + } + + /** + * @memberof GraphQL.Group# + * @function userJoinGroupRequestsToGroup + * @summary Renvoie toutes les requetes UserJoinGroup adressées au groupe. + * @return {Promise(UserJoinGroup[])} + * @rights admin + * @async + */ + async userJoinGroupRequestsToGroup(args, context: Context, info): Promise<UserJoinGroup[]> { + if (context.models.auth.isAdmin(this.gid)) { + return context.models.request.getUserJoinGroupRequestsToGroup(this.gid); + } + throw new AuthenticationError("Not an admin"); + } + + /** + * @memberof GraphQL.Group# + * @function groupJoinMetagroupRequestsToGroup + * @summary Renvoie toutes les requetes GroupJoinMetagroup adressées au groupe. + * @return {Promise(GroupJoinMetagroup[])} + * @rights admin + * @async + */ + async groupJoinMetagroupRequestsToGroup(args, context: Context, info): Promise<GroupJoinMetagroup[]> { + if (context.models.auth.isAdmin(this.gid)) { + return context.models.request.getGroupJoinMetagroupRequestsToGroup(this.gid); + } + throw new AuthenticationError("Not an admin"); + } + + /** + * @memberof GraphQL.Group# + * @function groupCoauthorEventRequestsToGroup + * @summary Renvoie toutes les requetes GroupCoauthorEvent adressées au groupe. + * @return {Promise(GroupCoauthorEvent[])} + * @rights admin + * @async + */ + async groupCoauthorEventRequestsToGroup(args, context: Context, info): Promise<GroupCoauthorEvent[]> { + if (context.models.auth.isAdmin(this.gid)) { + return context.models.request.getGroupCoauthorEventRequestsToGroup(this.gid); + } + throw new AuthenticationError("Not an admin"); + } + + /** + * @memberof GraphQL.Group# + * @function visibilityEdges + * @summary Renvoie les groupes auxquels ce groupe est visible. + * @return {Promise(Group[])} + * @rights viewer + * @async + */ + async visibilityEdges(args, context: Context, info): Promise<Group[]> { + if (context.models.auth.isViewer(this.gid)) { + throw "Not implemented" + } + throw new AuthenticationError("Not a viewer"); + } +} + + +export class SimpleGroup extends Group { + + /** + * @memberof GraphQL + * @class SimpleGroup + * @extends GraphQL.Group + * @summary Resolvers des groupes simples + * @classdesc Une classe représentant le type SimpleGroup du schéma. + * Comme Apollo Server, par défaut, appelle la propriété / fonction avec le nom a résoudre, il n'y a pas besoin d'écrire les resolvers explicitement. + * @arg {string} gid - Identifiant du groupe simple, supposé valide. + * @rights connectedOrOnplatal + */ + constructor(gid: string) { + super(gid); + } + + /** + * @memberof GraphQL.SimpleGroup# + * @function tryCreate + * @summary Fonction qui va essayer de créer le groupe simple correspondant + * @arg {string} gid - Identifiant du groupe simple, sans hypothese sur la validité. + * @returns {Promise(SimpleGroup)} - Renvoie le groupe simple créé, ou null en cas d'erreur. + * @rights connectedOrOnplatal + * @async + * @static + */ + static async tryCreate(gid: string): Promise<SimpleGroup> { + let g = new SimpleGroup(gid); + if(await g.fetchData()) { + return g; + } + return null; + } + + /** + * @memberof GraphQL.SimpleGroup# + * @function fetchData + * @summary Fonction qui va chercher toutes les données sur ce groupe simple dans le LDAP, si ce n'est pas déja fait. + * @returns {Promise(boolean)} Renvoie true si le chargement est réussi. + * @async + * @protected + */ + protected async fetchData(): Promise<boolean> { + if(!this.m_dataLoaded) { + try { + let data = await LDAP_Group.peek(this.gid); + if(data.gid !== this.gid) { + throw "Error in search"; + } + + //this.m_createdAt = data.createdAt; + //this.m_updatedAt = data.updatedAt; + this.m_name = data.name; + this.m_description = data.description; + //this.m_mail = data.mail; + //this.m_website = data.website; + + this.m_members = data.members; + //this.m_speakers = data.speakers; + this.m_admins = data.admins; + //this.m_likers = data.likers; + + //this.m_frontPage = data.frontPage; + //this.m_postsSummary = data.postsSummary; + + //this.m_parent = data.parent; + //this.m_children = data.children; + //this.m_school = ???; + + this.m_dataLoaded = true; + return true; + } + catch { + return false; + } + } + return true; + } + + /** + * Protected properties. + * Ce sont tous les champs triviaux, ie qui peuvent etre récupérés en une seule requete a la base (que ce soit pour SimpleGroup ou MetaGroup), + * ce qui permet d'etre plus efficace. + * La variable dataLoaded témoigne de si on est déja allés chercher les données. + * ATTENTION. Ce ne sont PAS directement les resolvers, il FAUT donc qu'ils aient un nom DIFFÉRENT du nom des champs dans le schéma. + */ + protected m_members: string[] + protected m_speakers: string[] + protected m_admins: string[] + protected m_likers: string[] + protected m_parent: string + protected m_children: string[] + protected m_school: string + + /** + * Ci-dessous les resolvers a proprement parler. + */ + + __typename: string = "SimpleGroup"; + + /** + * @memberof GraphQL.SimpleGroup# + * @function members + * @summary Renvoie la liste des membres + * @return {Promise(User[])} + * @rights viewer + * @async + */ + async members(args, context: Context, info): Promise<User[]> { + if(context.models.auth.isViewer(this.gid)) { + await this.fetchData(); + return this.m_members.map(uid => { + return new User(uid); + }); + } + throw new AuthenticationError("Not a viewer"); + } + + /** + * @memberof GraphQL.SimpleGroup# + * @function speakers + * @summary Renvoie la liste des speakers + * @return {Promise(User[])} + * @rights viewer + * @async + */ + async speakers(args, context: Context, info): Promise<User[]> { + if(context.models.auth.isViewer(this.gid)) { + await this.fetchData(); + return this.m_speakers.map(uid => { + return new User(uid); + }); + } + throw new AuthenticationError("Not a viewer"); + } + + /** + * @memberof GraphQL.SimpleGroup# + * @function admins + * @summary Renvoie la liste des admins + * @return {Promise(User[])} + * @rights viewer + * @async + */ + async admins(args, context: Context, info): Promise<User[]> { + if(context.models.auth.isViewer(this.gid)) { + await this.fetchData(); + return this.m_admins.map(uid => { + return new User(uid); + }); + } + throw new AuthenticationError("Not a viewer"); + } + + /** + * @memberof GraphQL.SimpleGroup# + * @function likers + * @summary Renvoie la liste des sympathisants + * @return {Promise(User[])} + * @rights viewer + * @async + */ + async likers(args, context: Context, info): Promise<User[]> { + if(context.models.auth.isViewer(this.gid)) { + await this.fetchData(); + return this.m_likers.map(uid => { + return new User(uid); + }); + } + throw new AuthenticationError("Not a viewer"); + } + + /** + * @memberof GraphQL.SimpleGroup# + * @function parent + * @summary Renvoie le groupe parent + * @return {Promise(SimpleGroup)} + * @rights viewer + * @async + */ + async parent(args, context: Context, info): Promise<SimpleGroup> { + if(context.models.auth.isViewer(this.gid)) { + await this.fetchData(); + if(this.m_parent == 'root') { + return null; + } + else { + return new SimpleGroup(this.m_parent); + } + } + throw new AuthenticationError("Not a viewer"); + } + + /** + * @memberof GraphQL.SimpleGroup# + * @function children + * @summary Renvoie la liste des groupes enfants + * @return {Promise(SimpleGroup[])} + * @rights viewer + * @async + */ + async children(args, context: Context, info): Promise<SimpleGroup[]> { + if(context.models.auth.isViewer(this.gid)) { + await this.fetchData(); + return this.m_children.map(gid => { + return new SimpleGroup(gid); + }); + } + throw new AuthenticationError("Not a viewer"); + } + + /** + * @memberof GraphQL.SimpleGroup# + * @function memberOfMeta + * @summary Renvoie la liste des méta-groupes dont ce groupe fait partie + * @return {Promise(MetaGroup[])} + * @rights viewer + * @async + */ + async memberOfMeta(args, context: Context, info): Promise<MetaGroup[]> { + if(context.models.auth.isViewer(this.gid)) { + throw "Not implemented"; + } + throw new AuthenticationError("Not a viewer"); + } + + /** + * @memberof GraphQL.SimpleGroup# + * @function school + * @summary Renvoie l'école d'origine + * @return {Promise(string)} + * @rights viewer + * @async + */ + async school(args, context: Context, info): Promise<string> { + if(context.models.auth.isViewer(this.gid)) { + await this.fetchData(); + return this.m_school; + } + throw new AuthenticationError("Not a viewer"); + } +} + + +export class MetaGroup extends Group { + + /** + * @memberof GraphQL + * @class MetaGroup + * @extends GraphQL.Group + * @summary Resolvers des méta-groupes + * @classdesc Une classe représentant le type MetaGroup du schéma. + * Comme Apollo Server, par défaut, appelle la propriété / fonction avec le nom a résoudre, il n'y a pas besoin d'écrire les resolvers explicitement. + * @arg {string} gid - Identifiant du méta-groupe, supposé valide + * @rights connectedOrOnplatal + */ + constructor(gid: string) { + super(gid); + } + + /** + * @memberof GraphQL.MetaGroup# + * @function tryCreate + * @summary Fonction qui va essayer de créer le méta-groupe correspondant + * @arg {string} gid - Identifiant du méta-groupe, sans hypothese sur la validité. + * @returns {Promise(MetaGroup)} - Renvoie le méta-groupe créé, ou null en cas d'erreur. + * @rights connectedOrOnplatal + * @async + * @static + */ + static async tryCreate(gid: string): Promise<MetaGroup> { + let g = new MetaGroup(gid); + if(await g.fetchData()) { + return g; + } + return null; + } + + /** + * @memberof GraphQL.MetaGroup# + * @function fetchData + * @summary Fonction qui va chercher toutes les données sur ce méta-groupe dans la BDD, si ce n'est pas déja fait. + * @returns {Promise(boolean)} Renvoie true si le chargement est réussi. + * @async + * @protected + */ + protected async fetchData(): Promise<boolean> { + if (!this.m_dataLoaded) { + let data = await knex.select('created_at', + 'updated_at', + 'name', + 'description', + //'mail', + //'posts_summary', + //'front_page', + 'website').from('groups').where('gid', this.gid); + + if(data.length > 0) { + let g = data[0]; + + this.m_createdAt = g.created_at; + this.m_updatedAt = g.updated_at; + this.m_name = g.name; + this.m_description = g.description; + //this.m_mail = g.mail; + this.m_website = g.website; + + //this.m_frontPage = g.frontPage; + //this.m_postsSummary = g.postsSummary; + + this.m_dataLoaded = true; + return true; + } + else { + return false; + } + } + return true; + } + + /** + * Ci-dessous les resolvers a proprement parler. + */ + + __typename: string = "MetaGroup"; + + /** + * @memberof GraphQL.MetaGroup# + * @function admins + * @summary Renvoie la liste des admins + * @return {Promise(User[])} + * @rights viewer + * @async + */ + async admins(args, context: Context, info): Promise<User[]> { + if (context.models.auth.isViewer(this.gid)) { + throw "Not implemented"; + } + throw new AuthenticationError("Not a viewer"); + } + + /** + * @memberof GraphQL.MetaGroup# + * @function members + * @summary Renvoie la liste des groupes membres + * @return {Promise(SimpleGroup[])} + * @rights viewer + * @async + */ + async members(args, context: Context, info): Promise<SimpleGroup[]> { + if (context.models.auth.isViewer(this.gid)) { + throw "Not implemented" + + /*let member_group_list = await knex.distinct().select().from('groups') + .innerJoin('meta_group_membership', 'groups.uid', 'meta_group_membership.member_uid') + .where('meta_group_membership.union_uid', '=', group.gid); + let members = new Set; + for (const memberGroup of member_group_list) { + let res = await getGroupMemberUsers(quser, new Group(memberGroup)); + for (const member of res.values()) { + members.add(member); + } + } + return members;*/ + } + throw new AuthenticationError("Not a viewer"); + } + +} \ No newline at end of file diff --git a/src/graphql/object_resolvers/messages.ts b/src/graphql/object_resolvers/messages.ts new file mode 100644 index 0000000000000000000000000000000000000000..623bf59f8023c51ddb837685ae30e417ad0673f2 --- /dev/null +++ b/src/graphql/object_resolvers/messages.ts @@ -0,0 +1,884 @@ +/** + * @file Resolvers pour tous les types de messages + * @author akka, ofacklam + */ + +import {Group, SimpleGroup, MetaGroup} from './groups'; +import {User} from './users'; +import knex from '../../../db/knex_router'; +import { Context } from '../typeDefs/queries'; +import { ApolloError } from 'apollo-server-core'; +import { GroupSet } from '../models/tools'; + +export abstract class Message { + + /** + * @memberof GraphQL + * @class Message + * @summary Resolvers des messages + * @classdesc Une classe abstraite représentant l'interface Message du schéma. + * Comme Apollo Server, par défaut, appelle la propriété / fonction avec le nom a résoudre, il n'y a pas besoin d'écrire les resolvers explicitement. + * @arg {number} mid - Identifiant du message, supposé valide. + * @rights dépend du type de message + * @abstract + */ + constructor(mid: number) { + this.mid = mid; + this.m_dataLoaded = false; + } + + /** + * @memberof GraphQL.Message# + * @function fetchData + * @summary Fonction qui va chercher toutes les données sur ce message dans la BDD, si ce n'est pas déja fait. + * @returns {Promise(boolean)} Renvoie true si le chargement est réussi. + * @async + * @protected + * @abstract + */ + protected abstract async fetchData(): Promise<boolean> + + /** + * Protected properties. + * Ce sont tous les champs triviaux, ie qui peuvent etre récupérés en une seule requete a la BDD, + * ce qui permet d'etre plus efficace. + * La variable dataLoaded témoigne de si on est déja allés chercher les données. + * ATTENTION. Ce ne sont PAS directement les resolvers, il FAUT donc qu'ils aient un nom DIFFÉRENT du nom des champs dans le schéma. + */ + protected m_dataLoaded: boolean + + protected m_createdAt: string + protected m_updatedAt: string + protected m_title: string + protected m_content: string + + /** + * Ci-dessous les resolvers a proprement parler. + */ + + /** + * @memberof GraphQL.Message# + * @function __resolveType + * @summary Renvoie quel type de Message c'est + * @return {string} + * @rights same as message object + */ + __resolveType(): string { + if (this instanceof Announcement) { + return "Announcement"; + } + else if (this instanceof Event) { + return "Event"; + } + else if (this instanceof Question) { + return "Question"; + } + else if (this instanceof Answer) { + return "Answer"; + } + else if (this instanceof PrivatePost) { + return "PrivatePost"; + } + else { + throw new ApolloError("Bad message type"); + } + } + + /** @rights same as message object */ + mid: number; + + /** + * @memberof GraphQL.Message# + * @function createdAt + * @summary Renvoie la date de création + * @return {Promise(string)} + * @rights same as message object + * @async + */ + async createdAt(args, context: Context, info): Promise<string> { + await this.fetchData(); + return this.m_createdAt; + } + + /** + * @memberof GraphQL.Message# + * @function updatedAt + * @summary Renvoie la date de mise a jour + * @return {Promise(string)} + * @rights same as message object + * @async + */ + async updatedAt(args, context: Context, info): Promise<string> { + await this.fetchData(); + return this.m_updatedAt; + } + + /** + * @memberof GraphQL.Message# + * @function title + * @summary Renvoie le titre du message + * @return {Promise(string)} + * @rights same as message object + * @async + */ + async title(args, context: Context, info): Promise<string> { + await this.fetchData(); + return this.m_title; + } + + /** + * @memberof GraphQL.Message# + * @function content + * @summary Renvoie le contenu du message + * @return {Promise(string)} + * @rights same as message object + * @async + */ + async content(args, context: Context, info): Promise<string> { + await this.fetchData(); + return this.m_content; + } +} + +export class Announcement extends Message { + + /** + * @memberof GraphQL + * @class Announcement + * @extends GraphQL.Message + * @summary Resolvers des announcements + * @classdesc Une classe représentant le type Announcement du schéma. + * Comme Apollo Server, par défaut, appelle la propriété / fonction avec le nom a résoudre, il n'y a pas besoin d'écrire les resolvers explicitement. + * @arg {number} mid - Identifiant du message, supposé valide. + * @rights membre d'un groupe author ou d'un groupe recipient + */ + constructor(mid: number) { + super(mid); + this.m_authors = new GroupSet(); + this.m_recipients = new GroupSet(); + } + + /** + * @memberof GraphQL.Announcement# + * @function tryCreate + * @summary Fonction qui va essayer de créer l'annonce correspondante + * @arg {number} mid - Identifiant du message, sans hypothese sur la validité. + * @returns {Promise(Announcement)} - Renvoie le Announcement créé, ou null en cas d'erreur. + * @rights membre d'un groupe author ou d'un groupe recipient + * @async + * @static + */ + static async tryCreate(mid: number): Promise<Announcement> { + let m = new Announcement(mid); + if(await m.fetchData()) { + return m; + } + return null; + } + + /** + * @memberof GraphQL.Announcement# + * @function fetchData + * @summary Fonction qui va chercher toutes les données sur cet announcement dans la BDD, si ce n'est pas déja fait. + * @returns {Promise(boolean)} Renvoie true si le chargement est réussi. + * @async + * @protected + */ + protected async fetchData(): Promise<boolean> { + if(!this.m_dataLoaded) { + let data = await knex.select('created_at', + 'updated_at', + 'title', + 'content', + //'importance', + 'views').from('messages_announcements').where('mid', this.mid); + + if (data.length > 0) { + let m = data[0]; + + this.m_createdAt = m.created_at; + this.m_updatedAt = m.updated_at; + this.m_title = m.title; + this.m_content = m.content; + //this.m_importance = m.importance; + this.m_views = m.views; + + this.m_dataLoaded = true; + } + else { + return false; + } + + //Charge les authors dans m_authors + + data = await knex.select('gid').from('announcements_authors').where('mid', this.mid); + + for(let t of data) { + this.m_authors.add(t.gid); + } + + //Charge les recipients dans m_recipients + + data = await knex.select('gid').from('announcements_recipients').where('mid', this.mid); + + for (let t of data) { + this.m_authors.add(t.gid); + } + } + + return true; + } + + /** + * Protected properties. + * Ce sont tous les champs triviaux, ie qui peuvent etre récupérés en une seule requete a la BDD, + * ce qui permet d'etre plus efficace. + * La variable dataLoaded témoigne de si on est déja allés chercher les données. + * ATTENTION. Ce ne sont PAS directement les resolvers, il FAUT donc qu'ils aient un nom DIFFÉRENT du nom des champs dans le schéma. + */ + protected m_importance: number + protected m_views : number + protected m_authors : GroupSet + protected m_recipients : GroupSet + + /** + * Ci-dessous les resolvers a proprement parler. + */ + + /** + * @memberof GraphQL.Announcement# + * @function authors + * @summary Renvoie les groupes auteurs + * @return {Promise(Group[])} + * @rights membre d'un groupe author ou d'un groupe recipient + * @async + */ + async authors(args, context: Context, info): Promise<Group[]> { + //TODO: verifier les authorisations + throw "Not implemented"; + + await this.fetchData(); + var ret:Group[]; + for(let t of this.m_authors) { + ret.push(new SimpleGroup(t)); + } + return ret; + } + + /** + * @memberof GraphQL.Announcement# + * @function recipients + * @summary Renvoie les groupes destinataires + * @return {Promise(Group[])} + * @rights membre d'un groupe author ou d'un groupe recipient + * @async + */ + async recipients(args, context: Context, info): Promise<Group[]> { + //TODO: verifier les authorisations + throw "Not implemented"; + + await this.fetchData(); + var ret: Group[]; + for (let t of this.m_recipients) { + ret.push(new SimpleGroup(t)); + } + return ret; + } + + /** + * @memberof GraphQL.Announcement# + * @function importance + * @summary Renvoie l'importance + * @return {Promise(number)} + * @rights membre d'un groupe author ou d'un groupe recipient + * @async + */ + async importance(args, context: Context, info): Promise<number> { + await this.fetchData(); + return this.m_importance; + } + + /** + * @memberof GraphQL.Announcement# + * @function views + * @summary Renvoie le nombre de vues + * @return {Promise(number)} + * @rights membre d'un groupe author ou d'un groupe recipient + * @async + */ + async views(args, context: Context, info): Promise<number> { + await this.fetchData(); + return this.m_views; + } + + /** + * @memberof GraphQL.Announcement# + * @function forEvent + * @summary Renvoie l'evenement correspondant, s'il existe + * @return {Promise(Event)} + * @rights membre d'un groupe author ou d'un groupe recipient + * @async + */ + async forEvent(args, context: Context, info): Promise<Event> { + let data = await knex.select('mid').from('messages_events').where('for_announcement', this.mid); + if(data.length > 0) { + return new Event(data[0].mid); + } + return null; + } +} + +export class Event extends Message { + + /** + * @memberof GraphQL + * @class Event + * @extends GraphQL.Message + * @summary Resolvers des events + * @classdesc Une classe représentant le type Event du schéma. + * Comme Apollo Server, par défaut, appelle la propriété / fonction avec le nom a résoudre, il n'y a pas besoin d'écrire les resolvers explicitement. + * @arg {number} mid - Identifiant du message, supposé valide. + * @rights membre d'un groupe author ou d'un groupe recipient + */ + constructor(mid: number) { + super(mid); + } + + /** + * @memberof GraphQL.Event# + * @function tryCreate + * @summary Fonction qui va essayer de créer l'evenement correspondant + * @arg {number} mid - Identifiant du message, sans hypothese sur la validité. + * @returns {Promise(Event)} - Renvoie le Event créé, ou null en cas d'erreur. + * @rights membre d'un groupe author ou d'un groupe recipient + * @async + * @static + */ + static async tryCreate(mid: number): Promise<Event> { + let m = new Event(mid); + if(await m.fetchData()) { + return m; + } + return null; + } + + /** + * @memberof GraphQL.Event# + * @function fetchData + * @summary Fonction qui va chercher toutes les données sur cet event dans la BDD, si ce n'est pas déja fait. + * @returns {Promise(boolean)} Renvoie true si le chargement est réussi. + * @async + * @protected + */ + protected async fetchData(): Promise<boolean> { + if (!this.m_dataLoaded) { + let data = await knex.select('created_at', + 'updated_at', + 'title', + 'content', + 'location', + 'start_time', + 'end_time', + 'for_announcement').from('messages_events').where('mid', this.mid); + + if (data.length > 0) { + let m = data[0]; + + this.m_createdAt = m.created_at; + this.m_updatedAt = m.updated_at; + this.m_title = m.title; + this.m_content = m.content; + this.m_location = m.location; + this.m_startTime = m.start_time; + this.m_endTime = m.end_time; + this.m_forAnnouncement = m.for_announcement; + + this.m_dataLoaded = true; + return true; + } + else { + return false; + } + } + return true; + } + + /** + * Protected properties. + * Ce sont tous les champs triviaux, ie qui peuvent etre récupérés en une seule requete a la BDD, + * ce qui permet d'etre plus efficace. + * La variable dataLoaded témoigne de si on est déja allés chercher les données. + * ATTENTION. Ce ne sont PAS directement les resolvers, il FAUT donc qu'ils aient un nom DIFFÉRENT du nom des champs dans le schéma. + */ + protected m_location: string + protected m_startTime: string + protected m_endTime: string + protected m_forAnnouncement: number + + /** + * Ci-dessous les resolvers a proprement parler. + */ + + /** + * @memberof GraphQL.Event# + * @function authors + * @summary Renvoie les groupes auteurs + * @return {Promise(Group[])} + * @rights membre d'un groupe author ou d'un groupe recipient + * @async + */ + async authors(args, context: Context, info): Promise<Group[]> { + throw "Not implemented"; + + /*return knex.select({ uid: 'group' }).from('group_message_relationships') + .where('message', messageID).whereIn('status', ['host', 'publish']);*/ + } + + /** + * @memberof GraphQL.Event# + * @function recipients + * @summary Renvoie les groupes destinataires + * @return {Promise(Group[])} + * @rights membre d'un groupe author ou d'un groupe recipient + * @async + */ + async recipients(args, context: Context, info): Promise<Group[]> { + throw "Not implemented"; + + /*return knex.select({ uid: 'group' }).from('group_message_relationships') + .where('message', messageID).where('status', 'recieve');*/ + } + + /** + * @memberof GraphQL.Event# + * @function location + * @summary Renvoie l'endroit + * @return {Promise(string)} + * @rights membre d'un groupe author ou d'un groupe recipient + * @async + */ + async location(args, context: Context, info): Promise<string> { + await this.fetchData(); + return this.m_location; + } + + /** + * @memberof GraphQL.Event# + * @function startTime + * @summary Renvoie l'horaire de début + * @return {Promise(string)} + * @rights membre d'un groupe author ou d'un groupe recipient + * @async + */ + async startTime(args, context: Context, info): Promise<string> { + await this.fetchData(); + return this.m_startTime; + } + + /** + * @memberof GraphQL.Event# + * @function endTime + * @summary Renvoie l'horaire de fin + * @return {Promise(string)} + * @rights membre d'un groupe author ou d'un groupe recipient + * @async + */ + async endTime(args, context: Context, info): Promise<string> { + await this.fetchData(); + return this.m_endTime; + } + + /** + * @memberof GraphQL.Event# + * @function participatingGroups + * @summary Renvoie les groupes qui participent + * @return {Promise(Group[])} + * @rights membre d'un groupe author ou d'un groupe recipient + * @async + */ + async participatingGroups(args, context: Context, info): Promise<Group[]> { + throw "Not implemented"; + } + + /** + * @memberof GraphQL.Event# + * @function participatingUsers + * @summary Renvoie les users qui participent + * @return {Promise(User[])} + * @rights membre d'un groupe author ou d'un groupe recipient + * @async + */ + async participatingUsers(args, context: Context, info): Promise<User[]> { + throw "Not implemented"; + } + + /** + * @memberof GraphQL.Event# + * @function forAnnouncement + * @summary Renvoie l'announcement correspondant, s'il existe + * @return {Promise(Announcement)} + * @rights membre d'un groupe author ou d'un groupe recipient + * @async + */ + async forAnnouncement(args, context: Context, info): Promise<Announcement> { + await this.fetchData(); + if (this.m_forAnnouncement == 0) { //what is the default ????? + return null; + } + else { + return new Announcement(this.m_forAnnouncement); + } + } +} + +export class PrivatePost extends Message { + + /** + * @memberof GraphQL + * @class PrivatePost + * @extends GraphQL.Message + * @summary Resolvers des posts privés + * @classdesc Une classe représentant le type PrivatePost du schéma. + * Comme Apollo Server, par défaut, appelle la propriété / fonction avec le nom a résoudre, il n'y a pas besoin d'écrire les resolvers explicitement. + * @arg {number} mid - Identifiant du message, supposé valide. + * @rights membre du groupe recipient + */ + constructor(mid: number) { + super(mid); + } + + /** + * @memberof GraphQL.PrivatePost# + * @function tryCreate + * @summary Fonction qui va essayer de créer le post privé correspondant + * @arg {number} mid - Identifiant du message, sans hypothese sur la validité. + * @returns {Promise(PrivatePost)} - Renvoie le PrivatePost créé, ou null en cas d'erreur. + * @rights membre du groupe recipient + * @async + * @static + */ + static async tryCreate(mid: number): Promise<PrivatePost> { + let m = new PrivatePost(mid); + if(await m.fetchData()) { + return m; + } + return null; + } + + /** + * @memberof GraphQL.PrivatePost# + * @function fetchData + * @summary Fonction qui va chercher toutes les données sur ce post privé dans la BDD, si ce n'est pas déja fait. + * @returns {Promise(boolean)} Renvoie true si le chargement est réussi. + * @async + * @protected + */ + protected async fetchData(): Promise<boolean> { + if (!this.m_dataLoaded) { + let data = await knex.select('created_at', + 'updated_at', + 'title', + 'content', + 'author', + 'recipient').from('messages_private_posts').where('mid', this.mid); + + if (data.length > 0) { + let m = data[0]; + + this.m_createdAt = m.created_at; + this.m_updatedAt = m.updated_at; + this.m_title = m.title; + this.m_content = m.content; + this.m_author = m.author; + this.m_recipient = m.recipient; + + this.m_dataLoaded = true; + return true; + } + else { + return false; + } + } + return true; + } + + /** + * Protected properties. + * Ce sont tous les champs triviaux, ie qui peuvent etre récupérés en une seule requete a la BDD, + * ce qui permet d'etre plus efficace. + * La variable dataLoaded témoigne de si on est déja allés chercher les données. + * ATTENTION. Ce ne sont PAS directement les resolvers, il FAUT donc qu'ils aient un nom DIFFÉRENT du nom des champs dans le schéma. + */ + protected m_author: string + protected m_recipient: string + + /** + * Ci-dessous les resolvers a proprement parler. + */ + + /** + * @memberof GraphQL.PrivatePost# + * @function author + * @summary Renvoie le user auteur + * @return {Promise(User)} + * @rights membre du groupe recipient + * @async + */ + async author(args, context: Context, info): Promise<User> { + await this.fetchData(); + return new User(this.m_author); + } + + /** + * @memberof GraphQL.PrivatePost# + * @function recipient + * @summary Renvoie le groupe destinataire + * @return {Promise(Group)} + * @rights membre du groupe recipient + * @async + */ + async recipient(args, context: Context, info): Promise<Group> { + await this.fetchData(); + throw "Not implemented"; + } +} + +export class Question extends Message { + + /** + * @memberof GraphQL + * @class Question + * @extends GraphQL.Message + * @summary Resolvers des questions + * @classdesc Une classe représentant le type Question du schéma. + * Comme Apollo Server, par défaut, appelle la propriété / fonction avec le nom a résoudre, il n'y a pas besoin d'écrire les resolvers explicitement. + * @arg {number} mid - Identifiant du message, supposé valide. + * @rights viewer du groupe recipient + */ + constructor(mid: number) { + super(mid); + } + + /** + * @memberof GraphQL.Question# + * @function tryCreate + * @summary Fonction qui va essayer de créer la question correspondante + * @arg {number} mid - Identifiant du message, sans hypothese sur la validité. + * @returns {Promise(Question)} - Renvoie la Question créée, ou null en cas d'erreur. + * @rights viewer du groupe recipient + * @async + * @static + */ + static async tryCreate(mid: number): Promise<Question> { + let m = new Question(mid); + if(await m.fetchData()) { + return m; + } + return null; + } + + /** + * @memberof GraphQL.Question# + * @function fetchData + * @summary Fonction qui va chercher toutes les données sur ce post privé dans la BDD, si ce n'est pas déja fait. + * @returns {Promise(boolean)} Renvoie true si le chargement est réussi. + * @async + * @protected + */ + protected async fetchData(): Promise<boolean> { + if (!this.m_dataLoaded) { + let data = await knex.select('created_at', + 'updated_at', + 'title', + 'content', + 'author', + 'recipient').from('messages_questions').where('mid', this.mid); + + if (data.length > 0) { + let m = data[0]; + + this.m_createdAt = m.created_at; + this.m_updatedAt = m.updated_at; + this.m_title = m.title; + this.m_content = m.content; + this.m_author = m.author; + this.m_recipient = m.recipient; + + this.m_dataLoaded = true; + return true; + } + else { + return false; + } + } + return true; + } + + /** + * Protected properties. + * Ce sont tous les champs triviaux, ie qui peuvent etre récupérés en une seule requete a la BDD, + * ce qui permet d'etre plus efficace. + * La variable dataLoaded témoigne de si on est déja allés chercher les données. + * ATTENTION. Ce ne sont PAS directement les resolvers, il FAUT donc qu'ils aient un nom DIFFÉRENT du nom des champs dans le schéma. + */ + protected m_author: string + protected m_recipient: string + + /** + * Ci-dessous les resolvers a proprement parler. + */ + + /** + * @memberof GraphQL.Question# + * @function author + * @summary Renvoie le user auteur + * @return {Promise(User)} + * @rights viewer du groupe recipient + * @async + */ + async author(args, context: Context, info): Promise<User> { + await this.fetchData(); + return new User(this.m_author); + } + + /** + * @memberof GraphQL.Question# + * @function recipient + * @summary Renvoie le groupe destinataire + * @return {Promise(Group)} + * @rights viewer du groupe recipient + * @async + */ + async recipient(args, context: Context, info): Promise<Group> { + await this.fetchData(); + throw "Not implemented"; + } + + /** + * @memberof GraphQL.Question# + * @function forAnswer + * @summary Renvoie la réponse correspondante, si elle existe + * @return {Promise(Answer)} + * @rights viewer du groupe recipient + * @async + */ + async forAnswer(args, context: Context, info): Promise<Answer> { + let data = await knex.select('mid').from('messages_answers').where('for_question', this.mid); + if (data.length > 0) { + return new Answer(data[0].mid); + } + return null; + } +} + +export class Answer extends Message { + + /** + * @memberof GraphQL + * @class Answer + * @extends GraphQL.Message + * @summary Resolvers des réponses + * @classdesc Une classe représentant le type Answer du schéma. + * Comme Apollo Server, par défaut, appelle la propriété / fonction avec le nom a résoudre, il n'y a pas besoin d'écrire les resolvers explicitement. + * @arg {number} mid - Identifiant du message, supposé valide. + * @rights viewer du groupe author + */ + constructor(mid: number) { + super(mid); + } + + /** + * @memberof GraphQL.Answer# + * @function tryCreate + * @summary Fonction qui va essayer de créer la réponse correspondante + * @arg {number} mid - Identifiant du message, sans hypothese sur la validité. + * @returns {Promise(Answer)} - Renvoie la Answer créée, ou null en cas d'erreur. + * @rights viewer du groupe author + * @async + * @static + */ + static async tryCreate(mid: number): Promise<Answer> { + let m = new Answer(mid); + if(await m.fetchData()) { + return m; + } + return null; + } + + /** + * @memberof GraphQL.Answer# + * @function fetchData + * @summary Fonction qui va chercher toutes les données sur ce post privé dans la BDD, si ce n'est pas déja fait. + * @returns {Promise(boolean)} Renvoie true si le chargement est réussi. + * @async + * @protected + */ + protected async fetchData(): Promise<boolean> { + if (!this.m_dataLoaded) { + let data = await knex.select('created_at', + 'updated_at', + 'title', + 'content', + 'recipient', //<--- WTF ???? + 'for_question').from('messages_answers').where('mid', this.mid); + + if (data.length > 0) { + let m = data[0]; + + this.m_createdAt = m.created_at; + this.m_updatedAt = m.updated_at; + this.m_title = m.title; + this.m_content = m.content; + this.m_author = m.recipient; + this.m_forQuestion = m.for_question; + + this.m_dataLoaded = true; + return true; + } + else { + return false; + } + } + return true; + } + + /** + * Protected properties. + * Ce sont tous les champs triviaux, ie qui peuvent etre récupérés en une seule requete a la BDD, + * ce qui permet d'etre plus efficace. + * La variable dataLoaded témoigne de si on est déja allés chercher les données. + * ATTENTION. Ce ne sont PAS directement les resolvers, il FAUT donc qu'ils aient un nom DIFFÉRENT du nom des champs dans le schéma. + */ + protected m_author: string + protected m_forQuestion: number + + /** + * Ci-dessous les resolvers a proprement parler. + */ + + /** + * @memberof GraphQL.Answer# + * @function author + * @summary Renvoie le groupe auteur + * @return {Promise(Group)} + * @rights viewer du groupe author + * @async + */ + async author(args, context: Context, info): Promise<Group> { + await this.fetchData(); + throw "Not implemented"; + } + + /** + * @memberof GraphQL.Answer# + * @function forQuestion + * @summary Renvoie la question correspondante + * @return {Promise(Question)} + * @rights viewer du groupe author + * @async + */ + async forQuestion(args, context: Context, info): Promise<Question> { + await this.fetchData(); + return new Question(this.m_forQuestion); + } +} diff --git a/src/graphql/object_resolvers/requests.ts b/src/graphql/object_resolvers/requests.ts new file mode 100644 index 0000000000000000000000000000000000000000..7c1a2c510fa6ca31501d28731781a3eaaa130e3a --- /dev/null +++ b/src/graphql/object_resolvers/requests.ts @@ -0,0 +1,431 @@ +/** + * @file Resolvers pour tous les types de messages + * @author akka, ofacklam + */ + +import { Group, SimpleGroup, MetaGroup } from "./groups"; +import { User } from './users'; +import { Event } from "./messages"; +import knex from '../../../db/knex_router'; +import { Context } from "../typeDefs/queries"; +import { ApolloError } from "apollo-server-core"; + +export abstract class Request { + + /** + * @memberof GraphQL + * @class Request + * @summary Resolvers des requetes + * @classdesc Une classe abstraite représentant l'interface Request du schéma. + * Comme Apollo Server, par défaut, appelle la propriété / fonction avec le nom a résoudre, il n'y a pas besoin d'écrire les resolvers explicitement. + * @arg {number} rid - Identifiant de la requete, supposé valide. + * @rights dépend du type de requete + * @abstract + */ + constructor(rid: number) { + this.rid = rid; + this.m_dataLoaded = false; + } + + /** + * @memberof GraphQL.Request# + * @function fetchData + * @summary Fonction qui va chercher toutes les données sur cette requete dans la BDD, si ce n'est pas déja fait. + * @returns {Promise(boolean)} Renvoie true si le chargement est réussi. + * @async + * @protected + * @abstract + */ + protected abstract async fetchData(): Promise<boolean> + + /** + * Protected properties. + * Ce sont tous les champs triviaux, ie qui peuvent etre récupérés en une seule requete a la BDD, + * ce qui permet d'etre plus efficace. + * La variable dataLoaded témoigne de si on est déja allés chercher les données. + * ATTENTION. Ce ne sont PAS directement les resolvers, il FAUT donc qu'ils aient un nom DIFFÉRENT du nom des champs dans le schéma. + */ + protected m_dataLoaded: boolean + + protected m_comment: string + protected m_from: string + protected m_to: string + + /** + * Ci-dessous les resolvers a proprement parler. + */ + + /** + * @memberof GraphQL.Request# + * @function __resolveType + * @summary Renvoie quel type de Request c'est + * @return {string} + * @rights same as request object + */ + __resolveType(): string { + if (this instanceof UserJoinGroup) { + return "UserJoinGroup"; + } + else if (this instanceof GroupJoinMetagroup) { + return "GroupJoinMetagroup"; + } + else if (this instanceof GroupCoauthorEvent) { + return "GroupCoauthorEvent"; + } + else { + throw new ApolloError("Bad request type"); + } + } + + /** @rights same as request object */ + rid: number; + + /** + * @memberof GraphQL.Request# + * @function comment + * @summary Renvoie le commentaire associé + * @return {Promise(string)} + * @rights same as request object + * @async + */ + async comment(args, context: Context, info): Promise<string> { + await this.fetchData(); + return this.m_comment; + } + + /** + * @memberof GraphQL.Request# + * @function from + * @summary Renvoie l'émetteur de demande + * @return {Promise(Group | User)} + * @rights same as request object + * @async + * @abstract + */ + abstract async from(args, context: Context, info); + + /** + * @memberof GraphQL.Request# + * @function to + * @summary Renvoie le destinataire de la demande + * @return {Promise(Group)} + * @rights same as request object + * @async + * @abstract + */ + abstract async to(args, context: Context, info): Promise<Group>; +} + +export class UserJoinGroup extends Request { + + /** + * @memberof GraphQL + * @class UserJoinGroup + * @extends GraphQL.Request + * @summary Resolvers des requetes UserJoinGroup + * @classdesc Une classe représentant le type UserJoinGroup du schéma. + * Comme Apollo Server, par défaut, appelle la propriété / fonction avec le nom a résoudre, il n'y a pas besoin d'écrire les resolvers explicitement. + * @arg {number} rid - Identifiant de la requete, supposé valide. + * @rights admin du groupe recipient ou le user émetteur + */ + constructor(rid: number) { + super(rid); + } + + /** + * @memberof GraphQL.UserJoinGroup# + * @function tryCreate + * @summary Fonction qui va essayer de créer la requete correspondante + * @arg {number} rid - Identifiant de la requete, sans hypothese sur la validité. + * @returns {Promise(UserJoinGroup)} - Renvoie le UserJoinGroup créé, ou null en cas d'erreur. + * @rights admin du groupe recipient ou le user émetteur + * @async + * @static + */ + static async tryCreate(rid: number): Promise<UserJoinGroup> { + let r = new UserJoinGroup(rid); + if(await r.fetchData()) { + return r; + } + return null; + } + + /** + * @memberof GraphQL.UserJoinGroup# + * @function fetchData + * @summary Fonction qui va chercher toutes les données sur cette requete dans la BDD, si ce n'est pas déja fait. + * @returns {Promise(boolean)} Renvoie true si le chargement est réussi. + * @async + * @protected + */ + protected async fetchData(): Promise<boolean> { + if (!this.m_dataLoaded) { + let data = await knex.select('request_comment', + 'request_from', + 'request_to').from('requests_user_join_group').where('rid', this.rid); + + if (data.length > 0) { + let r = data[0]; + + this.m_comment = r.request_comment; + this.m_from = r.request_from; + this.m_to = r.request_to; + + this.m_dataLoaded = true; + return true; + } + else { + return false; + } + } + return true; + } + + /** + * Ci-dessous les resolvers a proprement parler. + */ + + /** + * @memberof GraphQL.UserJoinGroup# + * @function from + * @summary Renvoie le user émetteur + * @return {Promise(User)} + * @rights admin du groupe recipient ou le user émetteur + * @async + */ + async from(args, context: Context, info): Promise<User> { + await this.fetchData(); + return new User(this.m_from); + } + + /** + * @memberof GraphQL.UserJoinGroup# + * @function to + * @summary Renvoie le groupe destinataire + * @return {Promise(SimpleGroup)} + * @rights admin du groupe recipient ou le user émetteur + * @async + */ + async to(args, context: Context, info): Promise<SimpleGroup> { + await this.fetchData(); + return new SimpleGroup(this.m_to); + } +} + +export class GroupJoinMetagroup extends Request { + + /** + * @memberof GraphQL + * @class GroupJoinMetagroup + * @extends GraphQL.Request + * @summary Resolvers des requetes GroupJoinMetagroup + * @classdesc Une classe représentant le type GroupJoinMetagroup du schéma. + * Comme Apollo Server, par défaut, appelle la propriété / fonction avec le nom a résoudre, il n'y a pas besoin d'écrire les resolvers explicitement. + * @arg {number} rid - Identifiant de la requete, supposé valide. + * @rights admin du groupe émetteur ou destinataire + */ + constructor(rid: number) { + super(rid); + } + + /** + * @memberof GraphQL.GroupJoinMetagroup# + * @function tryCreate + * @summary Fonction qui va essayer de créer la requete correspondante + * @arg {number} rid - Identifiant de la requete, sans hypothese sur la validité. + * @returns {Promise(GroupJoinMetagroup)} - Renvoie le GroupJoinMetagroup créé, ou null en cas d'erreur. + * @rights admin du groupe émetteur ou destinataire + * @async + * @static + */ + static async tryCreate(rid: number): Promise<GroupJoinMetagroup> { + let r = new GroupJoinMetagroup(rid); + if(await r.fetchData()) { + return r; + } + return null; + } + + /** + * @memberof GraphQL.GroupJoinMetagroup# + * @function fetchData + * @summary Fonction qui va chercher toutes les données sur cette requete dans la BDD, si ce n'est pas déja fait. + * @returns {Promise(boolean)} Renvoie true si le chargement est réussi. + * @async + * @protected + */ + protected async fetchData(): Promise<boolean> { + if (!this.m_dataLoaded) { + let data = await knex.select('request_comment', + 'request_from', + 'request_to').from('requests_group_join_metagroup').where('rid', this.rid); + + if (data.length > 0) { + let r = data[0]; + + this.m_comment = r.request_comment; + this.m_from = r.request_from; + this.m_to = r.request_to; + + this.m_dataLoaded = true; + return true; + } + else { + return false; + } + } + return true; + } + + /** + * Ci-dessous les resolvers a proprement parler. + */ + + /** + * @memberof GraphQL.GroupJoinMetagroup# + * @function from + * @summary Renvoie le groupe émetteur + * @return {Promise(SimpleGroup)} + * @rights admin du groupe émetteur ou destinataire + * @async + */ + async from(args, context: Context, info): Promise<SimpleGroup> { + await this.fetchData(); + return new SimpleGroup(this.m_from); + } + + /** + * @memberof GraphQL.GroupJoinMetagroup# + * @function to + * @summary Renvoie le groupe destinataire + * @return {Promise(MetaGroup)} + * @rights admin du groupe émetteur ou destinataire + * @async + */ + async to(args, context: Context, info): Promise<MetaGroup> { + await this.fetchData(); + return new MetaGroup(this.m_to); + } +} + +export class GroupCoauthorEvent extends Request { + + /** + * @memberof GraphQL + * @class GroupCoauthorEvent + * @extends GraphQL.Request + * @summary Resolvers des requetes GroupCoauthorEvent + * @classdesc Une classe représentant le type GroupCoauthorEvent du schéma. + * Comme Apollo Server, par défaut, appelle la propriété / fonction avec le nom a résoudre, il n'y a pas besoin d'écrire les resolvers explicitement. + * @arg {number} rid - Identifiant de la requete, supposé valide. + * @rights admin du groupe émetteur ou destinataire + */ + constructor(rid: number) { + super(rid); + } + + /** + * @memberof GraphQL.GroupCoauthorEvent# + * @function tryCreate + * @summary Fonction qui va essayer de créer la requete correspondante + * @arg {number} rid - Identifiant de la requete, sans hypothese sur la validité. + * @returns {Promise(GroupCoauthorEvent)} - Renvoie le GroupCoauthorEvent créé, ou null en cas d'erreur. + * @rights admin du groupe émetteur ou destinataire + * @async + * @static + */ + static async tryCreate(rid: number): Promise<GroupCoauthorEvent> { + let r = new GroupCoauthorEvent(rid); + if(await r.fetchData()) { + return r; + } + return null; + } + + /** + * @memberof GraphQL.GroupCoauthorEvent# + * @function fetchData + * @summary Fonction qui va chercher toutes les données sur cette requete dans la BDD, si ce n'est pas déja fait. + * @returns {Promise(boolean)} Renvoie true si le chargement est réussi. + * @async + * @protected + */ + protected async fetchData(): Promise<boolean> { + if (!this.m_dataLoaded) { + let data = await knex.select('request_comment', + 'request_from', + 'request_to', + 'for_event').from('requests_group_coauthor_event').where('rid', this.rid); + + if (data.length > 0) { + let r = data[0]; + + this.m_comment = r.request_comment; + this.m_from = r.request_from; + this.m_to = r.request_to; + this.m_forEvent = r.for_event; + + this.m_dataLoaded = true; + return true; + } + else { + return false; + } + } + return true; + } + + /** + * Protected properties. + * Ce sont tous les champs triviaux, ie qui peuvent etre récupérés en une seule requete a la BDD, + * ce qui permet d'etre plus efficace. + * La variable dataLoaded témoigne de si on est déja allés chercher les données. + * ATTENTION. Ce ne sont PAS directement les resolvers, il FAUT donc qu'ils aient un nom DIFFÉRENT du nom des champs dans le schéma. + */ + protected m_forEvent: number; + + /** + * Ci-dessous les resolvers a proprement parler. + */ + + /** + * @memberof GraphQL.GroupCoauthorEvent# + * @function from + * @summary Renvoie le groupe émetteur + * @return {Promise(Group)} + * @rights admin du groupe émetteur ou destinataire + * @async + */ + async from(args, context: Context, info): Promise<Group> { + await this.fetchData(); + throw "Not implemented"; + } + + /** + * @memberof GraphQL.GroupCoauthorEvent# + * @function to + * @summary Renvoie le groupe destinataire + * @return {Promise(Group)} + * @rights admin du groupe émetteur ou destinataire + * @async + */ + async to(args, context: Context, info): Promise<Group> { + await this.fetchData(); + throw "Not implemented"; + } + + /** + * @memberof GraphQL.GroupCoauthorEvent# + * @function forEvent + * @summary Renvoie l'evenement a co-organiser + * @return {Promise(Event)} + * @rights admin du groupe émetteur ou destinataire + * @async + */ + async forEvent(args, context: Context, info): Promise<Event> { + await this.fetchData(); + throw "Not implemented"; + //a-t-il le droit de voir ??? + return new Event(this.m_forEvent); + } +} \ No newline at end of file diff --git a/src/graphql/object_resolvers/users.ts b/src/graphql/object_resolvers/users.ts new file mode 100644 index 0000000000000000000000000000000000000000..c69048c4152c48b492ed55be13dcb4f88f1607cd --- /dev/null +++ b/src/graphql/object_resolvers/users.ts @@ -0,0 +1,349 @@ +/** + * @file Resolvers des utilisateurs + * @author ofacklam + */ + +import {Group, SimpleGroup, MetaGroup} from './groups'; +import {Question} from './messages'; +import {User as LDAP_User} from '../../ldap/export/user'; +import {Tools, GroupSet} from '../models/tools'; +import { Context } from '../typeDefs/queries'; + +export class User { + + /** + * @memberof GraphQL + * @class User + * @summary Resolvers des utilisateurs + * @classdesc Une classe représentant le type User du schéma. + * Comme Apollo Server, par défaut, appelle la propriété / fonction avec le nom a résoudre, il n'y a pas besoin d'écrire les resolvers explicitement. + * @arg {string} uid - Identifiant du user, supposé valide. + * @rights connectedOrOnplatal + */ + constructor(uid: string) { + this.uid = uid; + this.m_dataLoaded = false; + } + + /** + * @memberof GraphQL.User# + * @function tryCreate + * @summary Fonction qui va essayer de créer le user correspondant + * @arg {string} uid - Identifiant du user, sans hypothese sur la validité. + * @returns {Promise(User)} - Renvoie le user créé, ou null en cas d'erreur. + * @rights connectedOrOnplatal + * @async + * @static + */ + static async tryCreate(uid: string): Promise<User> { + let u = new User(uid); + if(await u.fetchData()) { + return u; + } + return null; + } + + /** + * @memberof GraphQL.User# + * @function fetchData + * @summary Fonction qui va chercher toutes les données sur ce user dans le LDAP, si ce n'est pas déja fait. + * @returns {Promise(boolean)} Renvoie true si le chargement est réussi. + * @async + * @protected + */ + protected async fetchData(): Promise<boolean> { + if(!this.m_dataLoaded) { + try { + let data = await LDAP_User.peek(this.uid); + if(data.uid !== this.uid) { + throw "Error in search"; + } + + this.m_givenName = data.givenName; + this.m_lastName = data.lastName; + this.m_nickname = data.nickname; + this.m_nationality = data.nationality; + this.m_birthdate = data.birthdate; + + this.m_mail = data.mail; + this.m_phone = data.phone; + this.m_address = data.address; + + this.m_memberOf = data.members; + this.m_speakerOf = data.speakers; + this.m_adminOf = data.admins; + this.m_likes = data.followers; + + this.m_dataLoaded = true; + return true; + } + catch { + return false; + } + } + return true; + } + + /** + * Protected properties. + * Ce sont tous les champs triviaux, ie qui peuvent etre récupérés en une seule requete au LDAP, + * ce qui permet d'etre plus efficace. + * La variable dataLoaded témoigne de si on est déja allés chercher les données. + * ATTENTION. Ce ne sont PAS directement les resolvers, il FAUT donc qu'ils aient un nom DIFFÉRENT du nom des champs dans le schéma. + */ + protected m_dataLoaded: boolean + + protected m_givenName: string + protected m_lastName: string + protected m_nickname: string + protected m_nationality: string + protected m_birthdate: string + + protected m_mail: string + protected m_phone: string + protected m_address: string + + protected m_memberOf: string[] + protected m_speakerOf: string[] + protected m_adminOf: string[] + protected m_likes: string[] + + /** + * Ci-dessous les resolvers a proprement parler. + */ + + /** @rights connectedOrOnplatal */ + uid: string; + + /** + * @memberof GraphQL.User# + * @function givenName + * @summary Renvoie le prénom + * @return {Promise(string)} + * @rights connectedOrOnplatal + * @async + */ + async givenName(args, context: Context, info): Promise<string> { + await this.fetchData(); + return this.m_givenName; + } + + /** + * @memberof GraphQL.User# + * @function lastName + * @summary Renvoie le nom de famille + * @return {Promise(string)} + * @rights connectedOrOnplatal + * @async + */ + async lastName(args, context: Context, info): Promise<string> { + await this.fetchData(); + return this.m_lastName; + } + + /** + * @memberof GraphQL.User# + * @function nickname + * @summary Renvoie le surnom + * @return {Promise(string)} + * @rights connectedOrOnplatal + * @async + */ + async nickname(args, context: Context, info): Promise<string> { + await this.fetchData(); + return this.m_nickname; + } + + /** + * @memberof GraphQL.User# + * @function nationality + * @summary Renvoie la nationalité + * @return {Promise(string)} + * @rights connectedOrOnplatal + * @async + */ + async nationality(args, context: Context, info): Promise<string> { + await this.fetchData(); + return this.m_nationality; + } + + /** + * @memberof GraphQL.User# + * @function birthdate + * @summary Renvoie la date de naissance + * @return {Promise(string)} + * @rights connectedOrOnplatal + * @async + */ + async birthdate(args, context: Context, info): Promise<string> { + await this.fetchData(); + return this.m_birthdate; + } + + /** + * @memberof GraphQL.User# + * @function promotion + * @summary Renvoie la promotion + * @return {Promise(string)} + * @rights connectedOrOnplatal + * @async + */ + async promotion(args, context: Context, info): Promise<string> { + await this.fetchData(); + throw "Not implemented"; + } + + /** + * @memberof GraphQL.User# + * @function mail + * @summary Renvoie l'adresse mail + * @return {Promise(string)} + * @rights connectedOrOnplatal + * @async + */ + async mail(args, context: Context, info): Promise<string> { + await this.fetchData(); + return this.m_mail; + } + + /** + * @memberof GraphQL.User# + * @function phone + * @summary Renvoie le numéro de téléphone + * @return {Promise(string)} + * @rights connectedOrOnplatal + * @async + */ + async phone(args, context: Context, info): Promise<string> { + await this.fetchData(); + return this.m_phone; + } + + /** + * @memberof GraphQL.User# + * @function address + * @summary Renvoie l'adresse + * @return {Promise(string)} + * @rights connectedOrOnplatal + * @async + */ + async address(args, context: Context, info): Promise<string> { + await this.fetchData(); + return this.m_address; + } + + /** + * @memberof GraphQL.User# + * @function memberOf + * @summary Renvoie les groupes dont il est membre + * @return {Promise(Group[])} + * @rights connectedOrOnplatal + * @async + */ + async memberOf(args, context: Context, info): Promise<Group[]> { + await this.fetchData(); + let simple = new GroupSet(this.m_memberOf); + let meta = await Tools.metaGroupsOfGroups(simple); + + let all = new Array<Group>(); + + for(let gid of simple) { + all.push(new SimpleGroup(gid)); + //all.push(await SimpleGroup.tryCreate(gid)); + } + for(let gid of meta) { + all.push(new MetaGroup(gid)); + //all.push(await MetaGroup.tryCreate(gid)); + } + + return all; + } + + /** + * @memberof GraphQL.User# + * @function speakerOf + * @summary Renvoie les groupes dont il est speaker + * @return {Promise(Group[])} + * @rights connectedOrOnplatal + * @async + */ + async speakerOf(args, context: Context, info): Promise<Group[]> { + await this.fetchData(); + let speaker = new GroupSet(this.m_speakerOf); + let admin = new GroupSet(this.m_adminOf); + let meta = await Tools.metaGroupsOfGroups(admin); + + let all: Group[]; + + for (let gid of speaker) { + all.push(new SimpleGroup(gid)); + } + for (let gid of meta) { + all.push(new MetaGroup(gid)); + } + + return all; + } + + /** + * @memberof GraphQL.User# + * @function adminOf + * @summary Renvoie les groupes dont il est admin + * @return {Promise(Group[])} + * @rights connectedOrOnplatal + * @async + */ + async adminOf(args, context: Context, info): Promise<Group[]> { + await this.fetchData(); + let simple = new GroupSet(this.m_adminOf); + let meta = await Tools.metaGroupsOfGroups(simple); + + let all: Group[]; + + for (let gid of simple) { + all.push(new SimpleGroup(gid)); + } + for (let gid of meta) { + all.push(new MetaGroup(gid)); + } + + return all; + } + + /** + * @memberof GraphQL.User# + * @function likes + * @summary Renvoie les groupes dont il est sympathisant + * @return {Promise(Group[])} + * @rights connectedOrOnplatal + * @async + */ + async likes(args, context: Context, info): Promise<Group[]> { + await this.fetchData(); + let simple = new GroupSet(this.m_likes); + let meta = await Tools.metaGroupsOfGroups(simple); + + let all: Group[]; + + for (let gid of simple) { + all.push(new SimpleGroup(gid)); + } + for (let gid of meta) { + all.push(new MetaGroup(gid)); + } + + return all; + } + + /** + * @memberof GraphQL.User# + * @function questionsFromUser + * @summary Renvoie les questions que le user a posées + * @return {Promise(Question[])} + * @rights viewer of recipient group + * @async + */ + async questionsFromUser(args, context: Context, info): Promise<Question[]> { + throw "Not implemented"; + } + } \ No newline at end of file diff --git a/src/graphql/resolvers.ts b/src/graphql/resolvers.ts index 8d44672f7bde4c5803aec19077e1d8c8d94f4b07..5256612b1de2fbee97d87b30ba9536dfa6f8d277 100644 --- a/src/graphql/resolvers.ts +++ b/src/graphql/resolvers.ts @@ -1,228 +1,592 @@ /** - * @file Implémentation des requêtes GraphQL. - * @author akka vodol -*/ -import _ from 'lodash'; -import { assertBinaryExpression } from 'babel-types'; //hawkspar->akka ASKIP useless -import knex from '../../db/knex_router'; + * @file Résolveurs des requêtes GraphQL. + * @author akka vodol, ofacklam + * @memberof GraphQL + * @desc On ne définit dans ce fichier que les resolvers pour les types Query et Mutations. + * En effet on exploite les default resolvers fournis par Apollo Server (le deuxième point) : + * + * > Explicit resolvers are not needed for every type, since Apollo Server provides a default that can perform two actions depending on the contents of parent: + * > - Return the property from parent with the relevant field name + * > - Calls a function on parent with the relevant field name and provide the remaining resolver parameters as arguments + * https://www.apollographql.com/docs/apollo-server/v2/essentials/data.html#default + * + * Dans notre cas, le type de retour de chaque resolver est un objet JS (Group, Message, Request ou User) + * défini dans un des fichiers de `./object_resolvers/` ; donc les méthodes des classes Group, Message, Request et User + * dont le prototype est (parent, args, context: Context, info) sont utilisées comme resolvers de l'objet GraphQL respectif. + */ + +import { AuthenticationError } from "apollo-server-core"; +import { Context } from "./typeDefs/queries"; +import { User } from "./object_resolvers/users"; +import { Group, SimpleGroup, MetaGroup } from "./object_resolvers/groups"; +import { Announcement, Event, PrivatePost, Question, Answer, Message } from "./object_resolvers/messages"; +import { UserJoinGroup, GroupJoinMetagroup, GroupCoauthorEvent, Request } from "./object_resolvers/requests"; +import { GroupCollection, GroupSet } from "./models/tools"; -import '../config_passport'; +export const resolvers = { + Query: { -import * as connectors from './connectors/connectors'; -import * as authentifiers from './connectors/authentifiers'; -import MessageResolvers from './resolvers/messages'; -import GroupResolvers from './resolvers/groups'; + // User queries de base + // @rights connectedOrOnplatal + user: async function (root, args, context: Context): Promise<User> { + if(context.models.auth.isConnectedOrOnplatal()) { + return context.models.user.getUser(args.uid); + } + throw new AuthenticationError("Not connected or on-platal"); + }, -// nouveau test -/** - * @description Résolveurs des différentes requêtes GraphQL -*/ -class ChevalierError { - returned_error: any - name: String - message: String - constructor(err) { - this.returned_error = err; - this.name = "chevalier error"; - this.message = "Error encountered while running ldap access code : " + err.message; - } + // Group queries de base + // @rights connectedOrOnplatal + group: async function (root, args, context: Context): Promise<Group> { + if (context.models.auth.isConnectedOrOnplatal()) { + return context.models.group.getGroup(args.gid); + } + throw new AuthenticationError("Not connected or on-platal"); + }, - toString(): String { - return this.message; - } -} + // @rights connectedOrOnplatal + simpleGroup: async function (obj, args, context: Context): Promise<SimpleGroup> { + if (context.models.auth.isConnectedOrOnplatal()) { + return context.models.group.getSimpleGroup(args.gid); + } + throw new AuthenticationError("Not connected or on-platal"); + }, + // @rights connectedOrOnplatal + metaGroup: async function (obj, args, context: Context): Promise<MetaGroup> { + if (context.models.auth.isConnectedOrOnplatal()) { + return context.models.group.getMetaGroup(args.gid); + } + throw new AuthenticationError("Not connected or on-platal"); + }, -export const resolvers = { - // @rights user - Query: { + // Message queries de base + //message -> not implemented -> message(mid: ID!): Message - // group queries + // @rights membre d'un groupe author ou d'un groupe recipient + announcement: async function (root, args, context: Context): Promise<Announcement> { + //TODO : verifier les autorisations + throw "Not implemented"; + return context.models.message.getAnnouncement(args.mid); + throw new AuthenticationError("Not connected or on-platal"); + }, - allGroups: async function(root, args, context){ - let user = await authentifiers.anonymous(context.user); - return user && connectors.getAllVisibleGroups(user); + // @rights membre d'un groupe author ou d'un groupe recipient + event: async function (root, args, context: Context): Promise<Event> { + //TODO : verifier les autorisations + throw "Not implemented"; + return context.models.message.getEvent(args.mid); + throw new AuthenticationError("Not connected or on-platal"); }, - allSimpleGroups: async function (root, args, context){ - let user = await authentifiers.anonymous(context.user); - return user && connectors.getAllVisibleSimpleGroups(user); + // @rights membre du groupe recipient + privatePost: async function (root, args, context: Context): Promise<PrivatePost> { + //TODO : verifier les autorisations + throw "Not implemented"; + return context.models.message.getPrivatePost(args.mid); + throw new AuthenticationError("Not connected or on-platal"); }, - group: async function(root, args, context) { - let user = await authentifiers.anonymous(context.user); - return user && connectors.getGroupIfVisible(user, args.uid); + // @rights viewer du groupe recipient + question: async function (root, args, context: Context): Promise<Question> { + //TODO : verifier les autorisations + throw "Not implemented"; + return context.models.message.getQuestion(args.mid); + throw new AuthenticationError("Not connected or on-platal"); }, - simpleGroup: async function(obj, args, context){ - let user = await authentifiers.anonymous(context.user); - return user && connectors.getSimpleGroupIfVisible(user, args.uid); + // @rights viewer du groupe author + answer: async function (root, args, context: Context): Promise<Answer> { + //TODO : verifier les autorisations + throw "Not implemented"; + return context.models.message.getAnswer(args.mid); + throw new AuthenticationError("Not connected or on-platal"); }, - metaGroup: async function(obj, args, context){ - let user = await authentifiers.anonymous(context.user); - return user && connectors.getMetaGroupIfVisible(user, args.uid); + + // Request queries de base + //request -> not implemented -> request(rid: ID!): Request + + // @rights admin du groupe destinaire ou le user émetteur + userJoinGroupRequest: async function (root, args, context: Context): Promise<UserJoinGroup> { + //TODO : verifier les autorisations + throw "Not implemented"; + return context.models.request.getUserJoinGroupRequest(args.rid); + throw new AuthenticationError("Not connected or on-platal"); }, - /* - * Message queries. - */ + // @rights admin du groupe émetteur ou destinataire + groupJoinMetagroupRequest: async function (root, args, context: Context): Promise<GroupJoinMetagroup> { + //TODO : verifier les autorisations + throw "Not implemented"; + return context.models.request.getGroupJoinMetagroupRequest(args.rid); + throw new AuthenticationError("Not connected or on-platal"); + }, - allAnnouncements: function(obj, args, context) { - return knex.select().from("announcements"); + // @rights admin du groupe émetteur ou destinataire + groupCoauthorEventRequest: async function (root, args, context: Context): Promise<GroupCoauthorEvent> { + //TODO : verifier les autorisations + throw "Not implemented"; + return context.models.request.getGroupCoauthorEventRequest(args.rid); + throw new AuthenticationError("Not connected or on-platal"); }, - allEvents(root, args, context) { - return knex.select().from("events"); + // Tous les Messages visibles par un utilisateur(dont le uid, et donc les autorisations, est passé par context) + // @rights member of groups + allMessages: async function (root, args, context: Context): Promise<Message[]> { + let groups = context.models.auth.groupsMember(); + return context.models.message.getAllMessages(groups); }, - allMessages(root, args, context) { - const events = knex.select().from("events"); - const posts = knex.select().from("posts"); - return Promise.all([events, posts]).then(res => { - return _.flatten(res); - }); + // @rights member of groups + allAnnouncements: async function (root, args, context: Context): Promise<Announcement[]> { + let groups = context.models.auth.groupsMember(); + return context.models.message.getAllAnnouncements(groups); }, + // @rights member of groups + allEvents: async function (root, args, context: Context): Promise<Event[]> { + let groups = context.models.auth.groupsMember(); + return context.models.message.getAllEvents(groups); + }, - // user queries + // @rights member of groups + allPrivatePosts: async function (root, args, context: Context): Promise<PrivatePost[]> { + let groups = context.models.auth.groupsMember(); + return context.models.message.getAllPrivatePosts(groups); + }, - user: async function(obj, args, context){ - let user = await authentifiers.anonymous(context.user); - return user && connectors.getUser(user,args.uid); + // Tous les Groupes visibles par un utilisateur. + // Correspondrait au sous - champ "viewerOf" de User, volontairement non - défini comme tel.Tous les autres cas de figure sont couverts par les sous - champs "<permission>Of" de User + // @rights viewer of groups + allGroups: async function (root, args, context: Context): Promise<Group[]> { + let visibleGroupCollection = context.models.auth.groupsViewer(); + return context.models.group.getAllGroupsByCollection(visibleGroupCollection); }, - searchTOL: (obj, args, context) => { - const result = connectors.utilisateur.repliquerTOLdesIds({ - givenName: args.givenName, - lastName: args.lastName, - nickname: args.nickname, - nationality: args.nationality, - school: args.school, - promotion: args.promotion, - groups: args.groups, - studies: args.studies, - sport: args.sport, - phone: args.phone, - mail: args.mail, - adress: args.adress, - ip: args.ip - }); - return result; + // @rights viewer of groups + allSimpleGroups: async function (root, args, context: Context): Promise<SimpleGroup[]> { + let visibleGroupCollection = context.models.auth.groupsViewer(); + return context.models.group.getAllSimpleGroups(visibleGroupCollection.simpleGroups); }, - // viewer queries + // TOL + // @rights connectedOrOnplatal + searchTOL: async function (root, args, context: Context): Promise<User[]> { + if (context.models.auth.isConnectedOrOnplatal()) { + return context.models.user.searchTOL(args); + } + throw new AuthenticationError("Not connected or on-platal"); + } + }, - // member queries + Mutation: { - allMembers : async function(obj, args, context){ - let user = await authentifiers.member(context.user, args.from); - return user && connectors.getGroupMemberUsers(context.user, obj.groupUID); + // Groups - independent mutations + // @rights authenticated + editProfile: async function (root, args, context: Context): Promise<User> { + if(context.models.auth.isAuthenticated()) { + return context.models.user.editProfile(args); + } + throw new AuthenticationError("Not authenticated"); }, - // speaker queries + // Viewer mutations + // @rights viewer + likeGroup: async function (root, args, context: Context): Promise<boolean> { + if (context.models.auth.isViewer(args.gid)) { + return context.models.group.likeGroup(args.gid) + } + throw new AuthenticationError("Not a viewer"); + }, - allRequests: async function(obj, args, context){ - let res = []; - let user = authentifiers.admin(context.user, args.from); - if(user){ - res = res.concat(await connectors.getUserJoinGroupRequests(user, args.from)); + // @rights viewer + unlikeGroup: async function (root, args, context: Context): Promise<boolean> { + if (context.models.auth.isViewer(args.gid)) { + return context.models.group.unlikeGroup(args.gid) } - user = user || authentifiers.speaker(user, args.from); - if(user){ - res = res.concat(await connectors.getGroupJoinEventRequests(user, args.from)); - res = res.concat(await connectors.getYourGroupHostEventRequests(user, args.from)); + throw new AuthenticationError("Not a viewer"); + }, + + // @rights member d'un groupe author ou recipient + userParticipate: async function (root, args, context: Context): Promise<boolean> { + throw "Not implemented"; + //TODO : Vérifier les autorisations + return context.models.message.userParticipate(context.user.uid, args.forEvent); + throw new AuthenticationError("Not a viewer"); + }, + + // @rights member d'un groupe author ou recipient + userUnparticipate: async function (root, args, context: Context): Promise<boolean> { + throw "Not implemented"; + //TODO : Vérifier les autorisations + return context.models.message.userUnparticipate(context.user.uid, args.forEvent); + throw new AuthenticationError("Not a viewer"); + }, + + // @rights viewer + userRequestJoinGroup: async function (root, args, context: Context): Promise<UserJoinGroup> { + if(context.models.auth.isViewer(args.toGroup)) { + return context.models.request.userRequestJoinGroup(args.toGroup, args.comment); } - return res; + throw new AuthenticationError("Not a viewer"); }, - test: async function(obj, args, context){ - return connectors.getSimpleGroup(context.user, "br"); - } - }, + // @rights viewer + createQuestion: async function (root, args, context: Context): Promise<Question> { + if (context.models.auth.isViewer(args.toGroup)) { + return context.models.message.createQuestion(args.toGroup, args.title, args.content); + } + throw new AuthenticationError("Not a viewer"); + }, + // @rights viewer du groupe et author de la question + editQuestion: async function (root, args, context: Context): Promise<Question> { + throw "Not implemented"; + // TODO : Vérifier qu'il est l'auteur de la question et viewer + return context.models.message.editQuestion(args.questionToEdit, args.title, args.content); + throw new AuthenticationError("Not a viewer"); + }, - Request : { - __resolveType : function(obj){ - return obj.type; - } - }, + // @rights viewer du groupe et author de la question + removeQuestion: async function (root, args, context: Context): Promise<boolean> { + throw "Not implemented"; + // TODO : Vérifier qu'il est l'auteur de la question et viewer + return context.models.message.removeQuestion(args.questionToRemove); + throw new AuthenticationError("Not a viewer"); + }, - // @rights admin(obj.groupUID) - UserJoinGroup: { - user : (obj, args, context) => { - return connectors.getUser(context.user, obj.useruid); - /*return connectors.getUser(context.user, "quentin.gendre"); - if(obj.useruid === "anatole.romon"){ - return connectors.getUser(context.user, "anatole.romon").then(res => { - return connectors.getUser(context.user, "quentin.gendre"); - }); - }else{ - return new Promise( (resolve, reject) => { - resolve({givenName : "patrick"}); - }); - }*/ - } - }, + // Member mutations + // @rights member + userLeaveGroup: async function (root, args, context: Context): Promise<boolean> { + if (context.models.auth.isMember(args.gid)) { + return context.models.group.userLeaveGroup(args.gid); + } + throw new AuthenticationError("Not a member"); + }, - // @rights speaker(obj.groupUID) - GroupJoinEvent : { - event: (obj, args, context) => { - return connectors.getEvent(context.user, obj.eventuid); + // @rights member + createPrivatePost: async function (root, args, context: Context): Promise<PrivatePost> { + if (context.models.auth.isMember(args.toGroup)) { + return context.models.message.createPrivatePost(args.toGroup, args.title, args.content); + } + throw new AuthenticationError("Not a member"); }, - groupWantingToJoin: (obj, args, context) => { - return connectors.getGroup(context.user, obj.senderuid); - } - }, - // @rights speaker(obj.groupUID) - YourGroupHostEvent : { - event: (obj, args, context) => { - return connectors.getEvent(context.user, obj.eventuid); + // @rights member du groupe et author du post + editPrivatePost: async function (root, args, context: Context): Promise<PrivatePost> { + throw "Not implemented"; + // TODO : Vérifier qu'il est l'auteur du post et member + return context.models.message.editPrivatePost(args.privatePostToEdit, args.title, args.content); + throw new AuthenticationError("Not a member"); }, - sender: (obj, args, context) => { - return connectors.getGroup(context.user, obj.senderuid); - } - }, - // @rights user - User : { - groups : (obj, args, context) => { - let result = Promise.all(obj.groups.map((grid) => { - return connectors.getSimpleGroup(context.user,grid); - })); + // @rights member du groupe et author du post + removePrivatePost: async function (root, args, context: Context): Promise<boolean> { + throw "Not implemented"; + // TODO : Vérifier qu'il est l'auteur du post et member + return context.models.message.removePrivatePost(args.privatePostToRemove); + throw new AuthenticationError("Not a member"); + }, - return result.then(groups => { - return _.filter(groups,(o) => !_.isUndefined(o)); - }); + // Speaker mutations + // @rights speaker + writePostsSummary: async function (root, args, context: Context): Promise<boolean> { + if (context.models.auth.isSpeaker(args.forGroup)) { + return context.models.group.writePostsSummary(args.forGroup, args.content); + } + throw new AuthenticationError("Not a speaker"); }, - }, + // @rights speaker + groupParticipate: async function (root, args, context: Context): Promise<boolean> { + if (context.models.auth.isSpeaker(args.gid)) { + return context.models.message.groupParticipate(args.gid, args.forEvent); + } + throw new AuthenticationError("Not a speaker"); + }, - // @rights user - Mutation: { + // @rights speaker + groupUnparticipate: async function (root, args, context: Context): Promise<boolean> { + if (context.models.auth.isSpeaker(args.gid)) { + return context.models.message.groupUnparticipate(args.gid, args.forEvent); + } + throw new AuthenticationError("Not a speaker"); + }, + + // @rights speaker du groupe émetteur + groupRequestCoauthorEvent: async function (root, args, context: Context): Promise<GroupCoauthorEvent> { + if (context.models.auth.isSpeaker(args.fromGroup)) { + return context.models.request.groupRequestCoauthorEvent(args.fromGroup, args.toGroup, args.forEvent, args.comment); + } + throw new AuthenticationError("Not a speaker"); + }, + + // @rights speaker du groupe émetteur + createAnnouncement: async function (root, args, context: Context): Promise<Announcement> { + if (context.models.auth.isSpeaker(args.fromGroup)) { + return context.models.message.createAnnouncement(args.fromGroup, new GroupSet(args.toGroups), args.title, args.content, args.forEvent); + } + throw new AuthenticationError("Not a speaker"); + }, + + // @rights speaker du groupe émetteur + editAnnouncement: async function (root, args, context: Context): Promise<Announcement> { + throw "Not implemented"; + // TODO : Vérifier les autorisations. + return context.models.message.editAnnouncement(args.announcementToEdit, args.title, args.content, args.forEvent); + throw new AuthenticationError("Not a speaker"); + }, + + // @rights speaker du groupe émetteur + removeAnnouncement: async function (root, args, context: Context): Promise<boolean> { + throw "Not implemented"; + // TODO : Vérifier les autorisations + return context.models.message.removeAnnouncement(args.announcementToRemove); + throw new AuthenticationError("Not a speaker"); + }, + + // @rights speaker du groupe émetteur + createEvent: async function (root, args, context: Context): Promise<Event> { + if (context.models.auth.isSpeaker(args.fromGroup)) { + return context.models.message.createEvent(args.fromGroup, + new GroupSet(args.toGroups), + args.title, + args.content, + args.location, + args.startTime, + args.endTime, + args.forAnnouncement); + } + throw new AuthenticationError("Not a speaker"); + }, + + // @rights speaker du groupe émetteur + editEvent: async function (root, args, context: Context): Promise<Event> { + throw "Not implemented"; + // TODO : Vérifier les autorisations. + return context.models.message.editEvent(args.eventToEdit, + args.title, + args.content, + args.location, + args.startTime, + args.endTime, + args.forAnnouncement); + throw new AuthenticationError("Not a speaker"); + }, + + // @rights speaker du groupe émetteur + removeEvent: async function (root, args, context: Context): Promise<boolean> { + throw "Not implemented"; + // TODO : Vérifier les autorisations + return context.models.message.removeEvent(args.eventToRemove); + throw new AuthenticationError("Not a speaker"); + }, - // Superviser mutations + // @rights speaker du groupe + createAnswer: async function (root, args, context: Context): Promise<Answer> { + throw "Not implemented"; + // TODO : Vérifier les autorisations. + return context.models.message.createAnswer(args.forQuestion, args.title, args.content); + throw new AuthenticationError("Not a speaker"); + }, - takeAdminRights : async function(obj, args, context){ - let user = await authentifiers.superviser(context.user, args.from); - return user && await connectors.takeAdminRights(user, args.from, user.justification); + // @rights speaker du groupe + editAnswer: async function (root, args, context: Context): Promise<Answer> { + throw "Not implemented"; + // TODO : Vérifier les autorisations. + return context.models.message.editAnswer(args.answerToEdit, args.title, args.content); + throw new AuthenticationError("Not a speaker"); }, - releaseAdminRights : async function(obj, args, context){ - let user = await authentifiers.superviser(context.user, args.from); - return user && await connectors.releaseAdminRights(user, args.from, user.justification); + // @rights speaker du groupe + removeAnswer: async function (root, args, context: Context): Promise<boolean> { + throw "Not implemented"; + // TODO : Vérifier les autorisations + return context.models.message.removeAnswer(args.answerToRemove); + throw new AuthenticationError("Not a speaker"); }, // Admin mutations + // @rights admin of parent group + createSubgroup: async function (root, args, context: Context): Promise<Group> { + if(context.models.auth.isAdmin(args.fromGroup)) { + return context.models.group.createSubgroup(args); + } + throw new AuthenticationError("Not an admin"); + }, - createSubgroup: async function (obj, args, context){ - let user = authentifiers.admin(context.user, args.from); - return user && connectors.createSubgroup(user, args); + // @rights admin + makeAdmin: async function (root, args, context: Context): Promise<User> { + if (context.models.auth.isAdmin(args.forGroup)) { + return context.models.group.makeAdmin(args.forGroup, args.uid); + } + throw new AuthenticationError("Not an admin"); + }, + + // @rights admin + unmakeAdmin: async function (root, args, context: Context): Promise<User> { + if (context.models.auth.isAdmin(args.forGroup)) { + return context.models.group.unmakeAdmin(args.forGroup, args.uid); + } + throw new AuthenticationError("Not an admin"); + }, + + // @rights admin + makeSpeaker: async function (root, args, context: Context): Promise<User> { + if (context.models.auth.isAdmin(args.forGroup)) { + return context.models.group.makeSpeaker(args.forGroup, args.uid); + } + throw new AuthenticationError("Not an admin"); }, - }, - - ...MessageResolvers, + // @rights admin + unmakeSpeaker: async function (root, args, context: Context): Promise<User> { + if (context.models.auth.isAdmin(args.forGroup)) { + return context.models.group.unmakeSpeaker(args.forGroup, args.uid); + } + throw new AuthenticationError("Not an admin"); + }, + + // @rights admin + editGroup: async function (root, args, context: Context): Promise<Group> { + if (context.models.auth.isAdmin(args.forGroup)) { + return context.models.group.editGroup(args); + } + throw new AuthenticationError("Not an admin"); + }, + + // @rights admin + addVisibilityEdge: async function (root, args, context: Context): Promise<boolean> { + if (context.models.auth.isAdmin(args.forGroup)) { + return context.models.group.addVisibilityEdge(args.forGroup, args.visibleBy); + } + throw new AuthenticationError("Not an admin"); + }, - ...GroupResolvers, + // @rights admin + removeVisibilityEdge: async function (root, args, context: Context): Promise<boolean> { + if (context.models.auth.isAdmin(args.forGroup)) { + return context.models.group.removeVisibilityEdge(args.forGroup, args.visibleBy); + } + throw new AuthenticationError("Not an admin"); + }, + + // @rights admin du groupe émetteur + groupRequestJoinMetagroup: async function (root, args, context: Context): Promise<GroupJoinMetagroup> { + if (context.models.auth.isAdmin(args.fromGroup)) { + return context.models.request.groupRequestJoinMetagroup(args.fromGroup, args.toMetagroup, args.comment); + } + throw new AuthenticationError("Not an admin"); + }, + + // @rights admin du groupe destinataire + acceptUserJoinRequest: async function (root, args, context: Context): Promise<boolean> { + let req = await UserJoinGroup.tryCreate(args.request); + throw "Not implemented"; + //TODO : Vérifier les autorisations + //if (context.models.auth.isAdmin(req.to)) { + return context.models.request.acceptUserJoinRequest(req, args.comment); + throw new AuthenticationError("Not an admin"); + }, + + // @rights admin du groupe destinataire + acceptGroupJoinRequest: async function (root, args, context: Context): Promise<boolean> { + let req = await GroupJoinMetagroup.tryCreate(args.request); + throw "Not implemented"; + //TODO : Vérifier les autorisations + //if (context.models.auth.isAdmin(req.to)) { + return context.models.request.acceptGroupJoinRequest(req, args.comment); + throw new AuthenticationError("Not an admin"); + }, + + // @rights admin du groupe destinataire + refuseUserJoinRequest: async function (root, args, context: Context): Promise<boolean> { + let req = await UserJoinGroup.tryCreate(args.request); + throw "Not implemented"; + //TODO : Vérifier les autorisations + //if (context.models.auth.isAdmin(req.to)) { + return context.models.request.refuseUserJoinRequest(req, args.comment); + throw new AuthenticationError("Not an admin"); + }, + + // @rights admin du groupe destinataire + refuseGroupJoinRequest: async function (root, args, context: Context): Promise<boolean> { + let req = await GroupJoinMetagroup.tryCreate(args.request); + throw "Not implemented"; + //TODO : Vérifier les autorisations + //if (context.models.auth.isAdmin(req.to)) { + return context.models.request.refuseGroupJoinRequest(req, args.comment); + throw new AuthenticationError("Not an admin"); + }, + + // @rights admin + removeUser: async function (root, args, context: Context): Promise<User> { + if(context.models.auth.isAdmin(args.fromGroup)) { + return context.models.group.removeUser(args.fromGroup, args.uid); + } + throw new AuthenticationError("Not an admin"); + }, + + // @rights admin du groupe destinataire + acceptGroupCoauthorEventRequest: async function (root, args, context: Context): Promise<boolean> { + // Pour l'instant, ce n'est pas a implémenter... + let req = await GroupCoauthorEvent.tryCreate(args.request); + throw "Not implemented"; + //TODO : Vérifier les autorisations + //if (context.models.auth.isAdmin(req.to)) { + return context.models.request.acceptGroupCoauthorEventRequest(req, args.comment); + throw new AuthenticationError("Not an admin"); + }, + + // @rights admin du groupe destinataire + refuseGroupCoauthorEventRequest: async function (root, args, context: Context): Promise<boolean> { + // Pour l'instant, ce n'est pas a implémenter... + let req = await GroupCoauthorEvent.tryCreate(args.request); + throw "Not implemented"; + //TODO : Vérifier les autorisations + //if (context.models.auth.isAdmin(req.to)) { + return context.models.request.refuseGroupCoauthorEventRequest(req, args.comment); + throw new AuthenticationError("Not an admin"); + }, + + // @rights admin du groupe + censorQuestion: async function (root, args, context: Context): Promise<boolean> { + throw "Not implemented"; + //TODO : Vérifier les autorisations + return context.models.message.censorQuestion(args.questionToCensor); + throw new AuthenticationError("Not an admin"); + }, + + // @rights admin du groupe + censorAnswer: async function (root, args, context: Context): Promise<boolean> { + throw "Not implemented"; + //TODO : Vérifier les autorisations + return context.models.message.censorAnswer(args.answerToCensor); + throw new AuthenticationError("Not an admin"); + }, + + // @rights admin du groupe + censorPrivatePost: async function (root, args, context: Context): Promise<boolean> { + throw "Not implemented"; + //TODO : Vérifier les autorisations + return context.models.message.censorPrivatePost(args.privatePostToCensor); + throw new AuthenticationError("Not an admin"); + }, + + // @rights admin d'un groupe author + censorAnnouncement: async function (root, args, context: Context): Promise<boolean> { + throw "Not implemented"; + //TODO : Vérifier les autorisations + return context.models.message.censorAnnouncement(args.announcementToCensor); + throw new AuthenticationError("Not an admin"); + }, + + // @rights admin d'un groupe author + censorEvent: async function (root, args, context: Context): Promise<boolean> { + throw "Not implemented"; + //TODO : Vérifier les autorisations + return context.models.message.censorEvent(args.eventToCensor); + throw new AuthenticationError("Not an admin"); + } + } }; diff --git a/src/graphql/resolvers/groups.ts b/src/graphql/resolvers/groups.ts deleted file mode 100644 index c3795475507c8fc6011448878bad1412737f1519..0000000000000000000000000000000000000000 --- a/src/graphql/resolvers/groups.ts +++ /dev/null @@ -1,79 +0,0 @@ -/** - * @file ? - * @author ? - */ - -import * as connectors from '../connectors/connectors'; -import * as authentifiers from '../connectors/authentifiers'; -//hawkspar->akka ; doc ? -const GroupResolvers = { - // @rights viewer(obj.uid) - Group: { - __resolveType: async (obj) => { - switch (obj.type) { - case "simple": - return "SimpleGroup"; - case "meta": - return "MetaGroup"; - } - } - }, - - // @rights viewer(obj.uid) - SimpleGroup: { - async admins(obj, args, context) { - let user = await authentifiers.member(context.user, obj.uid); - return await user.ldap_access.listMembers(context.user, obj.uid); - }, - - async members(obj, args, context) { - let user = await authentifiers.member(context.user, obj.uid); - const res: String[] = await user.ldap_access.listMembers(context.user, obj.uid); - return res.map(x => { - return {uid: x}; - }); - }, - - async likers(obj, args, context) { - return connectors.utilisateur.listMembers(context.user, obj.uid); - }, - - async privatePosts(obj, args, context) { - let user = await authentifiers.member(context.user, obj.uid); - return user && connectors.receivedPrivatePosts(user, obj.uid); - }, - - async questions(obj, args, context) { - let user = await authentifiers.member(context.user, obj.uid); - return user && connectors.receivedQuestions(user, obj.uid); - }, - - async answers(obj, args, context) { - let user = await authentifiers.member(context.user, obj.uid); - return user && connectors.receivedAnswers(user, obj.uid); - } - }, - - // @rights viewer(obj.uid) - MetaGroup: { - async members(obj, args, context) { - }, - - async privatePosts(obj, args, context) { - let user = await authentifiers.member(context.user, obj.uid); - return user && connectors.receivedPrivatePosts(user, obj.uid); - }, - - async questions(obj, args, context) { - let user = await authentifiers.member(context.user, obj.uid); - return user && connectors.receivedQuestions(user, obj.uid); - }, - - async answers(obj, args, context) { - let user = await authentifiers.member(context.user, obj.uid); - return user && connectors.receivedAnswers(user, obj.uid); - } - } -}; - -export default GroupResolvers; \ No newline at end of file diff --git a/src/graphql/resolvers/messages.js b/src/graphql/resolvers/messages.js deleted file mode 100644 index 85c9d8feedfce053f4cad059fa7602d2d5a82994..0000000000000000000000000000000000000000 --- a/src/graphql/resolvers/messages.js +++ /dev/null @@ -1,121 +0,0 @@ -/** - * @file Resolvers pour tous les types de messages. hawkspar->akka ; bien, mais pas suffisant - * @author akka - */ -import * as connectors from '../connectors/connectors'; - -const MessageResolvers = { - Message: { - __resolveType: function(obj) { - if (obj.location) { - return "Event"; - } - if (obj.views) { - return "Announcement"; - } - if (obj.for_question) { - return "Answer"; - } - if (obj.for_answer) { - return "Question"; - } - return "PrivatePost"; - } - }, - - Announcement: { - forEvent : function(obj, args, context){ - // le champ is_announcement n'existe que sur les Events - // une ligne de la bdd events peut résoudre comme évènement ou comme annonce - if(obj.is_announcement) - return obj; - else - return null; - }, - - authors: async function (obj, args, context){ - return connectors.getMessageGroupAuthors(context.user, obj.id); - }, - - recipients: async function (obj, args, context){ - return connectors.getMessageGroupRecipients(context.user, obj.id); - }, - - }, - - Event: { - - startTime: function (obj) { - return obj.start_time; - }, - - endTime: function (obj) { - return obj.end_time; - }, - - asAnnouncement: function (obj, args, context) { - // le champ is_announcement indique si il existe une annonce qui va avec l'évènement - // une ligne de la bdd events peut résoudre comme évènement ou comme annonce - if (obj.is_announcement) - return obj; - else - return null; - }, - - authors: async function (obj, args, context) { - return connectors.getMessageGroupAuthors(context.user, obj.id); - }, - - recipients: async function (obj, args, context) { - return connectors.getMessageGroupRecipients(context.user, obj.id); - } - }, - - - - PrivatePost : { - - authors: async function(obj, args, context){ - return connectors.getUser(context.user, obj.author_uid, obj.author_db); - - }, - - recipients: async function(obj, args, context){ - return connectors.getGroup(context.user, obj.recipient_uid); - - } - }, - - Question : { - - authors: async function(obj, args, context){ - return connectors.getUser(context.user, obj.author_uid, obj.author_db); - }, - - recipients: async function(obj, args, context){ - return connectors.getGroup(context.user, obj.recipient_uid); - }, - - forAnswer: function(obj, args, context){ - return obj.for_answer; - } - }, - - Answer : { - - authors: async function(obj, args, context){ - return connectors.getGroup(context.user, obj.author_uid); - }, - - recipients: async function(obj, args, context){ - return connectors.getGroup(context.user, obj.recipient_uid); - }, - - forQuestion: function(obj, args, context){ - return obj.for_question; - - }, - } -}; - -export default MessageResolvers; \ No newline at end of file diff --git a/src/graphql/schema.js b/src/graphql/schema.ts similarity index 50% rename from src/graphql/schema.js rename to src/graphql/schema.ts index 77c88112ca5856a69e3fa1b398575fbd8393c6fb..12b34f9f0fb3b42122a2be73d7eaefbaa3450f62 100644 --- a/src/graphql/schema.js +++ b/src/graphql/schema.ts @@ -3,8 +3,8 @@ * @author akka vodol */ -import actionDefs from './typeDefs/actions.graphql'; -import objectDefs from './typeDefs/objects.graphql'; +const actionDefs = require('./typeDefs/actions.graphql'); +const objectDefs = require('./typeDefs/objects.graphql'); import { resolvers } from './resolvers'; const typeDefs = actionDefs.concat(objectDefs); @@ -13,7 +13,9 @@ const schema = { typeDefs, resolvers, logger: {log: e => console.log(e)}, - inheritResolversFromInterfaces: true + inheritResolversFromInterfaces: true, + //formatResponse: response => { console.log(JSON.stringify(response)); return response; }, + //formatError: error => {console.log(JSON.stringify(error)); return error; } }; export default schema; \ No newline at end of file diff --git a/src/graphql/typeDefs/actions.graphql b/src/graphql/typeDefs/actions.graphql index 18fe6cf9786bb589aebfbda139ae4356142d5b97..9fe106739adfd8abdadeab53f27331ceae4636c1 100644 --- a/src/graphql/typeDefs/actions.graphql +++ b/src/graphql/typeDefs/actions.graphql @@ -1,244 +1,285 @@ -# Requêtes -type Query { - - # group queries - - allGroups: [Group] - allSimpleGroups: [SimpleGroup] - - group(uid: ID!) : Group - simpleGroup(uid : ID!) : SimpleGroup - metaGroup(uid : ID!) : MetaGroup - - # message queries +""" +@file Définit les types spéciaux Query et Mutation, points d'entrée du schéma GraphQL. +@author akka vodol, kadabra, ofacklam +""" +type Query { + # User queries de base + user(uid:ID!): User + + # Group queries de base + group(gid:ID!): Group + simpleGroup(gid:ID!): SimpleGroup + metaGroup(gid:ID!): MetaGroup + + # Message queries de base + announcement(mid:ID!): Announcement + event(mid:ID!): Event + privatePost(mid:ID!): PrivatePost + question(mid:ID!): Question + answer(mid:ID!): Answer + + # Request queries de base + userJoinGroupRequest(rid:ID!): UserJoinGroup + groupJoinMetagroupRequest(rid:ID!): GroupJoinMetagroup + groupCoauthorEventRequest(rid:ID!): GroupCoauthorEvent + + # Tous les Messages visibles par un utilisateur (dont le uid, et donc les autorisations, est passé par context) allMessages: [Message] - message(id: ID!): Message - allEvents: [Event] allAnnouncements: [Announcement] - - # user queries - - user(uid: ID) : User + allEvents: [Event] + allPrivatePosts: [PrivatePost] + # Tous les Groupes visibles par un utilisateur. + # Correspondrait au sous-champ "viewerOf" de User, volontairement non-défini comme tel. Tous les autres cas de figure sont couverts par les sous-champs "<permission>Of" de User + allGroups: [Group] + allSimpleGroups: [SimpleGroup] + # TOL searchTOL( givenName: String, lastName: String, nickname: String, nationality: String, school: String, - promotion: String, - groups: String, + groups: [String], studies: String, - sport: String, phone: String, mail: String, - adress: String, - ip: String - ): [String] - - # Admin queries - - allRequests(from : String!) : [Request] - - # Speaker queries + address: String + ): [User!] - # allRequests(from : String!) : [Request] - - # Member Queries - - allMembers(from : String!) : [Group] - - # Viewer Queries - - test : String } type Mutation { + """ + Par rapport à un groupe donné, un user peut avoir différents niveaux de droits : + - none : ne sait meme pas que le groupe existe, aucun autre droit (typiquement, une connection où l'utilisateur ne s'est pas authentifié) + - authenticated : sait que le groupe existe, aucun autre droit (une connection on-platal sans auth, ou une connection authentifiée) + - viewer : le user a aussi accès à l'activité publique du groupe : frontpage, Q&A, liste des membres, speakers et admins, Message dont le groupe est destinataire + - member : le user a aussi acces à l'activité interne du groupe : les PrivatePost, ainsi que les Message dont le groupe est auteur + - speaker : le user peut aussi parler au nom du groupe. Il a le droit de publier des annonces et d'organiser des évènements + - admin : le user a tous les droits sur le groupe + + Un des rôles du *graphe organique des groupes* est de définir le niveau de droit des users pour chaque groupe. + D'abord, petit détail de terminologie : les cinq niveaux de droits sont inclus les uns dans les autres (un speaker est aussi un viewer, par ex.) (sauf pour les permissions héritées) + Plus précisément, on a les inclusions suivantes : admin strict > speaker > membre strict > membre hérité > viewer > authenticated. + Il n'y a que admin hérité qui n'est pas nécessairement inclus dans les autres niveaux de droits. + + Dans la BDD sous-jacente, il faut que pour chaque groupe, on ait admin > speaker > member. + + Détaillons ici les conditions exactes pour avoir un niveau de droit donné. + == Pour les groupes simples == + - Member : + - Un user est membre strict du groupe G s'il est member de G selon la BDD sous-jacente. + - Un user est membre hérité du groupe G s'il est membre strict d'un de ses descendants. + - Speaker : un user est speaker du groupe G s'il est speaker de G selon la BDD. Pas de notion d'héritage de speaker. + - Admin : + - Un user est admin strict du groupe G s'il est admin de G selon la BDD. + - Un user est admin hérité du groupe G s'il est admin strict d'un de ses ascendants. + - Viewer : un user est viewer du groupe G + - s'il est membre hérité de G. + - s'il est membre hérité d'un groupe immédiatement parent de G (one-edge-down visibility), ou + - s'il est membre hérité d'un groupe faisant partie du champ "visibilityEdge" de G + - s'il est membre d'un métagroupe dont G est membre (implicit visibility-edges). + - Dans tous les autres cas, le user a le niveau de droits "none" ou "authenticated", selon le cas de figure. + + == Pour les méta-groupes == + - Un user est membre d'un méta-groupe G s'il est membre (hérité) d'un groupe simple dans G. + - Un user est speaker d'un méta-groupe G s'il est speaker d'un groupe simple dans G. + - Un user est admin d'un méta-groupe G s'il est admin (hérité) d'un groupe simple dans G. + - Un user est viewer d'un méta-groupe G s'il est viewer d'un groupe simple dans G. + - # Superviser mutations - - takeAdminRights(from : String!) : Boolean - releaseAdminRights(from : String!) : Boolean - - # Admin mutations - - createSubgroup( - from : String!, - uid: ID = null, - name: String!, - website: String, - description: String, - school: String - ): Group - - addUser(from : String!, userid : String!): User - removeUser(from : String!, userid : String!): User - addAdmin(from : String!, userid : String!): User - removeAdmin(from : String!, userid : String!): User - - editGroup( - from : String!, - name: String, - website: String, - description: String, - school: String - ): Group + L'autre rôle du *graphe organique des groupes* est de permettre l'administration "en cascade" des groupes enfants. + Si un groupe est le parent d'un autre, alors les admins du groupe parent sont admins (hérités) du groupe enfant. Exemples : + - BR est parent de Chocapix. L'admin de Chocapix est parti en vacances. L'admin de BR peut, en tant qu'admin (hérité) de Chocapix, faire ce qu'il a à faire, sans attendre le retour du respo Chocapix. + - Cotisants-Kès est parent de Troll'X. Troll'X fait n'importe quoi (en floodant de Messages par ex). Les admins de Cotisants-Kès (les kessiers) peuvent, comme ils sont admins hérités de Troll'X, stopper les dégâts. + + Remarque sur speaker : + Il s'agit d'un nouveau niveau de droit par rapport à Frankiz 3.0 ; qui est a présent implémenté sur le LDAP bloaziadur. + + Les mutations ci-dessous sont divisées en quatre, selon le niveau de droit requis pour pouvoir les appeler. + Les Mutations concernant les *Requests* suivent à peu près toutes le schéma suivant : + - <typeAuteur>Request<NatureDeLaRequest>(auteur, destinataire, comment): Request + - accept<NatureDeLaRequest>Request(request: ID!, comment: String): Boolean + - refuse<NatureDeLaRequest>Request(request: ID!, comment: String): Boolean + - le paramètre est le rid de la Request à accepter ou refuser + - seul les admins du groupe destinataire peuvent accepter ou refuser une Request + Les Mutations concernant les *Messages* suivent à peu près toutes le schéma suivant : + - create<TypeDeMessage>(auteur, destinataire, title, content, etc.): <TypeDeMessage> + - edit<TypeDeMessage>(<typeDeMessage>ToEdit: ID!): <TypeDeMessage> + - remove<TypeDeMessage>(<typeDeMessage>ToRemove: ID!): Boolean + - = l'auteur supprime le message + - pour les Messages où l'auteur est un utilisateur, seul l'auteur a le droit de remove son Message + - pour les Messages où l'auteur est un groupe, n'importe quel admin du groupe (ou speaker selon le cas) a le droit de remove le Message + - censor<TypeDeMessage>(<typeDeMessage>ToCensor: ID!): Boolean + - = le groupe destinataire supprime le message + - n'importe quel admin du groupe a le droit de censurer un Message qui lui est adressé + - (le destinataire est un Group pour tous les Messages) + """ + + # Groups-independent mutations + editProfile( + nickname: String, + mail: String, + phone: String + ): User - # Creates a new event. - postEvent( - # ID of the organizing group. - from: ID!, - # Title of the event. + # Viewer mutations + likeGroup(gid: ID!): Boolean # devenir sympathisant + unlikeGroup(gid: ID!): Boolean + userParticipate( + forEvent: ID! + ): Boolean + userUnparticipate( + forEvent: ID! + ): Boolean + + userRequestJoinGroup(toGroup: ID!, comment: String): UserJoinGroup + + createQuestion( + toGroup: ID!, title: String, - # Date of event. - date: String + content: String, + ): Question + editQuestion( + questionToEdit: ID!, + title: String, + content: String, + ): Question + removeQuestion( + questionToRemove: ID! + ): Boolean + + # Member mutations + userLeaveGroup(gid: ID!): Boolean + + createPrivatePost( + toGroup: ID!, + title: String, + content: String + ): PrivatePost + editPrivatePost( + privatePostToEdit: ID!, + title: String, + content: String + ): PrivatePost + removePrivatePost( + privatePostToRemove: ID! + ): Boolean + + # Speaker mutations + writePostsSummary(forGroup: ID!, content: String): Boolean + + groupParticipate(gid: ID!, forEvent: ID!): Boolean + groupUnparticipate(gid: ID!, forEvent: ID!): Boolean + + groupRequestCoauthorEvent( + fromGroup: ID!, + toGroup: ID!, + forEvent: ID!, + comment: String + ): GroupCoauthorEvent + + createAnnouncement( + fromGroup: ID!, + toGroups: [ID!], + title: String, + content: String, + forEvent: ID + ): Announcement + editAnnouncement( + announcementToEdit: ID!, + title: String, + content: String, + forEvent: ID + ): Announcement + removeAnnouncement( + announcementToRemove: ID! + ): Boolean + + createEvent( + fromGroup: ID!, + toGroups: [ID!], + title: String, + content: String, + location: String, + startTime: String, + endTime: String, + forAnnouncement: ID ): Event + editEvent( + eventToEdit: ID!, + title: String, + content:String, + location: String, + startTime: String, + endTime: String, + forAnnouncement: ID + ): Event + removeEvent( + eventToRemove: ID! + ): Boolean - answerEventRequest(from : String, request: ID, accept : Boolean): Request - - # Log user into the API - login(username: String!, password: String!): String! - # Log user out of the API - logout(username: String!): Boolean - leave(from : String!) : Boolean - - # Viewer mutations - - requestJoin(from : String!) : Boolean - -} - - - -# asSuperviser(groupUID: String): SuperviserMutation -# asAdmin(groupUID: String): AdminMutation -# asSpeaker(groupUID: String): SpeakerMutation -# asMember(groupUID: String): MemberMutation -# asViewer(groupUID: String): ViewerMutation - - - -# accessGroups : GroupQuery -# accessPosts : MessageQuery -# accessUsers : UserQuery - -# asAdmin(groupUID: ID): AdminQuery -# asSpeaker(groupUID: ID): AdminQuery -# asMember(groupUID: ID): MemberQuery -# asViewer(groupUID: ID): AdminQuery - - -type SuperviserMutation { - runAdminOperation : AdminMutation - takeAdminRights : Boolean - releaseAdminRights : Boolean -} - -type AdminMutation { - - isAdmin: Boolean + createAnswer( + forQuestion: ID!, + title: String, + content: String + ): Answer + editAnswer( + answerToEdit: ID!, + title: String, + content: String + ): Answer + removeAnswer( + answerToRemove: ID! + ): Boolean + # Admin mutations createSubgroup( - uid: ID = null, - name: String, - website: String, - description: String, - school: String + fromGroup: ID!, + subGid: ID, + subName: String!, + subDescription: String, + subMail: String, + subWebsite: String, + subSchool: String ): Group - addUser(userid : String): User - removeUser(userid : String): User - addAdmin(userid : String): User - removeAdmin(userid : String): User + makeAdmin(forGroup: ID!, uid: ID!): User + unmakeAdmin(forGroup: ID!, uid: ID!): User + makeSpeaker(forGroup: ID!, uid: ID!): User + unmakeSpeaker(forGroup: ID!, uid: ID!): User editGroup( + forGroup: ID!, name: String, - website: String, description: String, + mail: String, + website: String, + frontPage: String, school: String ): Group -} + addVisibilityEdge(forGroup: ID!, visibleBy: ID!): Boolean + removeVisibilityEdge(forGroup: ID!, visibleBy: ID!): Boolean -type SpeakerMutation{ - postEvent(name: String, date: String): Event - answerEventRequest(request: ID, accept : Boolean): Request -} + groupRequestJoinMetagroup(fromGroup: ID!, toMetagroup: ID!, comment: String): GroupJoinMetagroup -type MemberMutation { - leave: Group -} + acceptUserJoinRequest(request: ID!, comment: String): Boolean + acceptGroupJoinRequest(request: ID!, comment: String): Boolean + refuseUserJoinRequest(request: ID!, comment: String): Boolean + refuseGroupJoinRequest(request: ID!, comment: String): Boolean -type ViewerMutation { - requestJoin: Group -} + removeUser(fromGroup: ID!, uid: ID!): User + acceptGroupCoauthorEventRequest(request: ID!, comment: String): Boolean + refuseGroupCoauthorEventRequest(request: ID!, comment: String): Boolean -""" -Requête pour obtenir un groupe. -""" -type GroupQuery{ - allGroups: [Group] - allSimpleGroups: [SimpleGroup] - - group(uid: ID) : Group - simpleGroup(uid : ID) : SimpleGroup - metaGroup(uid : ID) : MetaGroup - -} - -""" -Requête pour obtenir un message. -""" -type MessageQuery{ - allMessages: [Message] - allEvents: [Event] - message(id: ID): Message - allAnnouncements: [Announcement] -} - - -""" -Requête pour obtenir un utilisateur. -""" -type UserQuery{ - - user(uid: ID) : User - - searchTOL( - givenName: String, - lastName: String, - nickname: String, - nationality: String, - school: String, - promotion: String, - groups: String, - studies: String, - sport: String, - phone: String, - mail: String, - adress: String, - ip: String - ): [String] -} - -# Requête à la base de donnée nécessitant d'être administrateur. -type AdminQuery{ - isAdmin: Boolean - allRequests : [Request] -} - -type SpeakerQuery{ - isSpeaker: Boolean - allRequests : [Request] -} - -type MemberQuery{ - isMember: Boolean - allMembers : [Group] -} + censorQuestion(questionToCensor: ID!): Boolean + censorAnswer(answerToCensor: ID!): Boolean + censorPrivatePost(privatePostToCensor: ID!): Boolean + censorAnnouncement(announcementToCensor: ID!): Boolean + censorEvent(eventToCensor: ID!): Boolean -type ViewerQuery{ - isViewer: Boolean } diff --git a/src/graphql/typeDefs/actions_wish_list.graphql b/src/graphql/typeDefs/actions_wish_list.graphql deleted file mode 100644 index 4fc70a09f220bc25a1201df8d8e60ce49772039d..0000000000000000000000000000000000000000 --- a/src/graphql/typeDefs/actions_wish_list.graphql +++ /dev/null @@ -1,266 +0,0 @@ -""" -@file Définit les types spéciaux Query et Mutation, points d'entrée du schéma GraphQL. - Ce fichier est la wish-list de kadabra (qui veut avoir un schéma clair pour travailler sereinement sur le front). -@author akka vodol, kadabra -""" - -type Query { - # User queries de base - user(uid:ID!): User - - # Group queries de base - group(gid:ID!): Group - simpleGroup(gid:ID!): SimpleGroup - metaGroup(gid:ID!): MetaGroup - - # Message queries de base - message(mid:ID!): Message - announcement(mid:ID!): Announcement - event(mid:ID!): Event - privatePost(mid:ID!): PrivatePost - question(mid:ID!): Question - answer(mid:ID!): Answer - - # Request queries de base - request(rid:ID!): Request - userJoinGroupRequest(rid:ID!): UserJoinGroup - groupCoauthorEventRequest(rid:ID!): GroupCoauthorEvent - - - - # Tous les Messages visibles par un utilisateur (dont le uid, et donc les autorisations, est passé par context) - allMessages: [Message] - allAnnouncements: [Announcement] - allEvents: [Event] - # Tous les Groupes visibles par un utilisateur. - # Correspondrait au sous-champ "viewerOf" de User, volontairement non-défini comme tel. Tous les autres cas de figure sont couverts par les sous-champs "<permission>Of" de User - allGroups: [Group] - - - - # Toutes les Requests auxquelles un groupe doit répondre - requestsToGroup(gid:ID!): [Request] - userJoinGroupRequestsToGroup(gid:ID!): UserJoinGroup - groupCoauthorEventRequestsToGroup(gid:ID!): GroupCoauthorEvent - - - - # TOL - searchTOL( - givenName: String, - lastName: String, - nickname: String, - nationality: String, - school: String, - promotion: String, - groups: String, - studies: String, - sport: String, - phone: String, - mail: String, - address: String, - ip: String - ): [User!] - -} - -type Mutation { - """ - Par rapport à un groupe donné, un user peut avoir différents niveaux de droits : - - none : sait que le groupe existe, mais aucun autre droit (typiquement, une connection où l'utilisateur ne s'est pas authentifié) - - viewer : le user a aussi accès à l'activité publique du groupe : frontpage, Q&A, liste des membres, speakers et admins - - member : le user a aussi acces à l'activité interne du groupe : les PrivatePost, ainsi que les Message dont le groupe est auteur ou destinataire - - speaker : le user peut aussi parler au nom du groupe. Il a le droit de publier des annonces et d'organiser des évènements - - admin : le user a tous les droits sur le groupe - - Un des rôles du *graphe organique des groupes* est de définir le niveau de droit des users pour chaque groupe. - D'abord, petit détail de terminologie : les cinq niveaux de droits sont inclus les uns dans les autres (un speaker est aussi un viewer, par ex.) - - Les conditions pour qu'un user soit membre, speaker ou admin sont claires, puisque cette information est stockée directement en BDD. - - Un user non-membre est viewer du groupe G : - - s'il est membre d'un groupe immédiatement parent de G (one-edge-down visibility), ou - - s'il est membre d'un groupe faisant partie du champ "visibilityEdge" de G - - s'il est membre d'un métagroupe dont G est membre (implicit visibility-edges). - - Dans tous les autres cas, le user a le niveau de droits "none". - - L'autre rôle du *graphe organique des groupes* est de permettre l'administration "en cascade" des groupes enfants. - Si un groupe est le parent d'un autre, alors les admins du groupe parent peuvent se déclarer admins du groupe enfant. Exemples : - - BR est parent de Chocapix. L'admin de Chocapix est parti en vacances. L'admin de BR peut se déclarer admin de Chocapix et faire ce qu'il a à faire, sans attendre le retour du respo Chocapix. - - Cotisants-Kès est parent de Troll'X. Troll'X fait n'importe quoi (en floodant de Messages par ex). Les admins de Cotisants-Kès (les kessiers) peuvent se déclarer admin de Troll'X et stopper les dégâts. - - Remarque sur speaker : - Il s'agit d'un nouveau niveau de droit par rapport à Frankiz 3.0 ; il n'est pas implémenté dans le LDAP frankiz. - Par conséquent, il est probable que, au début du moins, on impose que speaker=admin, pour pouvoir continuer à utiliser ce LDAP. - - Les mutations ci-dessous sont divisées en quatre, selon le niveau de droit requis pour pouvoir les appeler. - Les Mutations concernant les *Requests* suivent à peu près toutes le schéma suivant : - - <typeAuteur>Request<NatureDeLaRequest>(auteur, destinataire, comment): Request - - accept<NatureDeLaRequest>Request(request: ID!, comment: String): Boolean - - refuse<NatureDeLaRequest>Request(request: ID!, comment: String): Boolean - - le paramètre est le rid de la Request à accepter ou refuser - - seul les admins du groupe destinataire peuvent accepter ou refuser une Request - Les Mutations concernant les *Messages* suivent à peu près toutes le schéma suivant : - - create<TypeDeMessage>(auteur, destinataire, title, content, etc.): <TypeDeMessage> - - edit<TypeDeMessage>(<typeDeMessage>ToEdit: ID!): <TypeDeMessage> - - remove<TypeDeMessage>(<typeDeMessage>ToRemove: ID!): Boolean - - = l'auteur supprime le message - - pour les Messages où l'auteur est un utilisateur, seul l'auteur a le droit de remove son Message - - pour les Messages où l'auteur est un groupe, n'importe quel admin du groupe (ou speaker selon le cas) a le droit de remove le Message - - censor<TypeDeMessage>(<typeDeMessage>ToCensor: ID!): Boolean - - = le groupe destinataire supprime le message - - n'importe quel admin du groupe a le droit de censurer un Message qui lui est adressé - - (le destinataire est un Group pour tous les Messages) - """ - - # Groups-independent mutations - editProfile( - nickname: String, - mail: String, - phone: String - ): User - - # Viewer mutations - likeGroup(groupId: ID!): Boolean # devenir sympathisant - userParticipate( - forEvent: ID! - ): Boolean - userUnparticipate( - forEvent: ID! - ): Boolean - - userRequestJoinGroup(toGroup: ID!, comment: String): Request - - createQuestion( - toGroup: ID!, - title: String, - content: String, - ): Question - editQuestion( - questionToEdit: ID!, - title: String, - content: String, - ): Question - removeQuestion( - questionToRemove: ID! - ): Question - - # Member mutations - userLeaveGroup(groupId: ID!): Boolean - - createPrivatePost( - toGroup: ID!, - title: String, - content: String - ): PrivatePost - editPrivatePost( - privatePostToEdit: ID!, - title: String, - content: String - ): PrivatePost - removePrivatePost( - privatePostToRemove: ID! - ): PrivatePost - - # Speaker mutations - writePostsSummary(forGroup: ID!, content: String): Boolean - - groupRequestCoauthorEvent( - fromGroup: ID!, - toGroup: ID!, - forEvent: ID!, - comment: String - ): Request - - createAnnouncement( - fromGroup: ID!, - toGroups: [ID!], - title: String, - content: String, - forEvent: ID - ): Announcement - editAnnouncement( - announcementToEdit: ID!, - title: String, - content: String, - forEvent: ID - ): Announcement - removeAnnouncement( - announcementToRemove: ID! - ): Boolean - - createEvent( - fromGroup: ID!, - toGroups: [ID!], - title: String, - content: String, - location: String, - startTime: String, - endTime: String, - forAnnouncement: ID - ): Event - editEvent( - eventToEdit: ID!, - title: String, - content:String, - location: String, - startTime: String, - endTime: String, - forAnnouncement: ID - ): Event - removeEvent( - eventToRemove: ID! - ): Boolean - - createAnswer( - forQuestion: ID!, - title: String, - content: String - ) - editAnswer( - answerToEdit: ID!, - title: String, - content: String - ) - removeAnswer( - answerToRemove: ID! - ): Boolean - - # Admin mutations - createSubgroup( - fromGroup: ID!, - subGid: ID, - subName: String!, - subDescription: String, - subMail: String, - subWebsite: String, - subSchool: String - ): Group - becomeAdmin(forGroup: ID!): Boolean # requiert que l'utilisateur soit admin du groupe parent de forGroup - - makeAdmin(forGroup: ID!, userId: ID!): User - unmakeAdmin(forGroup: ID!, userId: ID!): User - makeSpeaker(forGroup: ID!, userId: ID!): User - unmakeSpeaker(forGroup: ID!, userId: ID!): User - - editGroup( - forGroup: ID!, - name: String, - description: String, - mail: String, - website: String, - school: String - ): Group - - groupRequestJoinMetagroup(fromGroup: ID!, toMetagroup: ID!, comment: String): Request - - acceptUserJoinRequest(request: ID!, comment: String): Boolean - acceptGroupJoinRequest(request: ID!, comment: String): Boolean - refuseUserJoinRequest(request: ID!, comment: String): Boolean - refuseGroupJoinRequest(request: ID!, comment: String): Boolean - - censorQuestion(questionToCensor: ID!): Boolean - censorPrivatePost(privatePostToCensor: ID!): Boolean - censorAnnouncement(announcementToCensor: ID!): Boolean - censorEvent(eventToCensor: ID!): Boolean - censorAnswer(answerToCensor: ID!): Boolean - -} diff --git a/src/graphql/typeDefs/objects.graphql b/src/graphql/typeDefs/objects.graphql index 47ba8ea4b15433890952cfcfeb72fcaa39383d89..1150f11df008dcfecd5652d619ff870ffde7c40d 100644 --- a/src/graphql/typeDefs/objects.graphql +++ b/src/graphql/typeDefs/objects.graphql @@ -1,189 +1,281 @@ -# hawkspar->all ; doc ? - -# Utilisateurs +""" +@file Définit les types du schéma GraphQL. Il y en a quatre catégories : User, Group, Message, Request. + +Conseils généraux pour toute tentative d'amélioration du schéma : + - mettre un -s aux champs array [SomeType]. + - ... et même aux champs union *pouvant être* des array. + - ne pas rajouter un champ si on peut prévoir qu'il ne sera jamais utilisé. + - ne pas rajouter un champ si cela complexifie trop la gestion des autorisations. + - (par exemple pour User, tous les champs doivent pouvoir être vus par toute personne ayant accès au TOL.) + - dans ce cas créer un nouveau query, tout simplement. + - respecter la convention : + - uid = user id + - gid = group id + - mid = message id + - rid = request id + - choisir des noms clairs, précis, concis. + - commenter lorsque c'est pertinent, également en étant clair, précis, concis. + +@author akka vodol, kadabra, ofacklam + + + +Un utilisateur +""" type User { - # Prénom de l'utilisateur - givenName: String! - # Nom de famille - lastName: String! - # Surnom - nickname: String - nationality: String uid: ID! + + # Pour l'identifier + givenName: String! # Prénom + lastName: String! # Nom de famille + nickname: String # Surnom + nationality: String birthdate: String! + promotion: String + #photo: TODO (difficile car nécessite de gérer des données non-texte) + + # Pour le contacter mail: String phone: String - # Groupes dont l'utilisateur est membre. - groups: [SimpleGroup] - # Groupes que l'utilisateur aime. - likes: [Group] - # Adresse(s) de l'utilisateur. - address: [String] - # Promotion - promotion: String + address: String # Adresse de l'utilisateur (numero de casert par exemple) + + # Ses interactions avec des groupes + memberOf: [Group] # Membre strict + inheritedMemberOf: [Group] # Membre hérité + speakerOf: [Group] # Speaker (strict) + adminOf: [Group] # Admin strict + inheritedAdminOf: [Group] # Admin hérité + likes: [Group] # Groupes dont l'utilisateur est sympathisant (purement indicatif, pas d'influence sur les niveaux de droit) + dislikes: [Group] # Groupes que l'utilisateur veut cacher + + # Les Message dont il est l'auteur + questionsFromUser: [Question] # Les seuls Messages publics où `authors` est de type User } -# Groupes associatifs - """ -L'interface Group représente les deux types de groupes implémentés dans Sigma : les groupes -simples, dont les membres sont des utilisateurs, et les métagroupes, dont les membres sont -des groupes simples (tel que Federez, dont les membres incluent le BR et DaTA). +L'interface Group représente les deux types de groupes implémentés dans Sigma : +- les groupes simples, dont les membres sont des utilisateurs, et +- les métagroupes, dont les membres sont des groupes simples (tel que Federez, dont les membres incluent le BR et DaTA). """ interface Group { - uid: ID - name: String - # Site Web. - website: String - description: String + gid: ID! + createdAt: String! # Date et heure de création du groupe. + updatedAt: String! # Date et heure de mise a jour des informations du groupe. + + # Pour l'identifier + name: String! + description: String # Une description tres rapide du groupe - # Jour et heure de création du groupe. - createdAt: String! - # Dernière mise à jour du groupe. - updatedAt: String! + # Pour le contacter + mail: String + website: String - # member requests + # Administrateurs, à contacter directement en cas de problème + admins: [User] - # Les posts prives dans ce groupe - privatePosts: [PrivatePost] - # Les questions addressees à ce groupe + # Activité publique du groupe + frontPage: String # page d'accueil du groupe, en markdown questions: [Question] - # Les reponses donnees par ce groupe - answers: [Answer] + answers: [Answer] # permet d'obtenir les questions qui ont eu une réponse + + # Activité interne + # Viewer pour voir les events/annonces adressés au groupe + announcementsToGroup: [Announcement] # annonces adressées au groupe + eventsToGroup: [Event] + + # Member pour voir les events/annonces créés par le groupe + announcementsFromGroup: [Announcement] # annonces écrites par le groupe + eventsFromGroup: [Event] + + # Member pour voir les posts privés + privatePosts: [PrivatePost] + postsSummary: String # récapitulatif de l'activité interne du groupe, en markdown + # Toutes les Requests auxquelles un groupe doit répondre (admin) + requestsToGroup: [Request] + userJoinGroupRequestsToGroup: [UserJoinGroup] + groupJoinMetagroupRequestsToGroup: [GroupJoinMetagroup] + groupCoauthorEventRequestsToGroup: [GroupCoauthorEvent] + # Graphe organique + visibilityEdges: [Group] # se rendre visible par des groupes en plus du graphe organique } # Le groupe de base, dont les membres sont des utilisateurs : binets, Kès... type SimpleGroup implements Group { - uid: ID - name: String - website: String + gid: ID! createdAt: String! updatedAt: String! + name: String! + description: String + mail: String + website: String - # Admin, membres, sympathisants du groupe - admins: [User] + # Admins, speakers (respos com), membres, sympathisants du groupe (stricts pour ce groupe) members: [User] + speakers: [User] + admins: [User] likers: [User] - description: String - # École d'origine du groupe - school: String - # Groupe parent - parent: Group - - privatePosts: [PrivatePost] + # Activité publique du groupe + frontPage: String # page d'accueil du groupe, en markdown questions: [Question] - answers: [Answer] + answers: [Answer] # permet d'obtenir les questions qui ont eu une réponse + + # Activité interne + # Viewer pour voir les events/annonces adressés au groupe + announcementsToGroup: [Announcement] # annonces adressées au groupe + eventsToGroup: [Event] + + # Member pour voir les events/annonces créés par le groupe + announcementsFromGroup: [Announcement] # annonces écrites par le groupe + eventsFromGroup: [Event] + + # Member pour voir les posts privés + privatePosts: [PrivatePost] + postsSummary: String # récapitulatif de l'activité interne du groupe, en markdown + + # Toutes les Requests auxquelles un groupe doit répondre (admin) + requestsToGroup: [Request] + userJoinGroupRequestsToGroup: [UserJoinGroup] + groupJoinMetagroupRequestsToGroup: [GroupJoinMetagroup] + groupCoauthorEventRequestsToGroup: [GroupCoauthorEvent] + + # Graphe organique des groupes + parents: [SimpleGroup] # Groupes parents + children: [SimpleGroup] # Groupes enfants + memberOfMeta: [MetaGroup] + visibilityEdges: [Group] # se rendre visible par des groupes en plus du graphe organique + + school: String # École d'origine du groupe (pour information) } # Un groupe dont les membre sont d'autres groupes type MetaGroup implements Group { - uid: ID - name: String - website: String + gid: ID! createdAt: String! updatedAt: String! + name: String! description: String + mail: String + website: String - # Les groupes constitutifs du méta-groupe. - members: [Group]! + # Admins et Groupes membres + admins: [User] + members: [SimpleGroup] # Par choix de paradigme, on veut éviter d'avoir des méta-méta-groupes. - privatePosts: [PrivatePost] + # Activité publique du groupe + frontPage: String # page d'accueil du groupe, en markdown questions: [Question] - answers: [Answer] -} + answers: [Answer] # permet d'obtenir les questions qui ont eu une réponse + + # Activité interne + # Viewer pour voir les events/annonces adressés au groupe + announcementsToGroup: [Announcement] # annonces adressées au groupe + eventsToGroup: [Event] + + # Member pour voir les events/annonces créés par le groupe + announcementsFromGroup: [Announcement] # annonces écrites par le groupe + eventsFromGroup: [Event] + + # Member pour voir les posts privés + privatePosts: [PrivatePost] + postsSummary: String # récapitulatif de l'activité interne du groupe, en markdown + # Toutes les Requests auxquelles un groupe doit répondre (admin) + requestsToGroup: [Request] + userJoinGroupRequestsToGroup: [UserJoinGroup] + groupJoinMetagroupRequestsToGroup: [GroupJoinMetagroup] + groupCoauthorEventRequestsToGroup: [GroupCoauthorEvent] -# Tout type de message adressé à un ou plusieurs groupes. + # Graphe organique + visibilityEdges: [Group] # se rendre visible par des groupes en plus du graphe organique +} -# Auteur possible d'un Message # union AuthorUnion = Group | [Group] | User -# union RecipientUnion = Group | [Group] - -# Les unions sont assez faibles dans GraphQL, -# elles n'acceptent pas les listes ni les interfaces - -# L'interface Message représente toute information que veut communiquer un groupe ou un user. -# Par choix de paradigme, tout Message est adressé à un (ou des) groupe(s). -# Les types implémentés sont divisés en deux : -# - les Message émanant d'un groupe : Announcement et Event, ainsi que Answer -# - les Message émanant d'un user : PrivatePost, ainsi que Question +# il n'est pas possible de faire des unions avec des listes d'objets... +""" +L'interface Message représente toute information que veut communiquer un groupe ou un user. +Par choix de paradigme, tout Message est adressé à un groupe (et un seul). +Les types implémentés sont divisés en deux : +- les Message émanant d'un groupe : Announcement et Event, ainsi que Answer +- les Message émanant d'un user : PrivatePost, ainsi que Question +""" interface Message { - id: ID! - # Titre du message - title: String! - content: String + mid: ID! createdAt: String! updatedAt: String! + title: String! + content: String! + + #authors: AuthorUnion + #recipient: Group } -# Annonce publique effectuée par un ou plusieurs groupes. +# Annonce effectuée par un ou plusieurs groupes. type Announcement implements Message { - id: ID! - title: String! + mid: ID! createdAt: String! updatedAt: String! + title: String! content: String! - importance: Int - views: Int - forEvent: Event authors: [Group] recipients: [Group] + + importance: Int # importance de cette Announcement, sur une échelle de [??] à [??] (TODO) + views: Int # nombre de vues + + # Si cette Announcement annonce un événement, référence le Event. Sinon null. + forEvent: Event } # Événements organisés par un ou plusieurs groupes. type Event implements Message { - id: ID! - # Intitulé de l'événement - title: String! - # Lieu de l'événement - location: String + mid: ID! createdAt: String! updatedAt: String! + title: String! + content: String! + + authors: [Group] # Organisateurs de l'événement + recipients: [Group] + + location: String! startTime: String! endTime: String! - # Organisateurs - # Personnes qui participent à l'événement. - participatingGroups: [Group] + + # Personnes et groupes qui participent à l'événement. + participatingGroups: [Group] # contributeurs mais pas organisateurs (par ex, Fanfare à une proj' JTX) participatingUsers: [User] - content: String - asAnnouncement: Announcement - authors: [Group] - recipients: [Group] + # Si cet Event a été annoncé par un Announcement, le référence. Sinon null. + forAnnouncement: Announcement } # Post interne d'un membre sur la page interne de son groupe type PrivatePost implements Message { - id: ID! + mid: ID! createdAt: String! updatedAt: String! title: String! content: String! - authors: User - recipients: Group + author: User + recipient: Group } # Question posée par un user à un groupe type Question implements Message { - id: ID! + mid: ID! createdAt: String! updatedAt: String! title: String! content: String! - authors: User - recipients: Group - - # Une annonce éventuellement concernée par cette question. - # Null si la question ne concerne pas une annonce particulière - - forAnnouncement: Announcement + author: User + recipient: Group # Référence la réponse donnée par le groupe à cette Question. Si pas encore répondu, null. forAnswer: Answer @@ -191,51 +283,62 @@ type Question implements Message { # Réponse à une Question type Answer implements Message { - id: ID! + mid: ID! createdAt: String! updatedAt: String! title: String! content: String! - authors: Group - recipients: Group + author: Group # La question à laquelle cette Answer répond. Non-nullable bien sûr forQuestion: Question! } +""" +Différents types de requêtes peuvent être adressées à un groupe. Elles sont stockées en BDD en attente d'être traitées. +Par exemple (le plus évident) demander à devenir membre, mais il existe aussi d'autres cas de figure. +On peut les voir comme des Mutations potentielles : en cas de validation de la requête, des entrées de la BDD seront modifiées. +Seuls les admins d'un Group (qu'il soit Simple ou Meta) ont le droit de valider ou refuser une requête. +Les différents types implémentant Request représentent des types de requête : +- UserJoinGroup: un User demande à devenir membre d'un SimpleGroup +- GroupCoauthorEvent: un groupe demande à devenir (co-)organisateur d'un événement *déjà existant* +""" +# Emetteur possible d'une Request +# union RequesterUnion = SimpleGroup | MetaGroup | User + interface Request { - # ID de la demande - id: ID! - # message accompagnant la demande - message: String + rid: ID! + comment: String # court message accompagnant la demande + + #from: RequesterUnion! # Émet la demande + to: Group! # Reçoit la demande } -# Demande d'un utilisateur désirant rejoindre le groupe. +# Un utilisateur demande à devenir membre d'un groupe (simple bien sûr). type UserJoinGroup implements Request{ - id: ID! - message: String - # Émetteur de la demande - user: User + rid: ID! + comment: String + + from: User! + to: SimpleGroup! } +# Un groupe simple demande à devenir membre d'un méta-groupe +type GroupJoinMetagroup implements Request{ + rid: ID! + comment: String -# Demande d'un groupe voulant rejoindre un événement -type GroupJoinEvent implements Request{ - id: ID! - message: String - # Événement concerné - event: Event - # Groupe voulant rejoindre l'événement - groupWantingToJoin: Group + from: SimpleGroup! + to: MetaGroup! } -# Demande au récipiendaire de rejoindre l'organisation d'un événement. -type YourGroupHostEvent implements Request{ - id: ID! - message: String - # Événement concerné - event: Event - # Groupe ayant publié l'évènement et lancé l'invitation - sender: Group +# Un Group demande à devenir (co-)author d'un Event *déjà existant*. +type GroupCoauthorEvent implements Request{ + rid: ID! + comment: String + + from: Group! # Groupe souhaitant l'évènement et lançant l'invitation + to: Group! # un des Groupes organisant l'événement (erreur sinon) + forEvent: Event! } diff --git a/src/graphql/typeDefs/objects_wish_list.graphql b/src/graphql/typeDefs/objects_wish_list.graphql deleted file mode 100644 index cd6fd9588c639011d92b7e30136a5f81c8031667..0000000000000000000000000000000000000000 --- a/src/graphql/typeDefs/objects_wish_list.graphql +++ /dev/null @@ -1,279 +0,0 @@ -""" -@file Définit les types du schéma GraphQL. Il y en a quatre catégories : User, Group, Message, Request. - Ce fichier est la wish-list de kadabra (qui veut avoir un schéma clair pour travailler sereinement sur le front). - -Conseils généraux pour toute tentative d'amélioration du schéma : - - mettre un -s aux champs array [SomeType]. - - ... et même aux champs union *pouvant être* des array. - - ne pas rajouter un champ si on peut prévoir qu'il ne sera jamais utilisé. - - ne pas rajouter un champ si cela complexifie trop la gestion des autorisations. - - (par exemple pour User, tous les champs doivent pouvoir être vus par toute personne ayant accès au TOL.) - - dans ce cas créer un nouveau query, tout simplement. - - respecter la convention : - - uid = user id - - gid = group id - - mid = message id - - rid = request id - - choisir des noms clairs, précis, concis. - - commenter lorsque c'est pertinent, également en étant clair, précis, concis. - -@author akka vodol, kadabra -""" - -""" -Un utilisateur -""" -type User { - uid: ID! - - # Pour l'identifier - givenName: String! # Prénom - lastName: String! # Nom de famille - nickname: String # Surnom - nationality: String - birthdate: String! - promotion: String - #photo: TODO (difficile car nécessite de gérer des données non-texte) - - # Pour le contacter - mail: String - phone: String - address: [String] # Adresse(s) de l'utilisateur (numero de casert par exemple) - - # Ses interactions avec des groupes - memberOf: [SimpleGroup] # Groupes dont l'utilisateur est membre - speakerOf: [SimpleGroup] - adminOf: [Group] - likes: [Group] # Groupes dont l'utilisateur est sympathisant (purement indicatif, pas d'influence sur les niveaux de droit) - - # Les Message dont il est l'auteur - questionsFromUser: [Question] # Les seuls Messages publics où `authors` est de type User -} - -""" -L'interface Group représente les deux types de groupes implémentés dans Sigma : -- les groupes simples, dont les membres sont des utilisateurs, et -- les métagroupes, dont les membres sont des groupes simples (tel que Federez, dont les membres incluent le BR et DaTA). -""" -interface Group { - gid: ID! - createdAt: String! # Date et heure de création du groupe. - updatedAt: String! # Date et heure de mise a jour des informations du groupe. - - # Pour l'identifier - name: String! - description: String # Une description tres rapide du groupe - - # Pour le contacter - mail: String - website: String - - # Administrateurs, à contacter directement en cas de problème - admins: [User] -} - -# Le groupe de base, dont les membres sont des utilisateurs : binets, Kès... -type SimpleGroup implements Group { - gid: ID! - createdAt: String! - updatedAt: String! - name: String! - description: String - mail: String - website: String - - # Admins, speakers (respos com), membres, sympathisants du groupe - members: [User] - speakers: [User] - admins: [User] - likers: [User] - - # Graphe organique des groupes - parent: SimpleGroup # Groupe parent - children: [SimpleGroup] # Groupes enfants - memberOfMeta: [MetaGroup] - visibilityEdges: [Group] # se rendre visible par des groupes en plus du graphe organique - - school: String # École d'origine du groupe (pour information) - - # Activité publique du groupe - frontPage: String # page d'accueil du groupe, en markdown - questions: [Question] - answers: [Answer] # permet d'obtenir les questions qui ont eu une réponse - - # Activité interne - announcementsFromGroup: [Announcement] # annonces écrites par le groupe - announcementsToGroup: [Announcement] # annonces adressées au groupe - eventsFromGroup: [Event] - eventsToGroup: [Event] - posts: [Post] - postsSummary: String # récapitulatif de l'activité interne du groupe, en markdown -} - -# Un groupe dont les membre sont d'autres groupes -type MetaGroup implements Group { - gid: ID! - createdAt: String! - updatedAt: String! - name: String! - description: String - mail: String - website: String - - # Admins et Groupes membres - admins: [User] - members: [SimpleGroup] # Par choix de paradigme, on veut éviter d'avoir des méta-méta-groupes. - - visibilityEdges: [Group] # se rendre visible par des groupes en plus du graphe organique -} - -union AuthorUnion = Group | [Group] | User - -""" -L'interface Message représente toute information que veut communiquer un groupe ou un user. -Par choix de paradigme, tout Message est adressé à un groupe (et un seul). -Les types implémentés sont divisés en deux : -- les Message émanant d'un groupe : Announcement et Event, ainsi que Answer -- les Message émanant d'un user : PrivatePost, ainsi que Question -""" -interface Message { - mid: ID! - createdAt: String! - updatedAt: String! - title: String! - content: String! - - authors: AuthorUnion - recipient: Group -} - -# Annonce effectuée par un ou plusieurs groupes. -type Announcement implements Message { - mid: ID! - createdAt: String! - updatedAt: String! - title: String! - content: String! - - authors: [Group] - recipient: Group - - importance: Int # importance de cette Announcement, sur une échelle de [??] à [??] (TODO) - views: Int # nombre de vues - - # Si cette Announcement annonce un événement, référence le Event. Sinon null. - forEvent: Event -} - -# Événements organisés par un ou plusieurs groupes. -type Event implements Message { - mid: ID! - createdAt: String! - updatedAt: String! - title: String! - content: String! - - authors: [Group] # Organisateurs de l'événement - recipient: Group - - location: String! - startTime: String! - endTime: String! - - # Personnes et groupes qui participent à l'événement. - participatingGroups: [Group] # contributeurs mais pas organisateurs (par ex, Fanfare à une proj' JTX) - participatingUsers: [User] - - # Si cet Event a été annoncé par un Announcement, le référence. Sinon null. - forAnnouncement: Announcement -} - -# Post interne d'un membre sur la page interne de son groupe -type PrivatePost implements Message { - mid: ID! - createdAt: String! - updatedAt: String! - title: String! - content: String! - - authors: User - recipient: Group -} - -# Question posée par un user à un groupe -type Question implements Message { - mid: ID! - createdAt: String! - updatedAt: String! - title: String! - content: String! - - authors: User - recipient: Group - - # Référence la réponse donnée par le groupe à cette Question. Si pas encore répondu, null. - forAnswer: Answer -} - -# Réponse à une Question -type Answer implements Message { - mid: ID! - createdAt: String! - updatedAt: String! - title: String! - content: String! - - authors: Group - recipient: Group # doit être le même que authors - - # La question à laquelle cette Answer répond. Non-nullable bien sûr - forQuestion: Question! -} - -""" -Différents types de requêtes peuvent être adressées à un groupe. Elles sont stockées en BDD en attente d'être traitées. -Par exemple (le plus évident) demander à devenir membre, mais il existe aussi d'autres cas de figure. -On peut les voir comme des Mutations potentielles : en cas de validation de la requête, des entrées de la BDD seront modifiées. -Seuls les admins d'un Group (qu'il soit Simple ou Meta) ont le droit de valider ou refuser une requête. -Les différents types implémentant Request représentent des types de requête : -- UserJoinGroup: un User demande à devenir membre d'un SimpleGroup -- GroupCoauthorEvent: un groupe demande à devenir (co-)organisateur d'un événement *déjà existant* -""" -# Emetteur possible d'une Request -union RequesterUnion = Group | User - -interface Request { - rid: ID! - comment: String # court message accompagnant la demande - - from: RequesterUnion! # Émet la demande - to: Group! # Reçoit la demande -} - -# Un utilisateur demande à devenir membre d'un groupe (simple bien sûr). -type UserJoinGroup implements Request{ - rid: ID! - comment: String - - from: User! - to: SimpleGroup! -} - -# Un groupe simple demande à devenir membre d'un méta-groupe -type GroupJoinMetagroup implements Request{ - rid: ID! - comment: String - - from: SimpleGroup! - to: MetaGroup! -} - -# Un Group demande à devenir (co-)author d'un Event *déjà existant*. -type GroupCoauthorEvent implements Request{ - rid: ID! - comment: String - - from: Group! # Groupe souhaitant l'évènement et lançant l'invitation - to: Group! # un des Groupes organisant l'événement (erreur sinon) - forEvent: Event! -} diff --git a/src/graphql/typeDefs/queries.d.ts b/src/graphql/typeDefs/queries.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..31d469632dbc75cd3393622e98b3ac71fb3889b0 --- /dev/null +++ b/src/graphql/typeDefs/queries.d.ts @@ -0,0 +1,61 @@ +/** + * @file Ce fichier définit quelques types utiles pour le serveur Apollo. + * @author ofacklam +*/ + +import { AuthorizationModel } from "../models/authorization"; +import { UserModel } from "../models/userModel"; +import { GroupModel } from "../models/groupModel"; +import { MessageModel } from "../models/messageModel"; +import { RequestModel } from "../models/requestModel"; + +interface Context { + request, + user: { uid: string }, + models: { + auth: AuthorizationModel, + user: UserModel, + group: GroupModel, + message: MessageModel, + request: RequestModel + } +} + +interface searchTOLArgs { + givenName: string, + lastName: string, + nickname: string, + nationality: string, + school: string, + groups: string[], + studies: string, + phone: string, + mail: string, + address: string +} + +interface editProfileArgs { + nickname: string, + mail: string, + phone: string +} + +interface createSubgroupArgs { + fromGroup: string, + subGid: string, + subName: string, + subDescription: string, + subMail: string, + subWebsite: string, + subSchool: string +} + +interface editGroupArgs { + forGroup: string, + name: string, + description: string, + mail: string, + website: string, + frontPage: string, + school: string +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index f55c10752cdc50e6dc083b745c74d68f7b3eeb6b..e0aa4dd5fa60f18298a95c0c42482efaf2c62b89 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,7 +7,7 @@ dotenv.config(); import app from './app'; import colors from 'colors'; -const port = process.env.PORT || 3000; +const port = process.env.PORT; const hostnameConfigMap = { 'development': 'localhost', 'staging': '0.0.0.0', diff --git a/src/ldap/admins.ts b/src/ldap/admins.ts deleted file mode 100644 index 4b08db197a27ca14b23cc5ee90993d955c593960..0000000000000000000000000000000000000000 --- a/src/ldap/admins.ts +++ /dev/null @@ -1,410 +0,0 @@ -/** - * @file Ce fichier regroupe les différentes classes avec différents admins. Ces classes sont dédiées à être exportées directement pour être utilisées par le solver. - * Le découpage par fichier est arbitraire mais permet de regrouper certaines classes proches. - * @author hawkspar - */ - -import {ldapConfig} from './config'; -import {LDAP} from './basics'; -import {Tests} from './utilities'; -import {Open, User, userData, groupData} from './users'; - -export class Admin extends User { - /** - * @class Cette classe est la classe de l'administrateur d'un groupe qui lui permet de rajouter des membres, en supprimer, idem pour des admins, - * ou éditer, voir supprimer le groupe. - * @summary Ce constructeur appelle simplement le constructeur de sa classe mère. - */ - constructor() { super(); } - - //------------------------------------------------------------------------------------------------------------------------ - // Fonctions de relation TBT - //------------------------------------------------------------------------------------------------------------------------ - - /** - * @summary Fonction qui permet de rajouter un membre déjà créé à un groupe. - * @desc Cette fonction fait essentiellement appel à {@link LDAP.modifier} et {@link listerGroupes}. Elle n'autorise pas les doublons et opère dans les deux dns users - * et groups. - * @arg {string} uid - Identifiant du futur membre - * @arg {string} gid - Identifiant du groupe - * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon - * @async - * @static - */ - static async addGroupMember(uid: string, gid: string) : Promise<boolean> { - try { - // Vérifie que l'utilisateur est pas déjà membre pour groupes - let lm = await Open.getMembers(gid); - if (!lm.includes(uid)) { - let vals = {}; - vals[ldapConfig.groups.member] = uid; - // Erreur si pb lors de la modification - if (!await LDAP.change(ldapConfig.key_id+gid+ldapConfig.dn_groups, "add", vals)) { - throw "Erreur lors de la modification dans l'arbre des groupes pour ajouter un membre."; - } - } - } - catch(err) { - throw "Erreur lors de la recherche de la liste des membres pour ajouter un membre."; - } - try { - // Vérifie que l'utilisateur est pas déjà membre pour users - let lg = await Open.getGroups(uid); - if (!lg.includes(gid)) { - let vals2 = {}; - vals2[ldapConfig.users.groups] = gid; - // Erreur si pb lors de la modification - if (!await LDAP.change(ldapConfig.key_id+uid+ldapConfig.dn_users, "add", vals2)) { - throw "Erreur lors de la modification dans l'arbre des utilisateurs pour ajouter un membre."; - } - } - return true; - } - catch(err) { - throw "Erreur lors de la recherche de la liste des membres pour ajouter un membre."; - } - } - - /** - * @summary Fonction qui permet de supprimer un membre existant d'un groupe. - * @desc Cette fonction fait essentiellement appel à {@link LDAP.search}, {@link LDAP.change}, {@link Open.getGroups} et {@link Open.getMembers}. - * @arg {string} uid - Identifiant de l'ex-membre - * @arg {string} gid - Identifiant du groupe - * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon - * @async - * @static - */ - static async delGroupMember(uid: string, gid: string): Promise<boolean> { - try { - // Vérifie que l'utilisateur est pas déjà viré pour groupes - let lm = await Open.getMembers(gid); - if (lm.includes(uid)) { - // Supprime tous les utilisateurs - if (!await LDAP.change(ldapConfig.key_id+gid+ldapConfig.dn_groups, "del", ldapConfig.group.member)) { - throw "Erreur lors de la suppression de tous les membres du groupe."; - } - // Les rajoute un par un, sauf pour le supprimé - lm.forEach(id => { - if (id!=uid) { - this.addGroupMember(id, gid).then(res => { - if (!res) { throw "Erreur lors du ré-ajout des autres membres"; } - }); - } - }); - } - } - catch(err) { - throw "Erreur pour obtenir une liste de membres d'un groupe pour supprimer un membre du groupe."; - } - try { - let lg = await Open.getGroups(uid); - // Vérifie que l'utilisateur est pas déjà viré pour users - if (lg.includes(gid)) { - // Supprime tous les groupes - if (!await LDAP.change(ldapConfig.key_id+uid+ldapConfig.dn_users, "del", ldapConfig.member.groups)) { - throw "Erreur lors de la suppression de tous les groupes du membre."; - } - // Les rajoute un par un, sauf pour le supprimé - lg.forEach(id => { - if (id!=gid) { - this.addGroupMember(uid, id).then(res => { - if (!res) { throw "Erreur lors du ré-ajout des autres groupes"; } - }); - } - }); - } - return true; - } - catch(err) { - throw "Erreur pour obtenir une liste de groupes d'un membres pour le supprimer du groupe."; - } - } - - /** - * @summary Fonction qui permet de promouvoir membre au stade d'administrateur d'un groupe. - * @desc Cette fonction fait essentiellement appel à {@link Admin.addGroupMember} {@link LDAP.change} et {@link Open.getAdmins}. Elle n'autorise pas - * les doublons et opère dans les deux dns users et groups. - * @arg {string} uid - Identifiant du futur membre - * @arg {string} gid - Identifiant du groupe - * @return {boolean} `true` si la modification s'est bien déroulée, false sinon - * @async - * @static - */ - static async addGroupAdmin(uid: string, gid: string): Promise<boolean> { - // Ajoute le membre au groupe avant d'en faire un admin - if (!await Admin.addGroupMember(uid,gid)) { throw "Erreur lors de l'ajout du futur admin en tant que membre."; } - try { - let la = await Open.getAdmins(gid); - if (!la.includes(uid)) { - // Finalement modification, uniquement dans groups - let vals = {}; - vals[ldapConfig.groups.admin] = uid; - if (!await LDAP.change(ldapConfig.key_id+gid+ldapConfig.dn_groups, "add", vals)) { - throw "Erreur lors de l'ajout de l'admin dans l'arbre des groupes."; - } - } - return true; - } - catch(err) { - throw "Erreur lors de l'obtention de la liste des administrateurs d'un groupe."; - } - } - - /** - * @summary Fonction qui permet de rétrograder un membre du stade d'administrateur d'un groupe au stade d'utilisateur. - * @desc Cette fonction fait essentiellement appel à {@link LDAP.change}. - * @arg {string} uid - Identifiant du futur membre - * @arg {string} gid - Identifiant du groupe - * @return {boolean} `true` si la modification s'est bien déroulée, false sinon - * @async - * @static - */ - static async delGroupAdmin(uid: string, gid: string): Promise<boolean> { - // Peut paraître absurde mais permet de s'assurer que le membre est bien présent et que ses champs sont comme il faut - if (!(await Admin.delGroupMember(uid, gid)&&Admin.addGroupMember(uid,gid))) { throw "Erreur dans l'éjection/réadmission du futur admin."; } - try { - // Vérifie que l'utilisateur est bien admin (comme dans delGroupMember) - let la = await Open.getAdmins(gid); - if (la.includes(uid)) { - // Supprime tous les administrateurs - if (!await LDAP.change(ldapConfig.key_id+gid+ldapConfig.dn_groups, "del", ldapConfig.group.admin)) { throw "Erreur dans la suppression de tous les admins pour en supprimer un."; } - // Les rajoute un par un, sauf pour le supprimé - la.forEach(id => { - if (id!=uid) { Admin.addGroupAdmin(id, gid).then(res => { - if (!res) { throw "Erreur dans le réajout d'un des autres admins."; } - }); } - }); - } - return true; - } - catch(err) { - throw "Erreur lors de l'obtention de la liste des administrateurs d'un groupe."; - } - } - - //------------------------------------------------------------------------------------------------------------------------ - // Fonction d'édition TBT - //------------------------------------------------------------------------------------------------------------------------ - - /** - * @summary Fonction qui édite un groupe existant dans le LDAP. Très similaire à {@link User.addGroup} - * @desc Appelle {@link LDAP.add} bien sûr, mais aussi {@link Admin.addGroupMember} et {@link Admin.addGroupAdmin} en godmode pour gérer les groupes du nouvel utilisateur. - * @arg {string} gid - Identifiant du groupe à modifier - * @arg {groupData} data - Dictionnaire des informations utilisateurs au même format que pour {@link User.addGroup} avec tous les champs optionnels. - * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon - * @async - * @static - */ - static async editGroup(gid: string, data: groupData) : Promise<boolean> { - try { - // Récupération des anciennes données - let profil = await Open.peekGroup(gid); - // Reecriture de profil avec les bons champs - Object.keys(profil).forEach(keyLDAP => { - Object.keys(ldapConfig.group).forEach(keyAlias => { - ldapConfig.group[keyAlias]=keyLDAP; - profil[keyAlias]=profil[keyLDAP]; - }); - }); - // Surcharge des champs à modifier selon data - Object.keys(data).forEach(key => { - profil[key]=data[key]; - }); - // Modification propre - if (!await Admin.delGroup(gid)&&await User.addGroup(profil)) { throw "Erreur de la destruction/recréation du groupe pour le modifier."; } - return true; - } - catch(err) { - throw "Erreur lors de l'obtention du profil d'un groupe pour le modifier."; - } - } - - //------------------------------------------------------------------------------------------------------------------------ - // Fonctions de suppression TBT - //------------------------------------------------------------------------------------------------------------------------ - - /** - * @summary Fonction qui supprime un groupe du LDAP. - * @desc Cette fonction commence par gérer les groupes du membre puis le supprime entièrement. - * Appelle {@link LDAP.clear} bien sûr, mais aussi {@link Admin.delGroupMember} et {@link Admin.delGroupAdmin} pour gérer les groupes de l'utilisateur sortant. - * @arg {string} gid - gid de la victime - * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon - * @async - * @static - */ - static async delGroup(gid): Promise<boolean> { - try { - // Gestion des membres et administrateurs d'abord - let profil = await Open.peekGroup(gid); - // Ordre important - profil[ldapConfig.group['admin']].forEach( id => { - this.delGroupAdmin( id, gid).then(res => { if (!res) { throw "Erreur lors de la suppression d'un admin d'un groupe en cours de suppression."; } }); - }); - profil[ldapConfig.group['member']].forEach(id => { - this.delGroupMember(id, gid).then(res => { if (!res) { throw "Erreur lors de la suppression d'un membre."; } }); - }); - // Elimination - if (!await LDAP.clear(ldapConfig.key_id+"="+gid+","+ldapConfig.dn_groups)) { throw "Erreur lors de la suppression de la feuille dans l'arbre des groupes."; } - return true; - } - catch(err) { - throw "Erreur lors de l'obtention du profil d'un groupe pour le supprimer."; - } - } -} - -export class Supervisor extends Admin { - /** - * @class Cette classe est la classe du super administrateur qui créé et supprime des membres. - * @summary Constructeur vide. - * @author hawkspar - */ - constructor(user) { super(); } - - //------------------------------------------------------------------------------------------------------------------------ - // Fonctions de création TBT - //------------------------------------------------------------------------------------------------------------------------ - - /** - * @summary Fonction qui créé un nouvel utilisateur dans le LDAP. - * @desc Appelle {@link LDAP.add} bien sûr, mais aussi {@link User.addGroupMember} et {@link Admin.addGroupAdmin} pour gérer les groupes du nouvel utilisateur. - * @arg {userData} data - Dictionnaire des informations utilisateurs. Des erreurs peuvent apparaître si tous les champs ne sont pas remplis. - * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon - * @async - * @static - */ - static async addUser(data: userData): Promise<boolean> { - // Calcul d'un dictionnaire d'ajout - let vals = {}; - - // uid de base généré à partir de nom et prénom, plus potentiellement promo et un offset - // MEF mélange de Promise et de fonction standard - try { - Tests.generateUid(data['givenName'],data['lastName'],data['promotion']).then(id => { vals[ldapConfig.key_id]=id; } ); - } - catch(err) { - throw "Erreur lors de la génération d'un hruid pour un nouvel utilisateur."; - } - - let uid = vals[ldapConfig.key_id]; - - // Ecriture de toutes les valeurs directement inscrites dans le LDAP (in pour input) - // Génère une erreur si un champ n'est pas rempli - ldapConfig.user.single.forEach(key_att => vals[ldapConfig.user.single[key_att]]=data[key_att]); - - // Appel à la fonction de base - if (!await LDAP.add(ldapConfig.key_id+"="+uid+","+ldapConfig.dn_users, vals)) { throw "Erreur de l'ajout de la feuille à l'arbre utilisateur."; } - - // Modifications multiples pour avoir plusieurs champs de même type ; boucle sur les attributs multiples - ldapConfig.user.multiple.forEach(key_att => { - // On rajoute chaque valeur en entrée - data[key_att].forEach(val => { - let vals2 = {}; - vals2[ldapConfig.user.multiple[key_att]]=val; - LDAP.change(ldapConfig.key_id+"="+uid+","+ldapConfig.dn_users, "add", vals2).then(res => { - if (!res) { throw "Erreur lors de l'ajout d'une valeur pour un champ à valeurs multiples à la feuille du nouvel utilisateur."; } - }); - }); - }); - - // Certains champs nécessitent de petits calculs - let vals3={}; - - // Création d'un nom complet lisible - vals3[ldapConfig.user.single['fullName']]=data['givenName']+' '+data['lastName'].toUpperCase(); - - // ldapConfiguration du mot de passe utilisateur - // Le préfixe {CRYPT} signifie que le mdp est hashé dans OpenLDAP voir : https://www.openldap.org/doc/admin24/security.html - vals3[ldapConfig.user.single['password']] = "{CRYPT}"+data['password']; - - // Ecriture d'un surnom s'il y a lieu - if ((data['nickname']!=undefined) && (data['nickname']!='')) { - vals3[ldapConfig.user.single['nickname']]=data['nickname']; - } - try { - // Génération id aléatoire unique - vals3[ldapConfig.user.single['id']]= await Tests.generateId(ldapConfig.user.single['id'], ldapConfig.dn_users); - } - catch(err) { - throw "Erreur lors de la génération d'un id numérique pour un nouvel utilisateur."; - } - - // Stockage machine ; dépend du prénom - vals3[ldapConfig.user['directory']] = '/hosting/users/' + data['givenName'][0]; - - // Code root - vals3[ldapConfig.user.single['cleanFullName']]=data['fullName'].replace(':', ';').toLowerCase().normalize('UFD'); - - // Adressage root - if (data['groups'].includes("on_platal")) { vals3[ldapConfig.user.single['login']] = "/bin/bash"; } - else { vals3[ldapConfig.user.single['login']] = "/sbin/nologin"; } - - // Permissions BR - vals3[ldapConfig.user.single['readPerm']] = 'br.*,public.*'; - if (data['readPerm'].length>0) { vals3[ldapConfig.user.single['readPerm']] += ',' + data['readPerm']; } - vals3[ldapConfig.user.single['writePerm']] = 'br.*,!br.blague-du-jour,public.*,!br.campagnekes'; - if (data['writePerm'].length>0) { vals3[ldapConfig.user.single['readPerm']] += ',' + data['writePerm']; } - - // Valeur nécessaire ASKIP mais inutile - vals3[ldapConfig.user.single['idNum']] ='5000'; - - // Inscription des valeurs calculées - if (!await LDAP.change(ldapConfig.key_id+"="+uid+","+ldapConfig.dn_users, "add", vals3)) { - throw "Erreur lors de l'ajout des valeurs calculées à la feuille du nouvel utilisateur."; - } - - ["posixAccount", "shadowAccount", "inetOrgPerson", "brAccount"].forEach(cst => { - let val3={}; - vals3[ldapConfig.user.multiple['class']]=cst; - LDAP.change(ldapConfig.key_id+"="+uid+","+ldapConfig.dn_users, "add", vals3).then(res => { - if (!res) { throw "Erreur lors de l'ajout d'une valeur constante à la feuille du nouvel utilisateur."; } - }); - }); - - // Utilisation des fonctions adaptées pour assurer la cohérence de l'ensemble - data['groupsIsMember'].forEach(gid => { - Admin.addGroupMember(uid, gid).then(res => { - if (!res) { throw "Erreur lors de l'ajout du nouvel utilisateur à un groupe."; } - }); - }); - data['groupsIsAdmin'].forEach(gid => { - Admin.addGroupAdmin(uid, gid).then(res => { - if (!res) { throw "Erreur lors de l'ajout du nouvel utilisateur à un groupe en tant qu'admin."; } - }); - }); - - return true; - } - - //------------------------------------------------------------------------------------------------------------------------ - // Fonctions de suppression TBT - //------------------------------------------------------------------------------------------------------------------------ - - /** - * @summary Fonction qui supprime un utilisateur du LDAP. - * @desc Cette fonction commence par gérer les groupes du membre puis le supprime entièrement. - * Appelle {@link LDAP.clear} bien sûr, mais aussi {@link Admin.delGroupMember} et {@link Admin.delGroupAdmin} pour gérer les groupes de l'utilisateur sortant. - * @arg {string} uid - uid de la victime - * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon - * @async - * @static - */ - static async delUser(uid: string): Promise<boolean> { - try { - // Gestion des groupes d'abord - let profil = await Open.peekUser(uid); - profil[ldapConfig.user.multiple['groups']].forEach(gid => { - if (Open.isGroupAdmin(uid,gid)) { - if (!Admin.delGroupAdmin(uid, gid)) { throw "Erreur lors de la suppression des droits d'admin de l'utilisateur."; } - } - if (!Admin.delGroupMember(uid, gid)) { throw "Erreur lors de la suppression de l'appartenance à un groupe de l'utilisateur."; } - }); - } - catch(err) { - throw "Erreur lors de l'obtention des informations de l'utilisateur à supprimer."; - } - // Elimination - if (!LDAP.clear(ldapConfig.key_id+"="+uid+","+ldapConfig.dn_users)) { throw "Erreur lors de la suppression de l'utilisateur."; } - return true; - } -} \ No newline at end of file diff --git a/src/ldap/basics.ts b/src/ldap/basics.ts deleted file mode 100644 index 9e29669db22816ed92d17484f6f213192aa79d5f..0000000000000000000000000000000000000000 --- a/src/ldap/basics.ts +++ /dev/null @@ -1,189 +0,0 @@ -/** - * @file Ce fichier regroupe les fonctions fondamentales aux interactions avec le LDAP. - * C'est ici que tout le filtrage est opéré, au plus bas niveau. - * Toutes les fonctions écrites ici sont asynchrones et renvoient des Promises ce qui nécessite de les appeler avec la synthaxe - * un peu particulière `f(args).then(res => ...)` pour exploiter leur résultat. - * Le découpage par fichier est arbitraire mais permet de regrouper certaines classes proches. - * @author hawkspar - */ - -import ldap from 'ldapjs'; -// Toutes les entrées utilisateur sont escapées par sécurité -import ldapEscape from 'ldap-escape'; -// Fichier de ldapConfig du ldap -import {ldapConfig, credentialsLdapConfig} from './config'; - -// Important ; permet de vérifier que l'utilisateur reste connecté. -//var ensureLoggedin = require('connect-ensure-login').ensureLoggedIn; //hawkspar->manifold : est-ce encore utile ? je ne crois pas - -// Connection au serveur LDAP avec des temps de timeout arbitraires -var client = ldap.createClient({ url: ldapConfig.server}); - -//------------------------------------------------------------------------------------------------------------------------ -// Fonctions de base agissant sur le LDAP -//------------------------------------------------------------------------------------------------------------------------ - -export class LDAP { - /** - * @class Cette classe est la brique de base du fichier tout entier puisqu'elle contient les functions qui agissent directement sur le LDAP. - * @summary Constructeur vide. - */ - constructor() {} - - /** - * @summary Fonction qui sert à s'identifier sur le LDAP. - * @desc Assez important en terme de sécurité, de gestion de conflit, et de droit d'accès. Méthode ldapjs - * (voir [`Client API`](http://ldapjs.org/client.html) méthode bind). - * @arg {string} dn - Nom de domaine ; identifiant de l'utilisateur cherchant à se connecter - * @arg {string} password - Mot de passe de l'utilisateur cherchant à se connecter - * @returns {Promise(boolean)} `true` si l'opération s'est bien déroulée, `false` sinon. - * @static - * @async - */ - static async bind(dn: string, password: string) : Promise<boolean> { - // Escape DN as everywhere in this file, but password is taken as is - client.bind(dn, password, res => { - // Gestion erreur - try { res; } - catch(err) { - throw "Erreur lors de la connection au LDAP."; - } - }); - // End with a boolean - return true; - } - - /** - * @summary Fonction qui sert à s'identifier sur le LDAP avec plein pouvoirs. - * @desc Appelle {@link bind} avec un utilisateur tout puissant. - * @returns {Promise(boolean)} `true` si l'opération s'est bien déroulée, `false` sinon. - * @static - * @async - */ - static async adminBind() : Promise<boolean> { return LDAP.bind(credentialsLdapConfig.dn, credentialsLdapConfig.password); } - - /** - * @summary Fonction qui sert à se déconnecter du LDAP. - * @desc Assez important en terme de sécurité, de gestion de conflit, et de droit d'accès. - * Fait appel à {@link LDAP.bind} avec deux champs vides. - * @returns {Promise(boolean)} `true` si l'opération s'est bien déroulée, `false` sinon. - * @static - * @async - */ - static async unbind() : Promise<boolean> { return LDAP.bind("", ""); } - - /** - * @summary Fonction qui interroge le LDAP selon un protocole spécifié en argument et renvoit les valeurs trouvées. - * @desc Cette fonction utilise ldapjs (voir [`Client API`](http://ldapjs.org/client.html) méthode search). Cette fonction fait une demande au LDAP - * qu'elle filtre selon un schéma prédéfini dans `filter` et à chaque résultat (event SearchEntry) le met dans une liste, et renvoit la liste à l'issue (event end). - * @arg {string} dn - DN de l'emplacement de la requête - * @arg {string} filter ["(objectClass=*)"] - Filtre logique de la recherche (format [`RFC2254`](https://tools.ietf.org/search/rfc2254)) déjà passé au ldapEscape - * @arg {string[]} attributes - Liste des attributs qui figureront dans le résultat final ; peut aussi être un seul élément - * @return {(Promise(Array.<Object>)|Promise(Array.Object.<string, Object>))} Résultats de la recherche ; soit une liste de valeurs d'attributs, - * soit une liste de dictionnaires si on veut plus d'un attribut (les clés du dictionnaire sont celles du LDAP) - * @static - * @async - */ - static async search(dn: string, attributes: string[], filter="(objectClass=*)") : Promise<Array<any>> { - LDAP.adminBind(); - let vals=[]; - // Interrogation LDAP selon ldapConfiguration fournie en argument - client.search(ldapEscape.dn("${txt}", { txt: dn}), { - "scope": "sub", - "filter": ldapEscape.filter("${txt}", { txt: filter}), - "attributes": attributes - }, (err, res) => { - // Gestion erreur ; pb car pas simple true / autre en sortie - if (err) { - throw "Erreur lors de la recherche sur le LDAP."; - } else { - // Dès que la recherche renvoit une entrée, on stocke les attributs qui nous intéresse - res.on('searchEntry', entry => { - // Cas un seul attribut où le résultat est une liste directement - if (!Array.isArray(attributes)) { vals.push(entry.object[attributes]); } - else if (attributes.length == 1) { vals.push(entry.object[attributes[0]]); } - // Cas plusieurs attributs donc résultat dictionnaire - else { - vals.push({}); - attributes.forEach(attribute => { - vals.slice(-1)[0][attribute]=entry.object[attribute]; - }); - } - }); - // Si la recherche renvoie une erreur, on renvoit - res.on('error', resErr => { throw resErr; }); - // Si la recherche est finie on se déconnecte et on renvoit la liste - res.on('end', res => { LDAP.unbind(); }); - } - }); - return vals; - } - - //TBT - /** - * @summary Fonction qui permet de modifier un élément sur le LDAP. Gestion intelligente de l'appartenance à un binet. - * @desc Cette fonction traite la demande avec ldapjs (voir [`Client API`](http://ldapjs.org/client.html) méthode modify). - * @arg {string} dn - DN de l'endroit à modifier - * @arg {string} op - Operation à réaliser sur le LDAP. Trois opération sont possibles ; "add", qui rajoute des attributs et qui peut créer des doublons, - * "del" qui en supprime, et "replace" qui remplace du contenu par un autre. - * @arg {Object.<string, string>} mod - Dictionnaire contenant les attributs à modifier et les nouvelles valeurs des attributs. - * @arg {Object} mod[key] - Nouvelle valeur de l'attribut key. Une nouvelle valeur vide ("") est équivalent à la suppression de cet attribut. - * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, `false` sinon. - * @static - * @async - */ - static async change(dn: string, op: string, mod) : Promise<boolean> { - LDAP.adminBind(); - // Modification LDAP selon ldapConfiguration en argument (pourrait prendre une liste de Changes) - client.modify(ldapEscape.dn("${txt}", {txt: dn}), new ldap.Change({ - operation: ldapEscape.dn("${txt}", {txt: op}), - modification: mod, - // Gestion erreur - }), err => { - throw "Erreur lors d'une opération de modification sur le LDAP."; - }); - LDAP.unbind(); - return true; - } - - //TBT - /** - * @summary Fonction qui permet de rajouter un élément sur le LDAP. - * @desc Cette fonction traite la demande avec ldapjs (voir [`Client API`](http://ldapjs.org/client.html) méthode add). - * @arg {string} dn - Adresse du parent - * @arg {Object.<string, string>} vals - Dictionnaire contenant les valeurs à créer - * @arg {Object} vals[key] - Nouvelle valeur pour le champ key - * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon. - * @static - * @async - */ - static async add(dn: string, vals) : Promise<boolean> { - LDAP.adminBind(); - // Ajout LDAP selon la ldapConfiguration en argument - client.add(ldapEscape.dn(ldapConfig.key_id+"="+vals[ldapConfig.key_id]+",${txt}", { txt: dn}), vals, err => { - throw "Erreur lors d'une opération d'ajout sur le LDAP."; - }); - LDAP.unbind(); - return true; - } - - //TBT - /** - * @summary Fonction qui permet de supprimer une feuille du LDAP. - * @desc Cette fonction traite la demande avec ldapjs (voir [`Client API`](http://ldapjs.org/client.html) méthode del). - * Elle est différente de modify avec "del" car elle affecte directement une feuille et pas un attribut. - * @arg {string} dn - Adresse de la cible - * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon - * @static - * @async - */ - static async clear(dn: string) : Promise<boolean> { - LDAP.adminBind(); - // Suppression LDAP - client.del(ldapEscape.dn("${txt}", {txt: dn}), err => { - throw "Erreur lors d'une opération de suppression sur le LDAP."; - }); - LDAP.unbind(); - return true; - } -} \ No newline at end of file diff --git a/src/ldap/config.ts b/src/ldap/config.ts deleted file mode 100644 index a05c02d720f3d5bb5188d273d76fd53df4a34034..0000000000000000000000000000000000000000 --- a/src/ldap/config.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * @file Importe la configuration du LDAP au sein de l'application, et remplace certaines valeurs en fonction des variables d'environnement. - * @author manifold - */ - -import fs from 'fs'; -import path from 'path'; -import colors from 'colors'; -// Point central ; tous les champs de la BDD sont 'cachés' dans config.json et pas visibles directement -let path_config = path.resolve('ldap_config.json'); -let path_credentials = path.resolve('ldap_credentials.json'); -console.log(colors.cyan("Loading LDAP config file from "+path_config)); -console.log(colors.cyan("Loading LDAP credentials from "+path_credentials)); -export const ldapConfig = JSON.parse(fs.readFileSync(path_config).toString()); -export const credentialsLdapConfig = JSON.parse(fs.readFileSync(path_credentials).toString()); -// Override config server from environment -if (process.env.LDAP_URI != null) { - ldapConfig.ldap.server = process.env.LDAP_URI; -} \ No newline at end of file diff --git a/src/ldap/export/group.ts b/src/ldap/export/group.ts new file mode 100644 index 0000000000000000000000000000000000000000..d36eb37a844afe75926bc3cc8c51b5eab1e66f34 --- /dev/null +++ b/src/ldap/export/group.ts @@ -0,0 +1,285 @@ +/** + * @file Ce fichier contient la classe de l'API du LDAP qui gère les opérations sur les groupes. Elle est destinée à être exportée pour être utilisée par les resolvers. + * @author hawkspar + * @memberof LDAP + */ + +import { ldapConfig, groupData, categories } from '../internal/config'; +import {Basics} from '../internal/basics'; +import {Tools} from '../internal/tools'; + +//------------------------------------------------------------------------------------------------------------------------ +// Classes à exporter TBT +//------------------------------------------------------------------------------------------------------------------------ + +export {groupData}; + +export class Group { + /** + * @memberof LDAP + * @class Group + * @classdesc Cette classe est une des deux classes exportables permettant de faire des opérations sur les groupes. + * @summary Constructeur vide. + */ + constructor() {} + + /** + * @memberof LDAP + * @summary Fonction qui renvoit toutes les infos relatives à un groupe particulier. + * @desc Cette fonction utilise {@link Tools.peek} avec l'interface {@link groupData}. Elle ne fait que consulter le groupe sans le changer, et en extrayant les uid des membres. + * @arg {string} gid - Identifiant du groupe + * @return {Promise(groupData)} Informations recueillies ; renvoie une liste de dictionnaire avec le profil complet du groupe au format {@link groupData}. + * @static + * @async + */ + static async peek(gid: string) : Promise<groupData> { + try { + let data = await Tools.peek<groupData>("group", gid, groupData); + // Extraction des uid de membres (TBM pour inclure childs aussi) + for (let cat of categories.concat(["parents"])) data[cat] = data[cat].map(dn => dn.split(',')[0].split('=')[1]); + return data; + } + catch(err) { + throw "Erreur lors d'une recherche d'informations sur un groupe."; + } + } + + /** + * @memberof LDAP + * @summary Fonction qui retrouve le groupe qui ressemblent à l'input et qui correspond au type fourni. Etape 0 vers un vrai TOL (Trombino On Line) pour les groupes. + * @desc Cette fonction utilise {@link Tools.search}. + * @arg {groupData} input - String entré par l'utilisateur qui ressemble au nom du groupe. + * @return {Promise(string[])} Liste des gid dont le nom ressemble à l'input. + * @static + * @async + */ + static async search(data: groupData) : Promise<string[]> { + try { + return Tools.search("group", data); + } + catch(err) { + throw "Erreur lors de la recherche approximative d'un groupe."; + } + } + + /** + * @memberof LDAP + * @summary Fonction qui permet de rajouter un administrateur à un groupe. + * @desc Cette fonction fait essentiellement appel à {@link Tools.add}. Le nouvel administrateur ne devient pas membre ou porte-parole du groupe pour autant ! + * @arg {string} uid - Identifiant du membre futur admin + * @arg {string} gid - Identifiant du groupe + * @return {boolean} `true` si la modification s'est bien déroulée, false sinon + * @async + * @static + */ + static async addAdmin(uid: string, gid: string): Promise<boolean> { return Tools.add(uid, gid, "admins"); } + + /** + * @memberof LDAP + * @summary Fonction qui permet de supprimer un administrateur. + * @desc Cette fonction fait essentiellement appel à {@link Tools.remove}. + * Elle ne remonte pas les échelons, car cela permettrait à un admin d'un petit groupe de supprimer un admin d'un grand. + * @arg {string} uid - Identifiant de l'admin à dégrader, supposé membre + * @arg {string} gid - Identifiant du groupe + * @return {boolean} `true` si la modification s'est bien déroulée, false sinon + * @async + * @static + */ + static async remAdmin(uid: string, gid: string): Promise<boolean> { return Tools.remove(uid, gid, "admins"); } + + /** + * @memberof LDAP + * @summary Fonction qui permet de rajouter un porte-parole à un groupe. + * @desc Cette fonction fait essentiellement appel à {@link Tools.add}. Elle ne rajoute pas l'utilisateur au groupe. + * @arg {string} uid - Identifiant du membre futur porte-parole + * @arg {string} gid - Identifiant du groupe + * @return {boolean} `true` si la modification s'est bien déroulée, false sinon + * @async + * @static + */ + static async addSpeaker(uid: string, gid: string): Promise<boolean> { return Tools.add(uid, gid, "speakers"); } + + /** + * @memberof LDAP + * @summary Fonction qui permet de rétrograder un membre du stade de porte-parole d'un groupe au stade d'utilisateur. + * @desc Cette fonction fait essentiellement appel à {@link Tools.remove}. Elle dégrade aussi d'un éventuel statut d'administrateur. + * @arg {string} uid - Identifiant de l'admin à dégrader (pas supposé valide) + * @arg {string} gid - Identifiant du groupe + * @return {boolean} `true` si la modification s'est bien déroulée, false sinon + * @async + * @static + */ + static async remSpeaker(uid: string, gid: string): Promise<boolean> { return Tools.remove(uid, gid, "speakers"); } + + /** + * @memberof LDAP + * @summary Fonction qui permet d'ajouter un utilisateur à un groupe. + * @desc Cette fonction fait essentiellement appel à {@link Tools.add}. + * @arg {string} uid - Identifiant de l'utilisateur à ajouter + * @arg {string} gid - Identifiant du groupe + * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon + * @async + * @static + */ + static async addMember(uid: string, gid: string) : Promise<boolean> { return Tools.add(uid, gid, "members"); } + + /** + * @memberof LDAP + * @summary Fonction qui permet de supprimer un membre existant d'un groupe. + * @desc Cette fonction fait essentiellement appel à {@link Tools.add}. + * Cette fonction supprime tous les droits de l'utilisateur sur le groupe, mais aussi sur les groupes sources si son statut de membre était hérité. + * @arg {string} uid - Identifiant de l'ex-membre (pas supposé valide) + * @arg {string} gid - Identifiant du groupe + * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon + * @async + * @static + */ + static async remMember(uid: string, gid: string): Promise<boolean> { return Tools.remove(uid, gid, "members"); } + + /** + * @memberof LDAP + * @summary Fonction qui permet d'ajouter un sympathisant à un groupe. + * @desc Cette fonction fait essentiellement appel à {@link Tools.add}. + * @arg {string} uid - Identifiant de l'utilisateur à ajouter + * @arg {string} gid - Identifiant du groupe + * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon + * @async + * @static + */ + static async addFollower(uid: string, gid: string) : Promise<boolean> { return Tools.add(uid, gid, "followers"); } + + /** + * @memberof LDAP + * @summary Fonction qui permet de supprimer un sympathisant d'un groupe. + * @desc Cette fonction fait essentiellement appel à {@link Tools.add}. + * @arg {string} uid - Identifiant de l'ex-sympathisant (pas supposé valide) + * @arg {string} gid - Identifiant du groupe + * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon + * @async + * @static + */ + static async remFollower(uid: string, gid: string): Promise<boolean> { return Tools.remove(uid, gid, "followers"); } + + /** + * @memberof LDAP + * @summary Fonction qui créé un nouveau groupe dans le LDAP. + * @desc Cette fonction fait une utilisation massive d'eval pour anonymiser son code ; c'est mal et cela suppose que beaucoup de soins ont été pris lors de + * l'escape de ses paramètres. Appelle {@link LDAP.add} et {@link LDAP.change}, mais aussi {@link Tools.add} + * pour gérer les groupes du nouvel utilisateur. Cettte application permet de rajouter des utilisateurs à toutes les catégories du groupe. + * @arg {groupData} data - Dictionnaire des informations utilisateurs (voir détail des champs dans ldapConfig.json) + * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon + * @async + * @static + */ + static async create(data: groupData) : Promise<boolean> { + // Calcul d'un dictionnaire d'ajout + let vals = {}; + + // gid de base généré à partir du nom standardisé, pas à partir de l'entrée 'gid' ! + try { + Tools.generateReadableId("group", data['name']).then(id => { + vals[ldapConfig.group.gid] = id; + vals[ldapConfig.group['name']] = id; + }); + } + catch(err) { + throw "Erreur lors de la génération d'un hruid pour créer un nouveau groupe."; + } + + let gid: string = vals[ldapConfig.group.gid]; + + // Ecriture de toutes les valeurs directement inscrites dans le LDAP + for (let key_att of ["name","logo","description","site","category"]) { + if (data[key_att] != undefined) vals[ldapConfig.group[key_att]]=data[key_att] + }; + + // Appel à la fonction de base + if (!await Basics.add("group", vals)) throw "Erreur lors de la création d'une nouvelle feuille dans l'arbre des groupes."; + + // Certains champs nécessitent de petits calculs + let vals2={}; + + // ?! + vals2[ldapConfig.group['password']] = "{CRYPT}"+data['password']; + + // Génération id aléatoire et test contre le LDAP + try { + Tools.generateId(ldapConfig.group["idNumber"], "group").then(id => { vals2[ldapConfig.group['idNumber']]=id; }); + } + catch(err) { + throw "Erreur lors de la génération d'un id numérique pour créer un nouveau groupe."; + } + + // Inscription des valeurs calculées par effet de bord + if (!await Basics.change("group", gid, "add", vals2)) { + throw "Erreur lors de l'ajout des valeurs intelligentes du nouveau groupe."; + } + + ["posixGroup", "brGroup"].forEach(cst => { + let vals3={}; + vals3[ldapConfig.group['classes']]=cst; + Basics.change("group", gid, "add", vals3).then(res => { + if (!res) throw "Erreur lors de l'ajout des valeurs constantes du nouveau groupe."; + }); }); + + // Ajout groupes parents et fils + for (let rel of ["childs","parents"]) { + data[rel].forEach(gid => { + let tmp = ldapConfig.group[rel]; + Basics.change("group", gid, "add", { tmp: gid }).then(res => { + if (!res) throw "Erreur de l'ajout d'un groupe associé au nouveau groupe."; + }); + }); + } + + // Utilisation des fonctions adaptées pour assurer la cohérence de l'ensemble + for (let cat of categories) { + for (let uid of data[cat]) Tools.add(uid, gid, cat).then(res => { + if (!res) throw "Erreur de l'ajout d'un membre au nouveau groupe."; + }); + } + return true; + } + + /** + * @memberof LDAP + * @summary Fonction qui supprime un groupe du LDAP. + * @desc Cette fonction commence par gérer les groupes du membre puis le supprime entièrement. A modifier une fois que le LDAP incluerait les groupes administres par une utilisateur. + * Appelle {@link LDAP.clear} bien sûr, mais aussi {@link Tools.remove} pour gérer les groupes de l'utilisateur sortant. + * @arg {string} gid - gid de la victime + * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon + * @async + * @static + */ + static async delete(gid: string): Promise<boolean> { + try { + // Gestion des catégories en bloc d'abord + let profile = await Group.peek(gid); + for (let cat of categories) profile[ldapConfig.group[cat]].forEach(uid => Tools.remove(uid, gid, cat)); + // Elimination + if (!await Basics.clear("group",gid)) throw "Erreur lors de la suppression de la feuille dans l'arbre des groupes."; + return true; + } + catch(err) { + throw "Erreur lors de l'obtention du profil d'un groupe pour le supprimer."; + } + } + + /** + * @memberof LDAP + * @summary Fonction qui édite un groupe existant dans le LDAP. Sans influence sur ses membres ou admins. + * @desc Appelle {@link Tools.edit}. + * @arg {groupData} data - Dictionnaire des informations du groupe. + * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon + * @async + * @static + */ + static async edit(data: groupData) : Promise<boolean> { + try { + return Tools.edit("group",data); + } + catch(err) { + throw "Erreur lors de la modification d'un groupe."; + } + } +} \ No newline at end of file diff --git a/src/ldap/export/user.ts b/src/ldap/export/user.ts new file mode 100644 index 0000000000000000000000000000000000000000..c9c4b8d1ab9b16afbe83d6305ac9c7dc11abc1a3 --- /dev/null +++ b/src/ldap/export/user.ts @@ -0,0 +1,185 @@ +/** + * @file Ce fichier contient la classe de l'API du LDAP qui gère les opérations sur les groupes. Elle est destinée à être exportée pour être utilisée par les resolvers. + * @author hawkspar + * @memberof LDAP + */ + +import { ldapConfig, userData, categories } from '../internal/config'; +import {Basics} from '../internal/basics'; +import {Tools} from '../internal/tools'; + +//------------------------------------------------------------------------------------------------------------------------ +// Classes à exporter TBT +//------------------------------------------------------------------------------------------------------------------------ + +export {userData}; + +export class User { + /** + * @memberof LDAP + * @class User + * @classdesc Cette classe est une des deux classes exportables permettant de faire des opérations sur les utilisateurs. + * @summary Constructeur vide. + */ + constructor() {} + + /** + * @memberof LDAP + * @summary Fonction qui renvoit les infos de base relatives à un utilisateur particulier. + * @desc Cette fonction utilise {@link Tools.peek} avec l'interface {@link userData}. + * @arg {string} uid - Identifiant de l'utilisateur, supposé valide. + * @return {Promise(userData)} Informations recueillies au format {@link userData}. + * @static + * @async + */ + static async peek(uid: string) : Promise<userData> { + try { + let data = await Tools.peek<userData>("user", uid, userData); + for (let cat of categories) data[cat] = data[cat].map(dn => dn.split(',')[0].split('=')[1]); + return data; + } + catch(err) { + throw "Error while peeking a user."; + } + } + + /** + * @memberof LDAP + * @summary Fonction qui retrouve les uid des paxs validant les critères de recherche. Utiliser {@link User.peek} au cas par cas après pour obtenir les vraies infos. + * @desc Cette fonction utilise {@link Tools.search}. + * @arg {userData} data - Dictionnaire contenant les données nécessaires à la recherche. Les valeurs sont celles entrées par l'utilisateur et sont par hypothèse + * comme des sous-parties compactes des valeurs renvoyées. Tous les champs ci-dessous peuvent être indifféremment des listes (par exemple pour chercher un membre + * de plusieurs groupes) ou des éléments isolés. Si un champ n'est pas pertinent, le mettre à '' ou undefined. + * @return {Promise(string[])} gids des profils qui "match" les critères proposés. + * @static + * @async + */ + static async search(data: userData) : Promise<string[]> { + try { + return Tools.search("user", data); + } + catch(err) { + throw "Erreur lors de la recherche approximative d'un utilisateur."; + } + } + + /** + * @memberof LDAP + * @summary Fonction qui créé un nouvel utilisateur dans le LDAP. + * @desc Appelle {@link LDAP.add} bien sûr, mais aussi {@link Tools.add} pour gérer les groupes du nouvel utilisateur. + * @arg {userData} data - Dictionnaire des informations utilisateurs. Des erreurs peuvent apparaître si tous les champs ne sont pas remplis. + * Cette application permet de rejoindre des groupes en masse pour toute catégorie, à la fois façon Sigma et CAS. + * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon + * @async + * @static + */ + static async create(data: userData): Promise<boolean> { + // Calcul d'un dictionnaire d'ajout + let vals = {}; + + // uid de base généré à partir de nom et prénom, plus potentiellement promo et un offset + // MEF mélange de Promise et de fonction standard + try { + Tools.generateUid("user",data['givenName'],data['lastName'],data['birthdate']).then(id => { vals[ldapConfig.user.uid]=id; } ); + } + catch(err) { + throw "Erreur lors de la génération d'un hruid pour un nouvel utilisateur."; + } + + let uid : string = vals[ldapConfig.user.uid]; + + // Génère une erreur si un champ n'est pas rempli + for (let key_att of ["givenName","lastName","nickname","gender","photo","phone","adress","mail","birthdate","nationality"]) { + // Ecriture de toutes les valeurs uniques + if (data[key_att] != undefined) vals[ldapConfig.user[key_att]]=data[key_att]; + } + + // Appel à la fonction de base + if (!await Basics.add("user", vals)) throw "Erreur de l'ajout de la feuille à l'arbre utilisateur."; + + // Certains champs nécessitent de petits calculs + let vals3={}; + + // ldapConfiguration du mot de passe utilisateur + // Le préfixe {CRYPT} signifie que le mdp est hashé dans OpenLDAP voir : https://www.openldap.org/doc/admin24/security.html + vals3[ldapConfig.user['password']] = "{CRYPT}"+data['password']; + + // Ecriture d'un surnom s'il y a lieu + if ((data['nickname']!=undefined) && (data['nickname']!='')) vals3[ldapConfig.user['nickname']]=data['nickname']; + try { + // Génération id aléatoire unique + vals3[ldapConfig.user['id']]= await Tools.generateId(ldapConfig.user['id'], "user"); + } + catch(err) { + throw "Erreur lors de la génération d'un id numérique pour un nouvel utilisateur."; + } + + // Code root + vals3[ldapConfig.user['cleanFullName']]=data['fullName'].replace(':', ';').toLowerCase().normalize('UFD'); + + // Inscription des valeurs calculées + if (!await Basics.change("user", uid, "add", vals3)) throw "Erreur lors de l'ajout des valeurs calculées à la feuille du nouvel utilisateur."; + + ["posixAccount", "shadowAccount", "brUser"].forEach(cst => { + let val3={}; + vals3[ldapConfig.user['class']]=cst; + Basics.change("user", uid, "add", vals3).then(res => { + if (!res) throw "Erreur lors de l'ajout d'une valeur constante à la feuille du nouvel utilisateur."; + }); + }); + + // Ajout dans les groupes à la catégorie voulue + for (let cat of categories) { + for (let gid of data[cat]) Tools.add(uid, gid, cat).then(res => { + if (!res) throw "Erreur de l'ajout d'un membre au nouveau groupe."; + }); + } + return true; + } + + //------------------------------------------------------------------------------------------------------------------------ + // Fonctions de suppression TBT + //------------------------------------------------------------------------------------------------------------------------ + + /** + * @memberof LDAP + * @summary Fonction qui supprime un utilisateur du LDAP. + * @desc Cette fonction commence par gérer les groupes du membre puis le supprime entièrement. + * Appelle {@link LDAP.clear} bien sûr, mais aussi {@link Tools.remove} pour gérer les groupes de l'utilisateur sortant. + * @arg {string} uid - uid de la victime + * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon + * @async + * @static + */ + static async delete(uid: string): Promise<boolean> { + try { + // Gestion des groupes de l'utilisateur d'abord + let profil = await User.peek(uid); + for (let cat of categories) profil[ldapConfig.user[cat]].forEach(gid => Tools.remove(uid, gid, cat)); + } + catch(err) { + throw "Erreur lors de l'obtention des informations de l'utilisateur à supprimer."; + } + // Elimination pure et simple + if (!Basics.clear("user", uid)) throw "Erreur lors de la suppression de l'utilisateur."; + return true; + } + + /** + * @memberof LDAP + * @summary Fonction qui édite un utilisateur existant dans le LDAP. + * @desc Appelle simplement {@link Tools.edit}. Sans effet sur les groupes de l'utilisateur concerné. + * @arg {userData} data - Dictionnaire des informations utilisateurs + * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon + * @async + * @static + */ + static async edit(data : userData) : Promise<boolean> { + try { + return Tools.edit("user",data); + } + catch(err) { + throw "Erreur lors de la modification d'un utilisateur."; + } + } +} \ No newline at end of file diff --git a/src/ldap/internal/basics.ts b/src/ldap/internal/basics.ts new file mode 100644 index 0000000000000000000000000000000000000000..97dd6267c0de2d098a08e42457d677ecc76140cd --- /dev/null +++ b/src/ldap/internal/basics.ts @@ -0,0 +1,263 @@ +/** + * @file Ce fichier regroupe les fonctions fondamentales aux interactions avec le LDAP. + * C'est ici que tout le filtrage est opéré, au plus bas niveau. + * Toutes les fonctions écrites ici sont asynchrones et renvoient des Promises ce qui nécessite de les appeler avec la synthaxe + * un peu particulière `f(args).then(res => ...)` pour exploiter leur résultat. + * @author hawkspar + * @memberof LDAP + */ + +import ldap from 'ldapjs'; +// Toutes les entrées utilisateur sont escapées par sécurité +import ldapEscape from 'ldap-escape'; +// Fichier de ldapConfig du ldap +import {ldapConfig, credentialsLdapConfig} from './config'; + +// Connection au serveur LDAP avec des temps de timeout arbitraires +var client = ldap.createClient({ url: ldapConfig.server}); + +// Interface pratique pour que Typescript comprenne ce qu'est un dictionnaire simple +interface dic { + [Key: string]: string | string[]; +} + +//------------------------------------------------------------------------------------------------------------------------ +// Fonctions de base agissant sur le LDAP +//------------------------------------------------------------------------------------------------------------------------ + +export class Basics { + /** + * @memberof LDAP + * @class Basics + * @classdesc Cette classe est la brique de base du fichier tout entier puisqu'elle contient les functions qui agissent directement sur le LDAP. + * @summary Constructeur vide. + */ + constructor() {} + + /** + * @memberof LDAP + * @summary Fonction qui sert à s'identifier sur le LDAP. + * @desc Assez important en terme de sécurité, de gestion de conflit, et de droit d'accès. Méthode ldapjs + * (voir [`Client API`](http://ldapjs.org/client.html) méthode bind). + * @arg {string} dn - Nom de domaine ; identifiant de l'utilisateur cherchant à se connecter + * @arg {string} password - Mot de passe de l'utilisateur cherchant à se connecter + * @returns {Promise(boolean)} `true` si l'opération s'est bien déroulée, `false` sinon. + * @static + * @async + */ + static async bind(dn: string, password: string) : Promise<boolean> { + // Escape DN as everywhere in this file, but password is taken as is + client.bind(dn, password, res => { + // Gestion erreur + try { res; } + catch(err) { + throw "Erreur lors de la connection au LDAP."; + } + }); + // End with a boolean + return true; + } + + /** + * @memberof LDAP + * @summary Fonction qui sert à s'identifier sur le LDAP avec plein pouvoirs. + * @desc Appelle {@link bind} avec un utilisateur tout puissant. + * @returns {Promise(boolean)} `true` si l'opération s'est bien déroulée, `false` sinon. + * @static + * @async + */ + static async adminBind() : Promise<boolean> { return Basics.bind(credentialsLdapConfig.dn, credentialsLdapConfig.password); } + + /** + * @memberof LDAP + * @summary Fonction qui sert à se déconnecter du LDAP. + * @desc Assez important en terme de sécurité, de gestion de conflit, et de droit d'accès. + * Fait appel à {@link Basics.bind} avec deux champs vides. + * @returns {Promise(boolean)} `true` si l'opération s'est bien déroulée, `false` sinon. + * @static + * @async + */ + static async unbind() : Promise<boolean> { return Basics.bind("", ""); } + + + /** + * @memberof LDAP + * @callback entryHandler + * @arg entry {*} - Convoluted ldap.js search result object + */ + + /** + * @memberof LDAP + * @summary Fonction qui interroge le LDAP selon un protocole spécifié en argument et modifie une liste pour y insérer les valeurs trouvées. + * @desc Cette fonction utilise ldapjs (voir [`Client API`](http://ldapjs.org/client.html) méthode search). Cette fonction fait une demande au LDAP + * qu'elle filtre selon un schéma prédéfini dans `filter` et à chaque résultat (event SearchEntry) le met dans une liste, et renvoit la liste à l'issue (event end). + * @arg {'gr'|'us'} domain - Emplacement de la requête (groupe ou utilisateur) + * @arg {string[]} attributes - Attributs à renvoyer + * @arg {string} id [null] - Identifiant facultatif pour une recherche triviale en o(1) + * @arg {string} filter ["(objectClass=*)"] - Filtre logique de la recherche (format [`RFC2254`](https://tools.ietf.org/search/rfc2254)) déjà passé au ldapEscape + * @arg {entryHandler} handler - Wrapper pour gérer les requêtes simples ou multiples + * @return {void} Utilise handler pour gérer ses résultats au fur et à mesure + * @static + * @async + */ + static search(domain: 'group'|'user', attributes: string[], id: string, filter: string, handler : (entry: any) => void) : Promise<void> { + let dn =""; + if (id != null) { + if (domain == "group") dn += ldapConfig.group.gid; + else dn += ldapConfig.user.uid; + dn += '=' + ldapEscape.dn("${txt}", { txt: id }) + ','; + } + dn+=ldapConfig.dn[domain]; + console.log("Searching dn= " + dn + ", filter : " + filter); + // Interrogation LDAP selon filter + let promise = new Promise<void>(function(resolve, reject) { + client.search(dn, { // Must be escaped in case of a malignious false id + "scope": "sub", + "filter": filter, // Must be escaped in case of a malignious search arg + "attributes": attributes + }, (err, res) => { + // Gestion erreur ; pb car pas simple true / autre en sortie + if (err) { + throw "Erreur lors de la recherche sur le LDAP."; + } else { + // Dès que la recherche renvoit une entrée, on stocke les attributs qui nous intéresse + res.on('searchEntry', entry => handler(entry)); + // Si la recherche renvoie une erreur, on renvoit + res.on('error', resErr => { throw resErr; }); + // Quand la recherche est finie on se déconnecte + res.on('end', _ => resolve()); + } + }); + }); + + return promise; + } + + /** + * @memberof LDAP + * @summary Fonction qui interroge le LDAP selon un protocole spécifié en argument et renvoit une liste de valeurs trouvées. + * @desc Cette fonction utilise {@link LDAP.search} directement. + * @arg {'gr'|'us'} domain - Emplacement de la requête (groupe ou utilisateur) + * @arg {string} attribute - Attribut unique à renvoyer + * @arg {string} id [null] - Identifiant facultatif pour une recherche triviale en o(1) + * @arg {string} filter ["(objectClass=*)"] - Filtre logique de la recherche (format [`RFC2254`](https://tools.ietf.org/search/rfc2254)) déjà passé au ldapEscape + * @return {Promise(string[])} Résultats de la recherche ; soit une liste de valeurs d'attributs, + * soit une liste de dictionnaires si on veut plus d'un attribut (les clés du dictionnaire sont celles du LDAP) + * @static + * @async + */ + static async searchSingle(domain: 'group'|'user', attribute: string, id: string=null, filter: string="(objectClass=*)") : Promise<string[]> { + let vals=[]; + await Basics.search(domain, [attribute], id, filter, entry => { + // Cas un seul attribut où le résultat est une liste directement + console.log("searchSingle found " + entry.object[(domain == 'group' ? ldapConfig['group']['gid'] : ldapConfig['user']['uid'])]); + vals.push(entry.object[attribute]); + }); + return vals; + } + + /** + * @memberof LDAP + * @summary Fonction qui interroge le LDAP selon un protocole spécifié en argument et renvoit les valeurs trouvées. + * @desc Cette fonction utilise ldapjs (voir [`Client API`](http://ldapjs.org/client.html) méthode search). Cette fonction fait une demande au LDAP + * qu'elle filtre selon un schéma prédéfini dans `filter` et à chaque résultat (event SearchEntry) le met dans une liste, et renvoit la liste à l'issue (event end). + * @arg {string} dn - DN de l'emplacement de la requête + * @arg {string} filter ["(objectClass=*)"] - Filtre logique de la recherche (format [`RFC2254`](https://tools.ietf.org/search/rfc2254)) déjà passé au ldapEscape + * @arg {string[]} attributes - Liste des attributs qui figureront dans le résultat final ; peut aussi être un seul élément + * @return {(Promise(Array.<Object>)|Promise(Array.Object.<string, Object>))} Résultats de la recherche ; soit une liste de valeurs d'attributs, + * soit une liste de dictionnaires si on veut plus d'un attribut (les clés du dictionnaire sont celles du LDAP) + * @static + * @async + */ + static async searchMultiple(domain: 'group'|'user', attributes: string[], id: string=null, filter: string="(objectClass=*)") : Promise<Array<dic>> { + let vals=[]; + await Basics.search(domain, attributes, id, filter, entry => { + // Cas plusieurs attributs donc résultat dictionnaire + vals.push({}); + console.log("searchMultiple found " + entry.object[(domain == 'group' ? ldapConfig['group']['gid'] : ldapConfig['user']['uid'])]); + attributes.forEach(attribute => vals.slice(-1)[0][attribute]=entry.object[attribute]); + }); + return vals; + } + + /** + * @memberof LDAP + * @summary Fonction qui permet de modifier un élément sur le LDAP. Gestion intelligente de l'appartenance à un binet. + * @desc Cette fonction traite la demande avec ldapjs (voir [`Client API`](http://ldapjs.org/client.html) méthode modify). + * @arg {'gr'|'us'} domain - Emplacement de la requête (groupe ou utilisateur) + * @arg {string} id - Identifiant unique de la feuille à modifier ; passé par ldapEscape dans cette fonction + * @arg {"add"|"del"|"replace"} op - Operation à réaliser sur le LDAP. Trois opération sont possibles ; "add", qui rajoute des attributs et qui peut créer des doublons, + * "del" qui en supprime, et "replace" qui remplace du contenu par un autre. + * @arg {Object.<string, string>} mod - Dictionnaire contenant les attributs à modifier et les nouvelles valeurs des attributs. + * @arg {Object} mod[key] - Nouvelle valeur de l'attribut key. Une nouvelle valeur vide ("") est équivalent à la suppression de cet attribut. + * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, `false` sinon. + * @static + * @async + */ + static async change(domain: 'group'|'user', id: string, op: "add"|"del"|"replace", mod: dic) : Promise<boolean> { + let dn = ""; + if (domain == 'group') dn += ldapConfig.group.gid; + else dn += ldapConfig.user.uid; + dn+='='+ldapEscape.dn("${txt}", { txt: id })+','+ldapConfig.dn[domain]; + // Modification LDAP selon dn fourni en argument (pourrait prendre une liste de Changes) + client.modify(ldapEscape.dn("${txt}", {txt: dn}), new ldap.Change({ + operation: ldapEscape.dn("${txt}", {txt: op}), + modification: mod, + // Gestion erreur + }), err => { throw "Erreur lors d'une opération de modification sur le LDAP."; }); + return true; + } + + /** + * @memberof LDAP + * @summary Fonction qui permet de rajouter un élément sur le LDAP. + * @desc Cette fonction traite la demande avec ldapjs (voir [`Client API`](http://ldapjs.org/client.html) méthode add). + * On notera le rôle particulier de vals[uid/gid] qui sert à identifier la feuille à changer ; passé par ldapEscape dans cette fonction. + * @arg {'gr'|'us'} domain - Emplacement de la requête (groupe ou utilisateur) + * @arg {Object.<string, string>} vals - Dictionnaire contenant les valeurs à créer (contient un champ en ldapConfig) + * @arg {Object} vals[key] - Nouvelle valeur pour le champ key + * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon. + * @static + * @async + */ + static async add(domain: 'group'|'user', vals) : Promise<boolean> { + let dn = ""; + if (domain == "group") dn += ldapConfig.group.gid+"="+ldapEscape.dn("${txt}", { txt: vals[ldapConfig.group.gid] }); + else dn += ldapConfig.user.uid+"="+ldapEscape.dn("${txt}", { txt: vals[ldapConfig.user.uid] }); + dn += ldapConfig.dn[domain]; + // Ajout LDAP selon la ldapConfiguration en argument + client.add(ldapEscape.dn("${txt}", { txt: dn }), vals, err => { + throw "Erreur lors d'une opération d'ajout sur le LDAP."; + }); + return true; + } + + /** + * @memberof LDAP + * @summary Fonction qui permet de supprimer une feuille du LDAP. + * @desc Cette fonction traite la demande avec ldapjs (voir [`Client API`](http://ldapjs.org/client.html) méthode del). + * Elle est différente de modify avec "del" car elle affecte directement une feuille et pas un attribut. + * @arg {'gr'|'us'} domain - Emplacement de la requête (groupe ou utilisateur) + * @arg {string} id - Identifiant unique de la cible, passé par ldapEscape dans cette fonction + * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon + * @static + * @async + */ + static async clear(domain: 'group'|'user', id: string) : Promise<boolean> { + let dn = ""; + if (domain == "group") dn += ldapConfig.group.gid+"="+ldapEscape.dn("${txt}", { txt: id }); + else dn += ldapConfig.user.uid+"="+ldapEscape.dn("${txt}", { txt: id }); + dn+=ldapConfig.dn[domain]; + // Suppression LDAP + client.del(ldapEscape.dn("${txt}", {txt: dn}), err => { + throw "Erreur lors d'une opération de suppression sur le LDAP."; + }); + return true; + } +} + +// Bind +Basics.unbind(); +Basics.adminBind(); + +console.log("Binding with LDAP client completed successfully, looking good !"); \ No newline at end of file diff --git a/src/ldap/internal/config.ts b/src/ldap/internal/config.ts new file mode 100644 index 0000000000000000000000000000000000000000..3b00495644370c4c05267190703c4a55afb2af8d --- /dev/null +++ b/src/ldap/internal/config.ts @@ -0,0 +1,121 @@ +/** + * Namespace qui regroupe toutes les fonctions en rapport avec le LDAP - API et fonctions internes + * @namespace LDAP + */ + +/** + * @file Importe la configuration du LDAP au sein de l'application, et remplace certaines valeurs en fonction des variables d'environnement. + * @author hawkspar + * @memberof LDAP + */ + +import fs from 'fs'; +import path from 'path'; +import colors from 'colors'; +import dotenv from 'dotenv'; + +// Chargement de l'environnement +let path_env = path.resolve(__dirname, '..', '..', '..', './.env'); +console.log(colors.red("Loading .env config file from "+path_env)); +dotenv.config({ path: path_env }); + +// Point central ; tous les champs de la BDD sont 'cachés' dans config.json et pas visibles directement +let path_config = path.resolve(__dirname, '..', '..', '..', './ldap_config.json'); +console.log(colors.cyan("Loading LDAP config file from "+path_config)); +export const ldapConfig = JSON.parse(fs.readFileSync(path_config).toString()); + +// Override config server from environment +if (process.env.LDAP_URI != null) ldapConfig.server = process.env.LDAP_URI; +else { + if (process.env.TARGET_ENV == `production`) ldapConfig.server = ldapConfig.server_prod; + else ldapConfig.server = ldapConfig.server_dev; +} + +// Gestion des super-identifiants +let path_credentials = path.resolve(__dirname, '..', '..', '..', 'ldap_credentials.json'); +console.log(colors.green("Loading LDAP credentials from "+path_credentials)); +export const credentialsLdapConfig = JSON.parse(fs.readFileSync(path_credentials).toString()); + +// Data formats and useful constants +export const categories = ["admins","speakers","members","followers"]; + +/** + * @memberof LDAP + * @class partUserData + * @desc Interface avec les données visibles au mponde extérieur. + * @var {string} uid - Identifiant utilisateur + * @var {string} givenName - Prénom + * @var {string} lastName - Nom + * @var {string?} gender - Sexe + * @var {string?} photo - Bytestring de la photo de l'utilisateur + * @var {string?} address - Adresse(s) + * @var {string[]} admins - Liste des gid (group id, inclus section sportive, binet, PA...) dont l'utilisateur est admin ; pas forcément sous-liste de members + * @var {string[]} speakers - Liste des gid dont l'utilisateur est porte-parole ; pas forcément sous-liste de members + * @var {string[]} members - Liste des gid dont l'utilisateur est membre + * @var {string[]} followers - Liste des gid dont l'utilisateur est sympathisant + */ +export class partUserData { + uid: string; + givenName: string; + lastName: string; + gender?: 'M'|'F'|'U'; + photo?: string; + address?: string; + admins: string[] = []; + speakers: string[] = []; + members: string[] = []; + followers: string[] = []; +} + +/** + * @memberof LDAP + * @class userData + * @desc Interface avec toutes les données extractables pour un utilisateur. + * @var {string?} password - Mot de passe (jamais transmis en clair) + * @var {string?} nickname - Surnom + * @var {string?} phone - Numéro(s) de téléphone + * @var {string?} mail - Adresse(s) courriel + * @var {string} birthdate - Date d'anniversaire + * @var {string} nationality - Nationalité d'origine + */ +export class userData extends partUserData { + nickname?: string; + password?: string; + phone?: string; + mail?: string; + birthdate: string; + nationality?: string; +} + +/** + * @memberof LDAP + * @class groupData + * @var {string} gid - Identifiant du groupe + * @var {string?} password - Mot de passe du groupe + * @var {string} name - Nom du groupe (souvent son nom mais pas nécessairement) + * @var {string?} logo - Logo du groupe (en bytestring) + * @var {string?} description - Description du groupe (script Markdown) + * @var {string?} site - Site web du groupe (URL) + * @var {string} category - Statut du groupe ; binet, section sportive... (actuellement juste 'binet' ou 'free') + * @var {string[]} parents - Liste des groupes directement parents de celui-ci + * @var {string[]} childs - Liste des groupes directement enfants de celui-ci + * @var {string[]} admins - Liste des admins du groupe + * @var {string[]} speakers - Liste des porte-parole du groupe + * @var {string[]} members - Liste des membres du groupe + * @var {string[]} followers - Liste des sympathisants du groupe + */ +export class groupData { + gid: string; + password?: string; + name: string; + logo?: string; + description?: string; + site?: string; + category: string; + parents: string[] = []; + //childs: string[] = []; + admins: string[] = []; + speakers: string[] = []; + members: string[] = []; + followers: string[] = []; +} \ No newline at end of file diff --git a/src/ldap/internal/tools.ts b/src/ldap/internal/tools.ts new file mode 100644 index 0000000000000000000000000000000000000000..cb5b6b6baf29c65d0d1d4c51ca6e838f27a2a20d --- /dev/null +++ b/src/ldap/internal/tools.ts @@ -0,0 +1,533 @@ +/** + * @file Ce fichier regroupe les fonctions génériques de recherche et de test utiles, mais trop puissantes pour être exportées directement. + * @author hawkspar + * @memberof LDAP + */ + +// Toutes les entrées utilisateur sont escapées par sécurité +import ldapEscape from 'ldap-escape'; +// Imports internes +import { ldapConfig, userData, groupData } from './config'; +import {Basics} from './basics'; + +//------------------------------------------------------------------------------------------------------------------------ +// Fonctions intermédiaires TBT +//------------------------------------------------------------------------------------------------------------------------ + +export class Tools { + /** + * @memberof LDAP + * @class Tools + * @classdesc Cette classe contient des fonctions intermédiaires qui ne sont pas destinées à être utilisées dans les resolvers. + * @summary Constructeur vide. + */ + constructor() {} + + /** + * @memberof LDAP + * @summary Fonction qui renvoit toutes les infos relatives à un groupe ou un utilisateur particulier. + * @desc Cette fonction utilise {@link LDAP.search} avec des attributs prédéfinis. Elle est naïve et n'opère pas de récursion. + * @param T - Format renvoyé (en pratique {@link userData} ou {@link groupData}) + * @arg {string} domain - Domaine de la recherche (utilisateur ou groupe) + * @arg {string} id - Identifiant de la feuille cherchée (uid ou gid) + * @return {Promise(T)} Informations recueillies ; renvoie une liste de dictionnaire avec le profil complet du groupe tel que défini par le paramètre T. + * @static + * @async + */ + static async peek<T>(domain: 'user'|'group', id: string, type: new () => T) : Promise<T> { + // Utrilisation du tableu d'équivalence dans le fichier de configuration + let map = ldapConfig[domain]; + let cleanKeys = Object.keys(map); + let dirtyKeys = cleanKeys.map(key => map[key]); + + // Création de la structure de données renvoyée + let cleanData: T = new type(); + let dirtyData = (await Basics.searchMultiple(domain, dirtyKeys, id))[0]; + // Renommage des clés + for(let cleanKey of cleanKeys) { + let val = dirtyData[map[cleanKey]]; + if (val !== undefined) { + if (Array.isArray(cleanData[cleanKey]) && !Array.isArray(val)) val = [val]; + cleanData[cleanKey] = val; + } + } + return cleanData; + } + + /** + * @memberof LDAP + * @summary Fonction qui retrouve les ids des paxs ou groupes validant les critères de recherche. Etape vers vrai TOL (Trombino On Line). + * Utiliser {@link peekUser} au cas par cas après pour obtenir les vraies infos. + * @desc Cette fonction utilise {@link LDAP.search} mais avec un filtre généré à la volée. Accepte des champs exacts ou incomplets pour la plupart des champs + * mais pas approximatifs et ne gère pas l'auto-complete. MEF Timeout pour des recherches trop vagues. Va crasher si un champ n'est pas dans ldapConfig. + * @param T - Format renvoyé (en pratique {@link userData} ou {@link groupData}) + * @arg {"us"|"gr"} domain - Domaine de la recherche (utilisateur ou groupe) + * @arg {userData | groupData} data - Dictionnaire contenant les données nécessaires à la recherche. Les valeurs sont celles entrées par l'utilisateur et sont par hypothèse + * comme des sous-parties compactes des valeurs renvoyées. Tous les champs ci-dessous peuvent être indifféremment des listes (par exemple pour chercher un membre + * de plusieurs groupes) ou des éléments isolés. Si un champ n'est pas pertinent, le mettre à '' ou undefined. + * @return {Promise(string[])} ids des profils qui "match" les critères proposés. + * @static + * @async + */ + static async search(domain: "user"|"group", data: userData|groupData) : Promise<string[]> { + let filter=""; + // Iteration pour chaque champ, alourdissement du filtre selon des trucs prédéfinis dans ldapConfig encore + for (var key in ldapConfig[domain]) { + if ((data[key]!= undefined) && (data[key] != '')) { // Si il y a qque chose à chercher pour ce filtre + if (!Array.isArray(data[key])) data[key]=[data[key]]; // Génération systématique d'une liste de valeurs à rechercher + // Iteration pour chaque valeur fournie par l'utilisateur + data[key].forEach(val => { + // Traduction en language LDAP + let attribute = ""; + attribute = ldapConfig[domain][key]; + // Escape user input + val = ldapEscape.filter("${fil}", { fil: val }); + // Creation incrémentale du filtre + filter="(&"+filter+ "(|("+attribute+"="+ val+")"+ // On cherche la valeur exacte + "("+attribute+"=*"+val+")"+ // La valeur finale avec des trucs avant ; wildcard * (MEF la wildcart ne marche pas pour tous les attributs) + "("+attribute+"=*"+val+"*)"+ // La valeur du milieu avec des trucs avant et après + "("+ attribute+"="+ val+"*)))"; // La valeur du début avec des trucs après + }); + } + } + // Appel avec filtre de l'espace + if (domain == "group") var att=ldapConfig.group.gid; + else var att=ldapConfig.user.uid; + return Basics.searchSingle(domain, att, null, filter); + } + + /** + * @memberof LDAP + * @summary Fonction qui édite un groupe ou utilisateur existant dans le LDAP. N'agit pas sur l'apprtenance à un groupe. + * @desc Appelle {@link LDAP.change}. + * @arg {"us"|"gr"} domain - Domaine de l'opération' (utilisateur ou groupe). + * @arg {userData | groupData} data - Dictionnaire avec les nouvelles valeurs de la feuille. + * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon. + * @async + * @static + */ + static async edit(domain: "user"|"group", data: userData|groupData) : Promise<boolean> { + if (domain == "user") var id=data['uid']; + else var id=data['gid']; + var dirtyKeys=ldapConfig[domain]; + // Rename in an LDAP-friendly way + let dirtyData = {}; + Object.keys(data).forEach(function(key: string) { + // Some values edit can't change + if (![ 'admins','speakers','members','followers', + 'directory','classes','id','cleanFullName'].includes(key)) dirtyData[dirtyKeys.key]=data[key]; + }); + return Basics.change(domain, id, "replace", dirtyData); + } + + /** + * @memberof LDAP + * @summary Fonction qui retrouve les utilisateurs ou groupes respectivement correspondant à un groupe ou un utilisateur de la même catégorie. + * @desc Cette fonction utilise {@link LDAP.search} et va directement à la feuille de l'utilisateur ou du groupe interrogé. + * Pour autant, elle est moins naïve qu'elle en a l'air. Elle ne gère ni la descente des admins ni la remontée des membres et renvoit une réponse naïve. + * @param {string} id - Identifiant du groupe ou de l'individu à interroger (supposé valide) + * @param {"user"|"group"} domain - Arbre à interroger + * @param {"admins"|"speakers"|"members"|"followers"} category - Catégorie considérée + * @return {Promise(string[])} Liste des id de groupes ou d'utilisateurs de la bonne catégorie associé à l'id + * @static + * @async + */ + static async get(id : string, domain : "user"|"group", category : string): Promise<string[]> { + try { + if (domain == "group") var dirtyId = ldapConfig.group.gid; + else var dirtyId = ldapConfig.user.uid; + dirtyId += "="+ldapEscape.filter("${txt}", { txt: id }) + "," + ldapConfig.dn[domain]; + return await Basics.searchSingle(domain, ldapConfig[domain][category], dirtyId); + } + catch(err) { + throw "Erreur lors d'une recherche générique d'un membre d'une certaine catégorie d'un groupe."; + } + } + + /** + * @memberof LDAP + * @summary Fonction intermédiaire naïve. + * @desc Cette fonction rajoute un utilisateur à un groupe pour un des deux arbres si cette entrée n'était pas déjà présente de façon à ne pas créer de doublon. + * @arg {string} id1 - uid/gid + * @arg {"group"|"user"} domain1 - Arbre concerné pour l'id1 + * @arg {string} id2 - gid/uid + * @arg {"group"|"user"} domain2 - Arbre concerné pour l'id2 + * @arg {"admins"|"speakers"|"members"|"followers"} category - Categorie de l'utilisateur concerné (admin, speaker, member ou follower) + * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon + * @async + * @static + */ + static async addIfAbsent(id1: string, domain1: "group"|"user", id2: string, domain2: "group"|"user", category: string): Promise<boolean> { + try { + // Vérifie que l'utilisateur est pas déjà membre pour groupes + let l = await Tools.get(id1, domain1, category); + var catName = ldapConfig[domain1][category]; + if (!l.includes(id2)) { + // Ajoute l'utilisateur dans la catégorie concernée + if (domain2 == "group") var id = ldapConfig[domain2].gid; + else var id = ldapConfig[domain2].uid; + if (!await Basics.change(domain1, id1, "add", {catName: id+"="+id2+","+ldapConfig.dn[domain2]})) throw "Erreur lors de la modification dans l'arbre "+domain2+" pour ajouter une entrée dans la catégorie voulue."; + } + } + catch(err) { throw "Erreur pour obtenir une liste d'entrées d'une catégorie d'un "+domain1+"."; } + return true; + } + + /** + * @memberof LDAP + * @summary Fonction intermédiaire. + * @desc Cette fonction gère les inclusions de droits. Elle ne rajoute pas un admin pour les admins, mais elle le rajoute en tant que speaker. + * Cette fonction agit uniquement sur l'arbre User. + * @arg {string} uid - uid de l'utilisateur à ajouter + * @arg {string} gid - gid du groupe concerné + * @arg {"admins"|"speakers"|"members"|"followers"} category - Categorie de l'utilisateur concerné (admin, speaker, member ou follower) + * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon + * @async + * @static + */ + static async addIncluded(uid: string, gid: string, category: string): Promise<boolean> { + var spk = ldapConfig.group.speakers; + var mem = ldapConfig.group.members; + switch (category) { + case "admins": + // Admin => speaker + if (!await Basics.change("user", uid, "add", { spk: ldapConfig.group.gid+"="+gid+","+ldapConfig.dn.group })) throw "Erreur à l'ajout d'un nouvel admin parmi les portes-paroles d'un groupe"; + case "speakers": + // Speaker & admin => member + if (!await Basics.change("user", uid, "add", { mem: ldapConfig.group.gid+"="+gid+","+ldapConfig.dn.group })) throw "Erreur à l'ajout d'un nouvel admin ou porte-parole parmi les membres d'un groupe"; + case "members": + case "followers": + break; + } + return true; + } + + /** + * @memberof LDAP + * @summary Fonction intermédiaire de récursion. + * @desc Cette fonction gère les droits par récursion par une classique Depth First Search. + * Cette fonction agit uniquement sur l'arbre User. + * @arg {string} uid - uid de l'utilisateur à ajouter + * @arg {string} gid - gid du groupe concerné + * @arg {boolean} direction - direction de la recursion + * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon + * @async + * @static + */ + static async addDFS(uid: string, gid: string, direction: boolean): Promise<boolean> { + // Cas récursif ascendant (admins) + if (direction) { + var rol = ldapConfig.group.admins; + var tov = ldapConfig.group.childs; + } + // Cas récursif descendant (membres) + else { + var rol = ldapConfig.group.members; + var tov = ldapConfig.group.parents; + } + // Classic DFS + var to_visit = [gid]; + while (to_visit.length > 0) { + let cur_gid = to_visit.pop(); + Basics.change("user", uid, "add", { rol: ldapConfig.group.gid+"="+cur_gid+","+ldapConfig.dn.group }); + to_visit.concat(await Basics.searchSingle("group", tov, cur_gid)); + } + return true; + } + + /** + * @memberof LDAP + * @summary Fonction essentielle qui permet d'ajouter un utilisateur à une catégorie d'un groupe. + * @desc Cette fonction fait essentiellement appel à d'autres fonctions de {@link Tools} ; {@link Tools.addIfNotPresent} et {@link Tools.manageInclusions}. + * Puis elle gère en interne la récursion et utilise {@link LDAP.change} pour cela. + * @arg {string} uid - Identifiant du futur membre + * @arg {string} gid - Identifiant du groupe + * @arg {"admins"|"speakers"|"members"|"followers"} category - Categorie de l'utilisateur concerné (admin, speaker, member ou follower) + * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon + * @async + * @static + */ + static async add(uid: string, gid: string, category: string): Promise<boolean> { + // Gestion naïve + Tools.addIfAbsent(uid, "user", gid, "group", category); + Tools.addIfAbsent(gid, "group", uid, "user", category); + // Gestion des droits par inclusion + Tools.addIncluded(uid, gid, category); + // Gestion des droits récursive + if (category != "followers") Tools.addDFS(uid, gid, false); + if (category == "admins") Tools.addDFS(uid, gid, true); + return true; + } + + /** + * @memberof LDAP + * @summary Fonction intermédiaire naïve. + * @desc Cette fonction enlève un utilisateur d'un groupe pour un des deux arbres si cette entrée n'était pas déjà absente. + * Elle ne donne pas de précédence particulière à un administrateur strict, ou de protection à une administrateur hérité. + * @arg {string} id1 - uid/gid + * @arg {"group"|"user"} domain1 - Arbre concerné pour l'id1 + * @arg {string} id2 - gid/uid + * @arg {"group"|"user"} domain2 - Arbre concerné pour l'id2 + * @arg {"admins"|"speakers"|"members"|"followers"} category - Categorie de l'utilisateur concerné (admin, speaker, member ou follower) + * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon + * @async + * @static + */ + static async remIfPresent(id1: string, domain1: "group"|"user", id2: string, domain2: "group"|"user", category: string): Promise<boolean> { + try { + let l = await Tools.get(id1, domain1, category); + var catName = ldapConfig[domain1][category]; + // Vérifie que l'identifiant est bien présent + if (l.includes(id2)) { + // Supprime tous les identifiants + if (!await Basics.change(domain1, id1, "del", catName)) throw "Erreur lors de la suppression de tous les "+category+" de l'identifiant "+id1+"."; + // Les rajoute un par un, sauf pour le supprimé + l.forEach(id => { + if (id!=id2) { + if (domain2 == "group") var id_n = ldapConfig[domain2].gid; + else var id_n = ldapConfig[domain2].uid; + var catName = ldapConfig[domain1][category]; + Basics.change(domain1, id1, "add", {catName: id_n+'='+id+','+ldapConfig[domain2].dn}).then(res => { + if (!res) throw "Erreur lors du ré-ajout d'un autre "+domain1+" de la catégorie "+category+"."; + }); + } + }); + } + } + catch(err) { throw "Erreur pour obtenir une liste d'entrées d'une catégorie d'un "+domain1+"."; } + return true; + } + + /** + * @memberof LDAP + * @summary Fonction intermédiaire. + * @desc Cette fonction gère les inclusions de droits. Elle ne rajoute pas un admin pour les admins, mais elle le rajoute en tant que speaker. + * Cette fonction agit uniquement sur l'arbre User. + * @arg {string} uid - uid de l'utilisateur à ajouter + * @arg {string} gid - gid du groupe concerné + * @arg {"admins"|"speakers"|"members"|"followers"} category - Categorie de l'utilisateur concerné (admin, speaker, member ou follower) + * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon + * @async + * @static + */ + static async remIncluded(uid: string, gid: string, category: string): Promise<boolean> { + var spk = ldapConfig.group.speakers; + var mem = ldapConfig.group.members; + switch (category) { + case "admins": + // Admin => speaker + let l = await Tools.get(gid, "group", spk); + // Supprime tous les identifiants + if (!await Basics.change("group", gid, "del", spk)) throw "Erreur lors de la suppression de tous les portes-paroles de l'identifiant "+gid+"."; + // Les rajoute un par un, sauf pour le supprimé + l.forEach(id => { + if (id!=uid) { + let tmp = ldapConfig.user.uid; + Basics.change("group", gid, "add", {spk: tmp+'='+uid+','+ldapConfig.user.dn}).then(res => { + if (!res) throw "Erreur lors du ré-ajout d'un autre membre de la catégorie porte-parole."; + }); + } + }); + case "speakers": + // Speaker & admin => member + let lm = await Tools.get(gid, "group", mem); + // Supprime tous les identifiants + if (!await Basics.change("group", gid, "del", mem)) throw "Erreur lors de la suppression de tous les portes-paroles de l'identifiant "+gid+"."; + // Les rajoute un par un, sauf pour le supprimé + l.forEach(id => { + if (id!=uid) { + let tmp = ldapConfig.user.uid; + Basics.change("group", gid, "add", {mem: tmp+'='+uid+','+ldapConfig.user.dn}).then(res => { + if (!res) throw "Erreur lors du ré-ajout d'un autre membre de la catégorie membre."; + }); + } + }); + case "members": + case "followers": + break; + } + return true; + } + + /** + * @memberof LDAP + * @summary Fonction intermédiaire de récursion. + * @desc Cette fonction gère les droits par récursion par une classique Depth First Search. + * Cette fonction agit uniquement sur l'arbre User. TBM + * @arg {string} uid - uid de l'utilisateur à ajouter + * @arg {string} gid - gid du groupe concerné + * @arg {boolean} direction - direction de la recursion + * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon + * @async + * @static + */ + static async remDFS(uid: string, gid: string, direction: boolean): Promise<boolean> { + // Cas récursif ascendant (admins) + if (direction) { + var rol = ldapConfig.group.admins; + var tov = ldapConfig.group.childs; + } + // Cas récursif descendant (membres) + else { + var rol = ldapConfig.group.members; + var tov = ldapConfig.group.parents; + } + // Classic DFS + var to_visit = [gid]; + while (to_visit.length > 0) { + let cur_gid = to_visit.pop(); + let l = await Tools.get(gid, "group", rol); + // Supprime tous les identifiants + if (!await Basics.change("group", gid, "del", rol)) throw "Erreur lors de la suppression de tous les "+rol+" de l'identifiant "+gid+"."; + // Les rajoute un par un, sauf pour le supprimé + l.forEach(id => { + if (id!=uid) { + let tmp = ldapConfig.user.uid; + Basics.change("group", gid, "add", {rol: tmp+'='+uid+','+ldapConfig.user.dn}).then(res => { + if (!res) throw "Erreur lors du ré-ajout d'un autre membre de la catégorie "+rol+"."; + }); + } + }); + to_visit.concat(await Basics.searchSingle("group", tov, cur_gid)); + } + return true; + } + + /** + * @memberof LDAP + * @summary Fonction qui permet de supprimer un membre d'une catégorie existant d'un groupe. + * @desc Cette fonction fait essentiellement appel à d'autres fonctions de {@link Tools} passées en argument et {@link LDAP.change}. + * Elle essaie d'assurer les propriétés d'inclusion et de récursion du LDAP. Elle est sans effet pour un statut hérité. + * @arg {string} uid - Identifiant de l'ex-membre + * @arg {string} gid - Identifiant du groupe + * @arg {"admins"|"speakers"|"members"|"followers"} category - Categorie de l'utilisateur concerné (admin, speaker, member ou follower) + * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon + * @async + * @static + */ + static async remove(uid: string, gid: string, category : string): Promise<boolean> { + // Invulnérabilité pour les admins hérités + if ((await Tools.get(gid, "group", category)).includes(uid)) { + Tools.remIfPresent(uid, "user", gid, "group", category); + Tools.remIfPresent(gid, "group", uid, "user", category); + // Gestion des droits par inclusion + Tools.remIncluded(uid, gid, category); + // Gestion des droits récursive + if (category != "followers") Tools.remDFS(uid, gid, false); + if (category == "admins") Tools.remDFS(uid, gid, true); + } + return true; + } + + /** + * @memberof LDAP + * @callback changeValueCallback + * @param {string} id - Id à modifier + * @param {number} n - Nombre d'itérations + * @return {string} Nouveau id + */ + /** + * @memberof LDAP + * @summary Cette fonction teste une valeur d'un attribut (typiquement un identifiant) et le fait évoluer jusqu'à ce qu'il soit unique. + * @desc Librement adapté de Stack Overflow. Appelle {@link LDAP.search} pour vérifier + * qu'il n'y a pas d'autres occurences de cette valeur pour cette attribut + * dans le dn fourni. + * @param {string} value - Valeur de l'attribut (le plus souvent un identifiant) à tester à cette itération + * @param {string} attribute - Attribut à tester + * @param {"gr"|"us"} domain - Domaine dans lequel l'attribut doit être unique + * @param {changeValueCallback} changeValue - Fonction qui prend uniquement en argument l'id courant et + * le nombre d'itérations et qui renvoit la prochaine valeur de l'attribut + * @param {number} n [0] - Nombre d'itérations (à initialiser à 0) + * @return {Promise(string)} Valeur unique dans le domaine spécifié de l'attribut spécifié + * @static + * @async + */ + static async ensureUnique(value: string, attribute: string, domain: 'group'|'user', changeValue: (string, number) => string, n: number=0) : Promise<string> { + // Recherche d'autres occurences de l'id + try { + if (domain == "group") var att=ldapConfig.group.gid; + else var att=ldapConfig.user.uid; + let matches = await Basics.searchSingle(domain, att, null, "("+attribute+"="+ldapEscape.filter("${txt}", { txt: value })+")") + // On renvoit la valeur si elle est bien unique + if (matches.length=0) return value; + // Sinon, on tente de nouveau notre chance avec la valeur suivante + else return Tools.ensureUnique(changeValue(value, n+1), attribute, domain, changeValue, n+1); + } + catch(err) { + throw "Erreur lors de la recherche d'une valeur pour assurer son unicité."; + } + } + + /** + * @memberof LDAP + * @summary Cette fonction génère un uid standard, puis le fait évoluer jusqu'à ce qu'il soit unique. + * @desc Limité à un appel à {@link Tools.ensureUnique} avec les bons paramètres, et quelques opérations sur l'uid pour qu'il soit valide (escape, normalisation). + * @param {"group"|"user"} domain - Arbre à parcourir + * @param {string} givenName - Prénom + * @param {string} lastName - Nom + * @param {string} promotion - Année de promotion + * @return {Promise(string)} Valeur unique dans le domaine spécifié de l'attribut spécifié + * @static + * @async + */ + static async generateUid(domain : "group"|"user", givenName: string, lastName: string, promotion: string) : Promise<string> { + try { + if (domain == "group") var att=ldapConfig.group.gid; + else var att=ldapConfig.user.uid; + // normalize et lowerCase standardisent le format + return Tools.ensureUnique((givenName+'.'+lastName).toLowerCase().normalize('UFD'), att, "user", (id: string, n: number) => { + if (n=1) id+='.'+promotion; // Si prénom.nom existe déjà , on rajoute la promo + else if (n=2) id+='.'+(n-1).toString(); // Puis si prénom.nom.promo existe déjà on passe à nom.prenom.promo .1 + else if (n>2) id+=n; // Ensuite on continue .123, .1234, etc... + return id; + }); + } + catch(err) { + throw "Erreur lors de l'assurance de l'unicité d'un human readable unique identifier (hruid)."; + } + } + + /** + * @memberof LDAP + * @summary Cette fonction génère un id lisible, puis le fait évoluer jusqu'à ce qu'il soit unique. + * @desc Limité à un appel à {@link Tools.ensureUnique} avec les bons paramètres, et quelques opérations sur l'uid pour qu'il soit valide (escape, normalisation). + * @param {"group"|"user"} domain - Arbre à parcourir + * @param {string} name - Nom + * @return {Promise(string)} Valeur unique dans le domaine spécifié de l'attribut spécifié + * @static + * @async + */ + static async generateReadableId(domain : "group"|"user", name: string) : Promise<string> { + try { + if (domain == "group") var att=ldapConfig.group.gid; + else var att=ldapConfig.user.uid; + // normalize et lowerCase standardisent le format + return Tools.ensureUnique(name.toLowerCase().normalize('UFD'), att, domain, (id: string, n: number) => { + if (n=1) id+='.'+n.toString(); // Si nom existe déjà , on essaie nom.1 + else if (n>1) id+=n.toString(); // Ensuite on continue .12, .123, etc... + return id; + }); + } + catch(err) { + throw "Erreur lors de l'assurance de l'unicité d'un human readable unique identifier (hruid)."; + } + } + + /** + * @memberof LDAP + * @summary Cette fonction teste une valeur dummy (0) pour un identifiant numérique puis le fait évoluer aléatoirement (entre 1 et 100 000) jusqu'à ce qu'il soit unique. + * @param {string} attribute - Intitulé exact de l'id concerné + * @param {"gr"|"us"} domain - Domaine dans lequel l'attribut doit être unique + * @return {Promise(string)} Valeur unique dans le domaine spécifié de l'attribut spécifié + * @static + * @async + */ + static async generateId(attribute: string, domain: "group"|"user") : Promise<string> { + try { + return Tools.ensureUnique("0", attribute, domain, (id,n) => Math.floor((Math.random() * 100000) + 1).toString()); + } + catch(err) { + throw "Erreur lors de l'assurance de l'unicité d'un unique identifier numérique."; + } + } +} \ No newline at end of file diff --git a/src/ldap/test.js b/src/ldap/test.js new file mode 100644 index 0000000000000000000000000000000000000000..832f1fee2566b21bbcba60cf0552242b64903ad0 --- /dev/null +++ b/src/ldap/test.js @@ -0,0 +1,6 @@ +var Group = require("../../tsbuild/src/ldap/export/group").Group; +console.log(Group); + +Group.peek("faerix").then(dat => { console.log(dat); }); + +var User = require("../../tsbuild/src/ldap/export/group"); \ No newline at end of file diff --git a/src/ldap/users.ts b/src/ldap/users.ts deleted file mode 100644 index f8fbab0e313d736e688dbd26b15a059c4619096e..0000000000000000000000000000000000000000 --- a/src/ldap/users.ts +++ /dev/null @@ -1,416 +0,0 @@ -/** - * @file Ce fichier regroupe les différentes classes avec différents utilisateurs. Ces classes sont dédiées à être exportées directement pour être utilisées par le solver. - * Le découpage par fichier est arbitraire mais permet de regrouper certaines classes proches. - * @author hawkspar - */ - -import { ldapConfig } from './config'; -import {LDAP} from './basics'; -import {searchUserFields, SmartSearch, Tests} from './utilities'; -import {Admin, Supervisor} from './admins'; -import ldap from 'ldapjs'; - -/** - * @const groupDataTemplate - * @var {string} name - Nom du groupe - * @var {string} ns - Statut du groupe ; 'binet' ou 'free', cà d ouvert à tous - * @var {string[]} members - Liste des membres du groupe - * @var {string[]} admins - Liste des admins du groupe ; supposée être une sous-liste de la précédente - */ -let groupDataTemplate = {}; -for (let key in ldapConfig.group.single) { groupDataTemplate[key] = ""; } -for (let key in ldapConfig.group.multiple) { groupDataTemplate[key] = [""]; } - -/** - * @type groupData - * @summary Interface sur le modèle de {@link groupDataTemplate} - */ -export type groupData = typeof groupDataTemplate; - -/** - * @interface userData - * @desc Interface avec toutes les données extractables pour un utilisateur. - * @var {string} givenName - Prénom - * @var {string} lastName - Nom - * @var {string} nickname - Surnom - * @var {string} photo - Bytestring de la photo de l'utilisateur - * @var {string} birthdate - Date d'anniversaire - * @var {string} promotion - Année(s) de promo - * @var {string} phone - Numéro(s) de téléphone - * @var {string} mail - Adresse(s) courriel - * @var {string} ip - Adresse(s) ip - * @var {string} adress - Adresse(s) - * @var {string} groups - Un ou plusieurs groupes dont l'utilisateur est membre (inclus section sportive, binet, PA...) - * @var {string} password - Mot de passe généré en amont - * @arg {string} readPerm - Permissions spéciales BR - * @var {string} writePerm - Permissions spéciales BR - * @var {string[]} forlifes - Alias BR (attention le filtre .fkz n'est plus fonctionnel) - * @var {string[]} groupsIsAdmin - Liste des gid dont le pax est admin ; supposé sous-liste de groups - */ -let userDataTemplate = {}; -for (let key in ldapConfig.user.single) { userDataTemplate[key] = ""; } -for (let key in ldapConfig.user.multiple) { userDataTemplate[key] = [""]; } - -/** - * @type groupData - * @summary Interface sur le modèle de {@link userDataTemplate} - */ -export type userData = typeof userDataTemplate; - -//------------------------------------------------------------------------------------------------------------------------ -// Classes à exporter TBT -//------------------------------------------------------------------------------------------------------------------------ - -export class Open { - /** - * @class Cette classe est la classe exportable de base permettant à un utilisateur non connecté de faire des petites recherches simples. - * @summary Constructeur vide. - */ - constructor() {} - - //------------------------------------------------------------------------------------------------------------------------ - // Fonctions de lecture - //------------------------------------------------------------------------------------------------------------------------ - /** - * @summary Fonction qui retrouve les groupes dont un individu est membre. - * @desc Cette fonction utilise {@link LDAP.search} va directement à la feuille de l'utilisateur. - * @arg {string} uid - Identifiant de l'individu à interroger (le plus souvent prenom.nom, parfois l'année, supposé valide) - * @return {Promise(string[])} Liste des uid de groupes (noms flat des groupes) où l'id fourni est membre - * @static - * @async - */ - static async getGroups(uid: string) { - try { - return LDAP.search(ldapConfig.key_id+uid+ldapConfig.dn_users, ldapConfig.user.groups)[0]; - } - catch(err) { - throw "Erreur lors de la recherche des groupes d'un individu."; - } - } - - /** - * @summary Fonction qui retrouve la liste des membres d'un groupe. - * @desc Cette fonction utilise {@link LDAP.search} avec un dictionnaire prédéfini dans ldapConfig.json. - * @arg {string} gid - Identifiant du groupe à interroger (le plus souvent nom du groupe en minuscule) - * @return {Promise(String[])} Liste des uid des membres où l'id fournie est membre (noms flat des groupes) - * @static - * @async - */ - static async getMembers(gid: string) { - try { - return LDAP.search(ldapConfig.key_id+gid+ldapConfig.dn_users, ldapConfig.group.member)[0]; - } - catch(err) { - throw "Erreur lors de la recherche des membres d'un groupe."; - } - } - - /** - * @summary Fonction qui retrouve la liste des admins d'un groupe. - * @desc Cette fonction utilise {@link LDAP.search} avec un dictionnaire prédéfini dans ldapConfig.json. - * @arg {string} gid - Identifiant du groupe à interroger (le plus souvent nom du groupe en minuscule) - * @return {Promise(string[])} Liste des uid des membres où l'id fournie est membre (noms flat des groupes) - * @static - * @async - */ - static async getAdmins(gid: string) { - try { - return LDAP.search(ldapConfig.key_id+gid+ldapConfig.dn_users, ldapConfig.group.admin)[0]; - } - catch(err) { - throw "Erreur lors de la recherche des admins d'un groupe."; - } - } - - /** - * @summary Cette fonction teste si un utilisateur est membre d'un groupe. - * @desc Utilise les méthodes statiques {@link open.getGroups} et {@link open.getMembers} - * @param {string} uid - Identifiant de l'utilisateur à tester - * @param {string} gid - Identification du groupe à tester - * @returns {Promise(boolean)} True si l'utilisateur est membre - * @static - * @async - */ - static async isGroupMember(uid: string, gid: string) { - try { - let lg = await this.getGroups(uid); - let lm = await this.getMembers(gid); - if (lg.includes(gid) && lm.includes(uid)) { - return true; - } - } - catch(err) { - throw "Erreur lors du test d'appartenance à un groupe."; - } - } - - /** - * @summary Cette fonction teste si un utilisateur est admin d'un groupe. - * @desc Utilise la méthode statique {@link Open.getAdmins} - * @param {string} uid - Identifiant de l'utilisateur à tester - * @param {string} gid - Identification du groupe à tester - * @returns {Promise(boolean)} True si l'utilisateur est administrateur - * @static - * @async - */ - static async isGroupAdmin(uid: string, gid: string) { - try { - let la = await this.getAdmins(gid); - if (la.includes(uid)) { - return true; - } - } - catch(err) { - throw "Erreur lors du test d'appartenance au bureau d'administration un groupe."; - } - } - - /** - * @summary Fonction qui renvoit toutes les infos relatives à un utilisateur particulier. - * @desc Cette fonction utilise {@link LDAP.search} avec des attributs prédéfinis. - * @arg {string} uid - Identifiant de l'utilisateur - * @return {Promise(userData)} Informations recueillies ; renvoie une liste de dictionnaire avec le profil complet de l'utilisateur ; - * voir `ldap_ldapConfig.json`(..\..\ldap_ldapConfig.json) pour les clés exactes. - * @static - * @async - */ - static async peekUser(uid: string) : Promise<userData> { - try { - let fields = []; - fields.push(ldapConfig.user.single.values()); - fields.push(ldapConfig.user.multiple.values()); - let LDAPUserData = await LDAP.search(ldapConfig.key_id+'='+uid+','+ldapConfig.dn_users, fields); - let cleanUserData = userDataTemplate; - // Rename output - for (let uncleanKey in LDAPUserData) { - for (let cleanKey in cleanUserData) { - if (uncleanKey==ldapConfig.group.cleanKey) { cleanUserData[cleanKey] = LDAPUserData[uncleanKey]; } - } - } - return cleanUserData; - } - catch(err) { - throw "Erreur lors d'une recherche d'informations sur un individu."; - } - } - - /** - * @summary Fonction qui renvoit toutes les infos relatives à un groupe particulier. - * @desc Cette fonction utilise {@link LDAP.search} avec des attributs prédéfinis. - * @arg {string} gid - Identifiant du groupe - * @return {Promise(groupData)} Informations recueillies ; renvoie une liste de dictionnaire avec le profil complet du groupe ; - * voir `ldap_ldapConfig.json`(..\..\ldap_ldapConfig.json) pour les clés exactes. - * @static - * @async - */ - static async peekGroup(gid: string) : Promise<groupData> { - try { - let fields = []; - fields.push(ldapConfig.user.single.values()); - fields.push(ldapConfig.user.multiple.values()); - let LDAPGroupData = await LDAP.search(ldapConfig.key_id+'='+gid+','+ldapConfig.dn_groups, fields); - let cleanGroupData=groupDataTemplate; - // Rename output - for (let uncleanKey in LDAPGroupData) { - for (let cleanKey in cleanGroupData) { - if (uncleanKey==ldapConfig.group.cleanKey) { cleanGroupData[cleanKey] = LDAPGroupData[uncleanKey]; } - } - } - return cleanGroupData; - } - catch(err) { - throw "Erreur lors d'une recherche d'informations sur un groupe."; - } - } - - //------------------------------------------------------------------------------------------------------------------------ - // Fonctions de recherche - //------------------------------------------------------------------------------------------------------------------------ - - /** - * @summary Fonction qui retrouve le groupe qui ressemblent à l'input et qui correspond au type fourni. Etape 0 vers un vrai TOL (Trombino On Line). - * @desc Cette fonction utilise {@link SmartSearch.groups}. - * @arg {string} input - String entré par l'utilisateur qui ressemble au nom du groupe. - * @return {Promise(string[])} Liste des gid dont le nom ressemble à l'input. - * @static - * @async - */ - static async findGroups(input: string) : Promise<string[]> { - try { - // Trucs intelligents faits dans ./utilities - return SmartSearch.groups(input, ldapConfig.key_id); - } - catch(err) { - throw "Erreur lors de la recherche approximative d'un groupe."; - } - } - - /** - * @summary Fonction qui retrouve les uid des paxs validant les critères de recherche. Autre étape vers vrai TOL (Trombino On Line). Doit être préféré à repliquer TOL - * car moins gourmande envers le LDAP (utiliser {@link peekUser} au cas par cas après pour obtenir les vraies infos). - * @desc Cette fonction utilise {@link SmartSearch.users}. - * @arg {searchUserFields} data - Dictionnaire contenant les données nécessaires à {@link SmartSearch.groups} - * @return {Promise(string[])} gids des profils qui "match" les critères proposés. - * @static - * @async - */ - static async findUsers(data: searchUserFields) : Promise<string[]> { - try { - return SmartSearch.users(data, ldapConfig.key_id); - } - catch(err) { - throw "Erreur lors de la recherche approximative d'un utilisateur."; - } - } -} - -export class User extends Open { - /** - * @class Cette classe est la classe de l'utilisateur connecté qui peut déjà créer un groupe et changer son profil. - * Techniquement, c'est la première classe qui a vraiment besoin de méthodes dynamiques dans l'arborescence, puisque c'est à partir du niveau User - * qu'on peut commencer à vouloir tracer les actions de l'utilisateur. - * @summary Constructeur vide. - */ - constructor() { super(); } - - //------------------------------------------------------------------------------------------------------------------------ - // Fonction de création TBT - //------------------------------------------------------------------------------------------------------------------------ - - /** - * @summary Fonction qui créé un nouveau groupe dans le LDAP. - * @desc Cette fonction fait une utilisation massive d'eval pour anonymiser son code ; c'est mal et cela suppose que beaucoup de soins ont été pris lors de - * l'escape de ses paramètres. Appelle {@link LDAP.add} et {@link LDAP.change}, mais aussi {@link Admin.addMemberGroup} et {@link Admin.addAdminGroup} - * pour gérer les groupes du nouvel utilisateur. Attention une manip FOIREUSE est cachée dedans. - * @arg {groupData} data - Dictionnaire des informations utilisateurs (voir détail des champs dans ldapConfig.json) - * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon - * @async - * @static - */ - static async addGroup(data: groupData) : Promise<boolean> { - // Calcul d'un dictionnaire d'ajout - let vals = {}; - - // uid de base généré à partir du nom standardisé - try { - Tests.generateReadableId(data['name']).then(id => { - vals[ldapConfig.key_id]=id; - vals[ldapConfig.group.single['name']]=id; - }); - } - catch(err) { - throw "Erreur lors de la génération d'un hruid pour créer un nouveau groupe."; - } - - let gid = vals[ldapConfig.key_id]; - - // Ecriture de toutes les valeurs directement inscrites dans le LDAP (in pour input) - ldapConfig.group.single.forEach(key_att => vals[ldapConfig.group.single[key_att]]=data[key_att]); - - // Appel à la fonction de base - if (!await LDAP.add(ldapConfig.key_id+"="+gid+","+ldapConfig.dn_groups, vals)) { - throw "Erreur lors de la création d'une nouvelle feuille dans l'arbre des groupes."; - } - // Certains champs nécessitent de petits calculs - let vals2={}; - - // ?! - vals2[ldapConfig.group.single['password']] = ''; - - // Génération id aléatoire et test contre le LDAP - try { - Tests.generateId(ldapConfig.group.single["idNumber"], ldapConfig.dn_groups).then(id => { vals2[ldapConfig.group.single['idNumber']]=id; }); - } - catch(err) { - throw "Erreur lors de la génération d'un id numérique pour créer un nouveau groupe."; - } - // FOIREUX : Hypothèse sur la structure du reste des données mais évite un test.assurerUnicite à deux variables - vals2[ldapConfig.group.single['idNumber2']]=vals2[ldapConfig.group.single['idNumber']]; - - // Stockage machine ; dépend du prénom - vals2[ldapConfig.group.single['directory']] = '/hosting/groups/'+gid; - - // Code root - vals2[ldapConfig.group.single['cleanFullName']]=data['name'].replace(':', ';').toLowerCase().normalize('UFD'); - - // Adressage root - vals2[ldapConfig.group.single['login']] = "/sbin/nologin"; - - // Permissions BR - vals2[ldapConfig.group.single['readPerm']] = '!*'; - vals2[ldapConfig.group.single['writePerm']] = '!*'; - - // Inscription des valeurs calculées par effet de bord - if (!await LDAP.change(ldapConfig.key_id+"="+gid+","+ldapConfig.dn_groups, "add", vals2)) { - throw "Erreur lors de l'ajout des valeurs intelligentes du nouveau groupe."; - } - - ["posixAccount", "posixGroup", "brAccount"].forEach(cst => { - let vals3={}; - vals3[ldapConfig.group.multiple['class']]=cst; - LDAP.change(ldapConfig.key_id+"="+gid+","+ldapConfig.dn_groups, "add", vals3).then(res => { - if (!res) { throw "Erreur lors de l'ajout des valeurs constantes du nouveau groupe."; } - }); - }); - - // Utilisation des fonctions adaptées pour assurer la cohérence de l'ensemble - data['members'].forEach(uid => { - Admin.addGroupMember(uid, gid).then(res => { - if (!res) { throw "Erreur de l'ajout d'un membre au groupe."; } - }); - }); - data['admins'].forEach(uid => { - Admin.addGroupAdmin(uid, gid).then(res => { - if (!res) { throw "Erreur de l'ajout d'un admin au groupe."; } - }); - }); - - return true; - } - - //------------------------------------------------------------------------------------------------------------------------ - // Fonctions d'édition TBT - //------------------------------------------------------------------------------------------------------------------------ - - /** - * @summary Fonction qui édite un utilisateur existant dans le LDAP. Très similaire à {@link creerUtilisateur} - * @desc Appelle simplement {@link creerUtilisateur} et {@link supprimerUtilisateur} en godmode, plus {@link renseignerSurUtilisateur} pour les champs non fournis. - * Ce choix a pour conséquence que l'ordre du dictionnaire de correspondance dans ldap_ldapConfig est important. - * Une version "nerfée" de cette fonction est envisageable ; elle donne bcp de pouvoir à l'utilisateur. - * @arg {string} uid - Utilisateur à modifier (le plus souvent le même, mais root possible) - * @arg {userData} data - Dictionnaire des informations utilisateurs au même format que pour {@link creerUtilisateur} avec tous les champs optionnels ; - * Attention toutes les clés de cette entrée seront modifiées dans le LDAP ; les nouveaux résultats écrasant les précédents, sauf 'readPerm','writePerm','forlifes','ips','groups' et 'groupsIsAdmin' - * qui sont censurés pour cette fonction) - * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon - * @async - * @static - */ - static async editUser(uid : string, data : userData) : Promise<boolean> { - // Récupération des anciennes données - let profil = await Open.peekUser(uid); - try { - // Régénération du champ manquant dans profil - let lg = await Open.getGroups(uid); - profil['groupsIsAdmin']=[]; - lg.forEach(gid => { - Open.isGroupAdmin(uid, gid).then(res => { - if (res) { profil['groupsIsAdmin'].push(gid); } - }); - }); - // Surcharge des champs à modifier selon data - Object.keys(data).forEach(function(key: string) { - // Some fields the user cannot change (groups and groupsIsAdmin must be changed through addGroupMember and addGroupAdmin in Admin) - if (!['readPerm','writePerm','forlifes','ips','groups','groupsIsAdmin'].includes(key)) { profil[key]=data[key]; } - }); - // Modification propre par effet de bord - if (!(await Supervisor.delUser(uid) && await Supervisor.addUser(profil))) { - throw "Erreur dans la destruction/création du compte."; - } else { - return true; - } - } - catch(err) { - throw "Erreur lors de la modification des groupes où un utilisateur est admin."; - } - } -} \ No newline at end of file diff --git a/src/ldap/utilities.ts b/src/ldap/utilities.ts deleted file mode 100644 index 19b5606d3ab0a625e000656e11850e3518096d1f..0000000000000000000000000000000000000000 --- a/src/ldap/utilities.ts +++ /dev/null @@ -1,238 +0,0 @@ -/** - * @file Ce fichier regroupe les fonctions simples de recherche et de test utiles, mais trop puissantes pour être exportées directement. - * Le découpage par fichier est arbitraire mais permet de regrouper certaines classes proches. - * @author hawkspar - */ - -import {ldapConfig} from './config'; -import {LDAP} from './basics'; - -/** - * @interface searchUserFields - * @desc Interface permettant la recherche d'un utilisateur avec des champs incomplets. Plusieurs valeurs sont possibles pour le même champ. - * Aucun de ces champs n'est obligatoire, mais certains de ces champs doivent être exacts pour obtenir un bon résultat. - * @var {string|string[]} givenName - Prénom(s) - * @var {string|string[]} lastName - Nom(s) - * @var {string|string[]} nickname - Surnom(s) - * @var {string|string[]} nationality - Nationalité(s) (à implémenter) - * @var {string|string[]} promotion - Année(s) de promo - * @var {string|string[]} phone - Numéro(s) de téléphone - * @var {string|string[]} mail - Adresse(s) courriel - * @var {string|string[]} ip - Adresse(s) ip - * @var {string|string[]} adress - Adresse(s) - * @var {string} school - Ecole d'appartenance (instable, doit être exact) - * @var {string|string[]} groups - Un ou plusieurs groupes dont l'utilisateur est membre (doit être exact). - * @var {string} course - PA ou autre. Doit être exact. - */ -export interface searchUserFields { - givenName: string, - lastName: string, - nickname: string, - nationality: string, - promotion: string, - phone: string, - mail: string, - ip: string, - adress: string, - school: string, - groups: string[], - studies: string, - sport: string -} - -//------------------------------------------------------------------------------------------------------------------------ -// Fonctions de recherche -//------------------------------------------------------------------------------------------------------------------------ - -export class SmartSearch { - /** - * @class Cette classe contient des fonctions de recherche génériques trop puissantes pour être exportées tel quel. - * @summary Constructeur vide. - * @author hawkspar - */ - constructor() {} - - /** - * @summary Fonction qui interroge le LDAP et retrouve les groupes (voir LDAP) qui ressemblent - * à l'entrée. Etape 0 vers un vrai TOL (Trombino On Line). - * @desc Cette fonction utilise {@link LDAP.search} mais avec un filtre généré à la volée. - * Accepte des champs exacts ou incomplets mais pas approximatifs - * et ne gère pas l'auto-complete. Cette fonction utilise aussi ldapConfig.json. MEF Timeout pour - * des recherches trop vagues. Renvoit une liste d'uid. - * Elle utilise LDAPEscape pour éviter les injections. - * @arg {string} input - String entré par l'utilisateur qui ressemble au nom du groupe. - * @arg {string[]} return_attributes - Liste d'attributs à renvoyer dans le résultat final - * @return {Promise(string[])} Liste des uid de groupes dont le nom ressemble à l'input - * @static - * @async - */ - static async groups(input: string, return_attributes: string[]) : Promise<string[]> { - // Construction du filtre custom - let filter= "(|("+ldapConfig.key_id+"="+ input+")" + // On cherche la valeur exacte - "(|("+ldapConfig.key_id+"=*"+input+")" + // La valeur finale avec des trucs avant ; wildcard * - "(|("+ldapConfig.key_id+"=*"+input+"*)"+ // La valeur du milieu avec des trucs avant et après - "("+ ldapConfig.key_id+"="+ input+"*))))"; // La valeur du début avec des trucs après - - // Appel rechercheLDAP avec filtre de l'espace - try { - return LDAP.search(ldapConfig.dn_groups, return_attributes, filter); - } - catch(err) { - throw "Erreur lors de la recherche intelligente d'un groupe."; - } - } - - /** - * @summary Fonction qui renvoit les attributs demandés des paxs validant les critères de recherche. Première étape vers vrai TOL (Trombino On Line). - * @desc Cette fonction utilise {@link LDAP.search} mais avec un filtre généré à la volée. Accepte des champs exacts ou incomplets pour la plupart des champs - * mais pas approximatifs et ne gère pas l'auto-complete. MEF Timeout pour des recherches trop vagues. Elle utilise LDAPEscape pour éviter les injections. - * Utiliser trouverGroupesParTypes pour chaque champ relié à groups. - * @arg {searchUserFields} data - Dictionnaire contenant les données nécessaires à la recherche. Les valeurs sont celles entrées par l'utilisateur et sont par hypothèse - * comme des sous-parties compactes des valeurs renvoyées. Tous les champs ci-dessous peuvent être indifféremment des listes (par exempl pour chercher un membre - * de plusieurs groupes) ou des éléments isolés. Si un champ n'est pas pertinent, le mettre à '' ou undefined. - * @arg {string[]} return_attributes - Liste d'attributs à renvoyer dans le résultat final. - * @return {Promise(Object[])} Liste de dictionnaires de profils en cohérence avec l'input avec pour clés les attributs des profils. - * @static - * @async - */ - static async users(data: searchUserFields, return_attributes: string[]) : Promise<string[]> { - let filter=""; - // Iteration pour chaque champ, alourdissement du filtre selon des trucs prédéfinis dans ldapConfig encore - for (var key in data) { - if ((data[key]!= undefined) && (data[key] != '')) { // Si il y a qque chose à chercher pour ce filtre - if (!Array.isArray(data[key])) { data[key]=[data[key]]; } // Gestion d'une liste de valeurs à rechercher - // Iteration pour chaque valeur fournie par l'utilisateur - data[key].forEach(val => { - // Traduction en language LDAP - let attribute = ldapConfig.user[key]; - // Creation incrémentale du filtre - filter="(&"+filter+ "(|("+attribute+"="+ val+")"+ // On cherche la valeur exacte - "(|("+attribute+"=*"+val+")"+ // La valeur finale avec des trucs avant ; wildcard * (MEF la wildcart ne marche pas pour tous les attributs) - "(|("+attribute+"=*"+val+"*)"+ // La valeur du milieu avec des trucs avant et après - "("+ attribute+"="+ val+"*)))))"; // La valeur du début avec des trucs après - }); - } - } - // Appel avec filtre de l'espace - try { - return LDAP.search(ldapConfig.dn_users, return_attributes, filter); - } - catch(err) { - throw "Erreur lors de la recherche intelligente d'un utilisateur."; - } - } -} - -//------------------------------------------------------------------------------------------------------------------------ -// Fonctions intermédiaires TBT -//------------------------------------------------------------------------------------------------------------------------ - -export class Tests { - /** - * @class Cette classe contient des fonctions de test d'unicité trop puissantes pour être exportées tel quel. - * @summary Constructeur vide. - * @author hawkspar - */ - constructor() {} - - /** - * @callback changeValueCallback - * @param {string} id - Id à modifier - * @param {number} n - Nombre d'itérations - * @return {string} Nouveau id - */ - /** - * @summary Cette fonction teste une valeur d'un attribut (typiquement un identifiant) et le fait évoluer jusqu'à ce qu'il soit unique. - * @desc Librement adapté de Stack Overflow. Appelle {@link LDAP.search} pour vérifier - * qu'il n'y a pas d'autres occurences de cette valeur pour cette attribut - * dans le dn fourni. - * @param {string} value - Valeur de l'attribut (le plus souvent un identifiant) à tester à cette itération - * @param {string} attribute - Attribut à tester - * @param {string} dn - *Domain Name* dans lequel l'attribut doit être unique - * @param {changeValueCallback} changeValue - Fonction qui prend uniquement en argument l'id courant et - * le nombre d'itérations et qui renvoit la prochaine valeur de l'attribut - * @param {int} n [0] - Nombre d'itérations (à initialiser à 0) - * @return {Promise(string)} Valeur unique dans le domaine spécifié de l'attribut spécifié - * @static - * @async - */ - static async ensureUnique(value: string, attribute: string, dn: string, changeValue: (string, number) => string, n=0) : Promise<string> { - // Recherche d'autres occurences de l'id - try { - return LDAP.search(dn, ldapConfig.key_id, "("+attribute+"="+value+")").then(function (matches: string[]) { - if (!matches) { throw ""; } - // On renvoit la valeur si elle est bien unique - else if (matches.length==0) { return value; } - // Sinon, on tente de nouveau notre chance avec la valeur suivante - else { return Tests.ensureUnique(changeValue(value, n+1), attribute, dn, changeValue, n+1); } - }); - } - catch(err) { - throw "Erreur lors de la recherche d'une valeur pour assurer son unicité."; - } - } - - /** - * @summary Cette fonction génère un uid standard, puis le fait évoluer jusqu'à ce qu'il soit unique. - * @desc Limité à un appel à {@link Tests.ensureUnique} avec les bons paramètres, et quelques opérations sur l'uid pour qu'il soit valide (escape, normalisation). - * @param {string} givenName - Prénom - * @param {string} lastName - Nom - * @param {string} promotion - Année de promotion - * @return {Promise(string)} Valeur unique dans le domaine spécifié de l'attribut spécifié - * @static - * @async - */ - static async generateUid(givenName: string, lastName: string, promotion: string) : Promise<string> { - try { - // normalize et lowerCase standardisent le format - return this.ensureUnique((givenName+'.'+lastName).toLowerCase().normalize('UFD'), ldapConfig.key_id, ldapConfig.dn_users, (id: string, n: number) => { - if (n==1) { id+='.'+promotion; } // Si prénom.nom existe déjà , on rajoute la promo - else if (n==2) { id+='.'+(n-1).toString(); } // Puis si prénom.nom.promo existe déjà on passe à nom.prenom.promo .1 - else if (n>2) { id+=n; } // Ensuite on continue .123, .1234, etc... - return id; - }); - } - catch(err) { - throw "Erreur lors de l'assurance de l'unicité d'un human readable unique identifier (hruid)."; - } - } - - /** - * @summary Cette fonction génère un id lisible, puis le fait évoluer jusqu'à ce qu'il soit unique. - * @desc Limité à un appel à {@link Tests.ensureUnique} avec les bons paramètres, et quelques opérations sur l'uid pour qu'il soit valide (escape, normalisation). - * @param {string} name - Nom - * @return {Promise(string)} Valeur unique dans le domaine spécifié de l'attribut spécifié - * @static - * @async - */ - static async generateReadableId(name: string) : Promise<string> { - try { - // normalize et lowerCase standardisent le format - return this.ensureUnique(name.toLowerCase().normalize('UFD'), ldapConfig.key_id, ldapConfig.dn_groups, (id: string, n: number) => { - if (n==1) { id+='.'+n.toString(); } // Si nom existe déjà , on essaie nom.1 - else if (n>1) { id+=n.toString(); } // Ensuite on continue .12, .123, etc... - return id; - }); - } - catch(err) { - throw "Erreur lors de l'assurance de l'unicité d'un human readable unique identifier (hruid)."; - } - } - - /** - * @summary Cette fonction teste une valeur dummy (0) pour un identifiant numérique puis le fait évoluer aléatoirement (entre 1 et 100 000) jusqu'à ce qu'il soit unique. - * @param {string} attribut - Intitulé exact de l'id concerné - * @param {string} dn - *Domain Name* dans lequel l'attribut doit être unique - * @return {Promise(string)} Valeur unique dans le domaine spécifié de l'attribut spécifié - * @static - * @async - */ - static async generateId(attribut: string, dn: string) : Promise<string> { - try { - return this.ensureUnique("0", attribut, dn, (id,n) => { return Math.floor((Math.random() * 100000) + 1).toString(); }); - } - catch(err) { - throw "Erreur lors de l'assurance de l'unicité d'un unique identifier numérique."; - } - } -} \ No newline at end of file diff --git a/test/resolvers/data/onplatal.ts b/test/resolvers/data/onplatal.ts new file mode 100644 index 0000000000000000000000000000000000000000..d1a3fdfcbaf2d7b755c28ae0195a29fb580b4b8d --- /dev/null +++ b/test/resolvers/data/onplatal.ts @@ -0,0 +1,41 @@ +/** + * @file Ce fichier charge tous les tests sans authentification + * @author ofacklam + */ + +import fs from 'fs'; +import path from 'path'; +import { TestCollection, TestSet } from "../testData"; +import { AuthorizationModel } from "../../../src/graphql/models/authorization"; +import { UserModel } from "../../../src/graphql/models/userModel"; +import { GroupModel } from "../../../src/graphql/models/groupModel"; +import { MessageModel } from "../../../src/graphql/models/messageModel"; +import { RequestModel } from "../../../src/graphql/models/requestModel"; + +let uid = AuthorizationModel.ONPLATALUSER; + +const tests : TestCollection = { + description: "On-platal user", + context : async ({ req }) => { + return { + request: req, + user: { uid: uid }, + models: { + auth: await AuthorizationModel.create(uid), + user: new UserModel(uid), + group: new GroupModel(uid), + message: new MessageModel(uid), + request: new RequestModel(uid) + } + }; + }, + tests : [] +} + +let dir = path.resolve(__dirname, './onplatal') +fs.readdirSync(dir).forEach(file => { + const testSet : TestSet = require(path.resolve(dir, file)).tests; + tests.tests.push(testSet); +}); + +export {tests}; \ No newline at end of file diff --git a/test/resolvers/data/onplatal/groups.ts b/test/resolvers/data/onplatal/groups.ts new file mode 100644 index 0000000000000000000000000000000000000000..7682bb21546467311a954c6fab83e52935210200 --- /dev/null +++ b/test/resolvers/data/onplatal/groups.ts @@ -0,0 +1,23 @@ +/** + * @file Ce fichier contient les tests des resolvers des groupes + * @author ofacklam +*/ + +import { TestSet } from '../../testData'; + +export const tests : TestSet = { + description: "Group type resolvers", + tests: [ + { + description: "group(gid)", + query: ` + query { + group(gid:"br") { + gid + } + } + `, + result: { "data": { "group": { "gid": "br" } } } + } + ] +} diff --git a/test/resolvers/data/onplatal/users.ts b/test/resolvers/data/onplatal/users.ts new file mode 100644 index 0000000000000000000000000000000000000000..4dc55f1eee01cb696029595364a382bd48567c92 --- /dev/null +++ b/test/resolvers/data/onplatal/users.ts @@ -0,0 +1,150 @@ +/** + * @file Ce fichier contient les tests des resolvers des users + * @author ofacklam +*/ + +import { TestSet } from '../../testData'; + +export const tests: TestSet = { + description: "User type resolvers", + tests: [ + { + description: "user(uid)", + query: ` + query { + user(uid: "oliver.facklam") { + uid + } + } + `, + result: { + "data": { + "user": { + "uid": "oliver.facklam" + } + } + } + }, + + { + description: "searchTOL(givenName)", + query: ` + query { + searchTOL(givenName: "Olivér") { + uid + } + } + `, + result: { + "data": { + "searchTOL": [ + { + "uid": "oliver.facklam" + } + ] + } + } + }, + + { + description: "searchTOL(lastName) partial", + query: ` + query { + searchTOL(lastName: "Fack") { + uid + } + } + `, + result: { + "data": { + "searchTOL": [ + { + "uid": "hugues.asofa-zeufack" + }, + { + "uid": "fabrice.zapfack" + }, + { + "uid": "jordan.lekeufack-sopze" + }, + { + "uid": "oliver.facklam" + } + ] + } + } + }, + + /*{ + description: "searchTOL(groups) triclassés", + query: ` + query { + searchTOL(groups: ["br", "faerix", "jtx"]) { + uid + } + } + `, + result: { + "data": { + "searchTOL": [ + { + "uid": "dubut.2003" + }, + { + "uid": "brodard.2004" + }, + { + "uid": "le-floch.2004" + }, + { + "uid": "rabie.2004" + }, + { + "uid": "pierre-etienne.marx" + }, + { + "uid": "lauriane.aufrant" + }, + { + "uid": "matthias.bry" + }, + { + "uid": "julia.chatain" + }, + { + "uid": "victor.berger" + }, + { + "uid": "augustin.lenormand" + }, + { + "uid": "timothee.darcet" + } + ] + } + } + },*/ + + { + description: "firstName, lastName, nickName", + query: ` + query { + user(uid: "oliver.facklam") { + givenName + lastName + nickname + } + } + `, + result: { + "data": { + "user": { + "givenName": "Olivér", + "lastName": "Facklam", + "nickname": null + } + } + } + }, + ] +} \ No newline at end of file diff --git a/test/resolvers/data/unauthenticated.ts b/test/resolvers/data/unauthenticated.ts new file mode 100644 index 0000000000000000000000000000000000000000..a606551678b0697004a461e6630797b37154b0e2 --- /dev/null +++ b/test/resolvers/data/unauthenticated.ts @@ -0,0 +1,41 @@ +/** + * @file Ce fichier charge tous les tests sans authentification + * @author ofacklam + */ + +import fs from 'fs'; +import path from 'path'; +import { TestCollection, TestSet } from "../testData"; +import { AuthorizationModel } from "../../../src/graphql/models/authorization"; +import { UserModel } from "../../../src/graphql/models/userModel"; +import { GroupModel } from "../../../src/graphql/models/groupModel"; +import { MessageModel } from "../../../src/graphql/models/messageModel"; +import { RequestModel } from "../../../src/graphql/models/requestModel"; + +let uid = AuthorizationModel.PUBLICUSER; + +const tests : TestCollection = { + description: "Public user", + context : async ({ req }) => { + return { + request: req, + user: { uid: uid }, + models: { + auth: await AuthorizationModel.create(uid), + user: new UserModel(uid), + group: new GroupModel(uid), + message: new MessageModel(uid), + request: new RequestModel(uid) + } + }; + }, + tests : [] +} + +let dir = path.resolve(__dirname, './unauthenticated') +fs.readdirSync(dir).forEach(file => { + const testSet : TestSet = require(path.resolve(dir, file)).tests; + tests.tests.push(testSet); +}); + +export {tests}; \ No newline at end of file diff --git a/test/resolvers/data/unauthenticated/users.ts b/test/resolvers/data/unauthenticated/users.ts new file mode 100644 index 0000000000000000000000000000000000000000..9321a578b3ee10bb7c7b01bb399719244273adba --- /dev/null +++ b/test/resolvers/data/unauthenticated/users.ts @@ -0,0 +1,44 @@ +/** + * @file Ce fichier contient les tests des resolvers des users + * @author ofacklam +*/ + +import { TestSet } from '../../testData'; + +export const tests: TestSet = { + description: "User type resolvers", + tests: [ + { + description: "user(uid) not authorized", + query: ` + query { + user(uid: "oliver.facklam") { + uid + } + } + `, + result: { + "data": { + "user": null + } + } + }, + + { + description: "searchTOL() not authorized", + query: ` + query { + searchTOL(givenName: "Olivér") { + uid + } + } + `, + result: { + "data": { + "searchTOL": null + } + } + } + ] +} + diff --git a/test/resolvers/resolvers.test.ts b/test/resolvers/resolvers.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..ae4b159139c45e6b4eb48cd7fd523ccc3e3c08dc --- /dev/null +++ b/test/resolvers/resolvers.test.ts @@ -0,0 +1,63 @@ +/** + * @file Ce fichier charge et execute tous les tests sur les resolvers + * @author ofacklam + */ + +import { expect } from 'chai'; +import fs from 'fs'; +import path from 'path'; +import { ApolloServer } from 'apollo-server-express'; +import { createTestClient } from 'apollo-server-testing'; + +//TODO : Set up a mock LDAP server (LDAP_URI = ... or TARGET_ENV=testing) + +import schema from '../../src/graphql/schema'; +import { TestCollection } from './testData'; + +const dir = path.resolve(__dirname, './data'); + +describe("GraphQL resolvers", function() { + fs.readdirSync(dir, {withFileTypes:true}) + .filter(item => !item.isDirectory()) + .forEach(file => { + const testCol : TestCollection = require(path.resolve(dir, file.name)).tests; + + describe(testCol.description, function() { + //contruct context and apollo server + const server = new ApolloServer({ + ...schema, + context: testCol.context + }); + const {query} = createTestClient(server); + + for (let testSet of testCol.tests) { + describe(testSet.description, function() { + for(let t of testSet.tests) { + it(t.description, async function() { + //silence the console + console.log = function () { }; + + //query + let res = await query({query: t.query}); + + //reset console + delete console.log; + + let final = {}; + final['data'] = res.data; + //if(res.errors !== undefined) final['errors'] = res.errors; + expect(final).to.deep.equal(t.result); + }) + } + }) + } + }) + }) + + beforeEach(function () { + + }); + afterEach(function () { + + }); +}) \ No newline at end of file diff --git a/test/resolvers/testData.d.ts b/test/resolvers/testData.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..b8a75520bb36fc0ee06ae7dd40947ae4009cab0a --- /dev/null +++ b/test/resolvers/testData.d.ts @@ -0,0 +1,23 @@ +/** + * @file Ce fichier définit le type de base pour les tests + * @author ofacklam +*/ + +import { Context } from "../../src/graphql/typeDefs/queries"; + +interface Test { + description : string, + query : string, + result +} + +interface TestSet { + description : string, + tests : Test[] +} + +interface TestCollection { + description : string, + context : ({req:any}) => Promise<Context>, + tests : TestSet[] +} \ No newline at end of file diff --git a/test/test.js b/test/test.js new file mode 100644 index 0000000000000000000000000000000000000000..f56440ecafc710c1d2bdaa484fe07415dca8e68c --- /dev/null +++ b/test/test.js @@ -0,0 +1,80 @@ +require('dotenv').config(); + +import { credentialsLdapConfig as credentialsConfig } from '../src/ldap/internal/config'; + +process.env.PORT = process.env.TEST_PORT || 3001; +// The port is changed so that the test suite can be ran without interfering with a running server + +require('../build/bundle'); + +const chai = require('chai'); + +const expect = chai.expect; + +const { GraphQLClient } = require('graphql-request'); + +const apiEndpoint = `http://localhost:${process.env.PORT}/graphql`; + +const auth = { + username : credentialsConfig.dn.split("=")[1].split(",")[0], + password : credentialsConfig.passwd +}; + +const testList = require('./testData').testList; + +describe('test server API', function () { + + let client; + + it('Should authentify and initialize the client', async function (){ + + const tempClient = new GraphQLClient(apiEndpoint); + const query = ` + mutation { + login(username : "${auth.username}", password: "${auth.password}" ) + } + `; + + const res = await tempClient.request(query); + + expect(res).to.have.property('login'); + + const token = res.login; + + client = new GraphQLClient(apiEndpoint, { + headers: { + mode : 'cors', // Don't know what that does, isn't necessary. Worth looking into + credentials : 'include', // Same + authorization: `Bearer ${token}` + } + }); + + }); + + it('Should query all groups to test the client', async function (){ + const query = ` + query { + allGroups { + name + } + } + `; + + const res = await client.request(query); + + + expect(res).to.have.property('allGroups'); + expect(res.allGroups).to.be.an('array'); + expect(res.allGroups).to.have.lengthOf.above(3); + }); + + for(const test of testList){ + + it(test.description, async function (){ + const res = await client.request(test.query); + expect(res).to.be.deep.equal(test.result); + }); + + } + +}); diff --git a/test/testData.js b/test/testData.js new file mode 100644 index 0000000000000000000000000000000000000000..00496169990173c6a9737237e29dcd0fc0c4cc89 --- /dev/null +++ b/test/testData.js @@ -0,0 +1,83 @@ +/* +* This is the list of unit tests which will be ran by the npm test command. +* To add new unit tests, just write the query you want to test in GraphiQL, +* then add new entries to the list with the following format : +* query : the query you wrote +* result : the output returned by GraphiQL +* description : what the test does +* +* Tests should be written to work on the current seed +* If the seed is modified, the expected result for the tests will have to be changed as well +*/ + +exports.testList = [ + { + query : `query{allGroups{name}}`, + result: { + "allGroups": [ + { "name": "BR" }, + { "name": "JTX" }, + { "name": "Faërix" }, + { "name": "Bôbar" }, + { "name": "Kès" }, + { "name": "Subaïsse" }, + { "name": "X-Broadway" }, + { "name": "Œnologie" }, + { "name": "Tribunes de l'X" }, + { "name": "X-Finance" }, + { "name": "ASK" } + ] + }, + description : "Should query all groups" + }, + { + query : ` + query{group(uid : "br"){ + uid, + name, + website + }}`, + result : { + "group": { + "uid": "br", + "name": "BR", + "website": "br.binets.fr" + } + }, + description : "Should query a single group" + }, + { + query : `query{ + allMessages{ + id, + title, + content + } + }`, + result : { + "allMessages": [ + { + "id": "11", + "title": "Fête de la lune", + "content": "La fête de la lune, c'est bientôt dans le grand hall !" + }, + { + "id": "12", + "title": "Perm BR du mardi soir", + "content": "La perm' BR c'est maintenant!" + }, + { + "id": "13", + "title": "Formation Git", + "content": "Aujourd'hui, on va parler du système de contrôle de versions Git, qui est particulièrement utile pour travailler à plusieurs sur des projets informatiques: PSC, code de PI ou de projet de MAP, site binet, quoi que ce soit!" + }, + { + "id": "14", + "title": "Formation Web", + "content": "Envie d'apprendre à faire un site Web en Django ? Alors viens en amphi Sauvy ce jeudi à 20h !" + } + ] + }, + description : "Should fetch all messages" + } +]; \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 7284a436ec35bdd7978e1ec22cb25a379a841fe3..fe9726b049c06760f49696848eec7396b3f7d135 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,5 @@ { - "comment": "Fichier servant à la configuration des fichiers typescript, ou javascript typé", + "comment": "Fichier servant à la configuration des fichiers typescript, ou javascript typé (https://www.typescriptlang.org/docs/handbook/tsconfig-json.html)", "compilerOptions": { "module": "commonjs", "esModuleInterop": true, diff --git a/tslint.json b/tslint.json new file mode 100644 index 0000000000000000000000000000000000000000..fa6d17853c1dbc8a0380f1ea9f6aa65c1e70e756 --- /dev/null +++ b/tslint.json @@ -0,0 +1,26 @@ +{ + "defaultSeverity": "error", + "extends": "tslint:recommended", + "rules": { + "indent": { "options": ["spaces", 4] }, + "linebreak-style": { "options": ["LF"] }, + "semicolon": { "options": ["always"] }, + "max-line-length": { + "severity": "warning", + "options": [120] + }, + "no-console": { + "severity": "warning", + "options": [ + "debug", + "info", + "log", + "time", + "timeEnd", + "trace" + ] + } + }, + "jsRules": true, + "rulesDirectory": [] +} \ No newline at end of file