npm package: @mnemotix/weever-core
Description: Weever Core toolkit
Author: Mnemotix
Latest version: 2.1.1

# Weever

[[_TOC_]]

## Tour d'horizon

Weever est un outil de documentation articulé autour d'une chaîne de traitement sémantique. 

Dit autrement, on peut la voir comme une application Web permettant une manipulation simplifiée d'une base de donnée RDF basée sur [l'ontologie générique de Mnémotix](https://mnemotix.gitlab.io/mnx-models/).

Dit autrement, Weever est un site Web à travers duquel il est possible de documenter la progression d'un ou plusieurs projets au travers d'événements temporels liés a un carnet d'adresse et a un système de fichier :

![weever-preview.png](assets/weever-preview.png)

Petit plus, il est possible de partager publiquement tout ou partie d'un projet. Un explorateur de données ouvertes est joint à l'application. 

![explorer-preview.png](assets/weever-explo-preview.png)


## A propos de ce dépôt

Ce dépôt contient le cœur de Weever, toute la logique permettant de démarrer une instance de l'application. Notez qu'elle reste une bibliothèque au sens Javascript, ou dit autrement une dépendance NPM. Il n'est donc pas possible de la démarrer en mode standalone.

Par contre, étant un moteur générique, il est possible de l'inclure dans un nouveau projet pour démarrer une nouvelle instance ad hoc. Cette nouvelle instance pourra :

 - être customisée graphiquement avec un thème de couleur à la carte.
 - être améliorée dans ses fonctionnalités grâce à un système d'injection de composants.

[Une instance de démo](https://weever.mnemotix.com/) existe pour montrer les fonctionnalités de base. 

Identifiants de test : jean.demo@gmil.co/test

## Manuel pour l'utilisateur

Pour les non-développeurs, le manuel d'utilisateur est disponible sur le [wiki de l'instance de Démo](https://gitlab.com/mnemotix/weever-community/weever-demo/-/wikis/home).

## Manuel pour le développeur

Le plus concret pour savoir comment structurer une instance Weever est de regarder dans le dépôt de [Weever de démo](https://gitlab.com/mnemotix/weever-community/weever-demo).

### Dépendances pré-requises

Weever est basé sur une liste de dépendances obligatoires dans sa partie backend et frontend.

#### Environnement logiciel

Weever est conçu pour interfacer un certain nombre de logiciels tiers.

- Une base de données RDF [GraphDB](https://graphdb.ontotext.com)
- Un moteur de recherche [ElasticSearch](https://www.elastic.co/fr/elasticsearch) dans lequel GraphDB indexe ses données via un certain nombre de [connecteurs](https://graphdb.ontotext.com/documentation/10.0/elasticsearch-graphdb-connector.html).
- Un serveur d'authentification [Keycloak](https://www.keycloak.org)
- Un serveur de stockage de fichier en cloud [Minio](https://min.io/)
- Un serveur de téléversement optimisé de fichiers [TusD](https://github.com/tus/tusd)
- Un serveur de génération d'imagette [Thumbor](https://github.com/thumbor/thumbor)

Si cette liste peut paraître imposante, toute cette stack applicative (à part GraphDB) est facile à mettre en place via un fichier docker-compose moyennant le réglage de quelques variables d'environnements abordé dans une section suivante.


#### Back-End

La partie Back-End de Weever est piloté par la bibliothèque [@synaptix/server](https://gitlab.com/mnemotix/synaptix-js/synaptix.js) anciennement nommée `synaptix.js`. Cette librairie est une boîte noire interfacée avec un bus sémantique Synaptix et permet de générer une API GraphQL assurant 99% de la gestion de donnée. 

Cette API GraphQL permet de :

- Requêter la base de données et l'index de l'application (`Query` au sens GraphQL)
- Mettre à jour la base de données. (`Mutation` au sens GraphQL)
- Gérer son compte utilisateur (connexion, déconnexion, création de compte, oubli de mot de passe).  (`Mutation` au sens GraphQL)

Chaque requête GraphQL est contextualisée par un jeton de session (JWT) et un header HTTP précisant la langue de l'interface.

Le 1% restant est un petit ensemble de middlewares ExpressJS (l'API GraphQL étant un middleware particulier) permettant de renvoyer différentes choses comme des fichiers de langues ou des fichiers normalisés.

La liste de ces middlewares sont les fichiers dans le dossier [/src/server/middlewares](src/server/middlewares) qui commencent par `serve***`

La documentation de `@synaptix/server` est [ici](https://mnemotix.gitlab.io/synaptix-js/synaptix.js).

#### Front-End

L'interface graphique de Weever s'appuie sur la librarie [@synaptix/ui](https://gitlab.com/mnemotix/synaptix-js/synaptix-ui) elle-même basée sur la libraire [MUI](https://mui.com).

`@synaptix/ui` offre des composants clé-en-main et connectés au modèle de données générique Mnémotix.

> Note: La documentation de SynaptixUI va venir bientôt...


L'interfaçage de données avec l'API GrapQL s'appuie sur la librairie [@synaptix/client](https://gitlab.com/mnemotix/synaptix-js/synaptix-client) (anciennement nommée `synaptix-client-toolkit`) elle-même basée sur la librairie `@apollo/client`.

D'autres librairie utilisant des contextes React sont déclarées en peerDependencies afin d'éviter des doublons et donc des erreurs. Notamment :
 - `dayjs` pour la gestion de date.
 - `formik` pour la gestion des formulaires.
 - `react-i18next` pour la gestion des labels i18n.
 - `react-router` pour la gestion des routes.

#### Package.json

En résumé, votre fichier `package.json` devra comporter les dépendances suivantes :

```json
{
  "@apollo/client": "^3.4.8",
  "@emotion/react": "^11.9.0",
  "@emotion/styled": "^11.8.1",
  "@mnemotix/synaptix-client-toolkit": "^2.5.5",
  "@mnemotix/synaptix.js": "^4.12.1",
  "@mui/material": "^5.6.4",
  "@mui/styles": "^5.6.2",
  "@mui/system": "^5.6.4",
  "@synaptix/ui": ">=1.0.0",
  "dayjs": "^1.10.7",
  "formik": "2.2.9",
  "graphql": "15.7.2",
  "react": "17.0.2",
  "react-dom": "17.0.2",
  "react-i18next": "11.16.2",
  "react-router": "5.2.0",
  "react-router-dom": "5.3.0"
}
```

> Note: La liste se trouve dans le champ `peerDependencies` du fichier [/package.json](package.json)


#### Environnement de développement

Weever est une application connectée à un écosystème logiciel (voir section `Environnement logiciel`), avant de démarrer une instance de développement, il faut s'assurer d'avoir réglé un certain nombre de variables d'environnements.

Ces variables sont à mettre dans un fichier `.env` à la racine du projet.

```
RDFSTORE_ROOT_URI=              # URI du endpoint GraphDB
RDFSTORE_REPOSITORY_NAME=       # Nom de la base recevant les données RDF
RDFSTORE_USER=                  # Identifiant  de l'utilisateur GraphDB ayant au RDFSTORE_REPOSITORY_NAME avec accès en R/W 
RDFSTORE_PWD=                   # Mot de passe de l'utilisateur GraphDB 

ES_MASTER_URI=                  # URI du endpoint "master" ES  
ES_CLUSTER_USER=                # Identifiant  de l'utilisateur ES
ES_CLUSTER_PWD=                 # Mot de passe de l'utilisateur ES
INDEX_VERSION=NATIVE            # Laisser NATIVE pour bypasser RabbitMQ et utiliser le client ES natif développé en NodeJS
INDEX_PREFIX_TYPES_WITH=        # Préfix [optionel] utilisé pour tous les indices ES

OAUTH_BASE_URL=                 # URI du endpoint Keycloak
OAUTH_ADMIN_USERNAME=           # Identifiant d'un utilisateur ayant des droits d'administrateur
OAUTH_ADMIN_PASSWORD=           # Mot de passe d'un utilisateur ayant des droits d'administrateur
OAUTH_REALM=                    # Identifiant du Realm 
OAUTH_REALM_CLIENT_ID=api       # Identifiant  du client OpenID, laisser "api" 
OAUTH_REALM_CLIENT_SECRET=      # Mot de passe du client OpenID
```

Pour prendre en compte ces variables, vous pouvez copier-coller le répertoire [/launcher](launcher/) dans votre projet. Ce répertoire contient (entre autre) deux sous répertoires [/conf/docker/local](conf/docker/local) et [/conf/docker/remote](conf/docker/remote) contenant des configuration docker-compose pour démarrer une stack de développement. Le premier (local) démarre une stack entièrement locale, le second démarre une stack se connectant sur un serveur GraphDB, un moteur ES, et un SSO keycloak distants.

L'éxécutable [/launcher/console.sh](launcher/console.sh) permet dans lancer les deux stacks.

```shell
REMOTE_MODE=(1|0) ./launcher/console.sh start
```

Notez qu'il faut lancer cette commande à la première installation

```shell
./launcher/console.sh install
```

#### Installation de données préliminaires

Un certain nombre de choses ne sont pas encore automatisées et nécéssite une intervention manuelle.

###### Créer une nouvelle base GraphDB et charger les ontologies

Dans le Workbench GraphDB, créer un nouveau "repository" qui prendra le même nom que la variable d'environnement `RDFSTORE_REPOSITORY_NAME` ainsi que qu'un utilisateur associé en Read/Write dont les identifiants auront les mêmes valeurs que les variables `RDFSTORE_USER` et `RDFSTORE_PWD`.

Attention, dans le formulaire de création de répo, ne pas oublier de sélectionner le "Ruleset Mnémotix" [rdfs-plus.mnx.pie](documentation/data/graphdb/rdfs-plus-mnx.pie)

![screen-repo-creation.png](documentation/data/graphdb/screen-repo-creation.png)

Une fois le répo créé, charger les ontologies disponibles dans le dossier [/documentation/data/graphdb](documentation/data/graphdb).

Ontologies standardisées :

  - foaf.ttl
  - geonames.ttl
  - prov.ttl
  - sioc.ttl
  - skos.ttl

Ontolologies Mnémotix :

  - mnx.ttl
  - mnx-project.ttl
  - mnx-skos.ttl

##### Instantier les connecteurs ES

Lancer la commande :

```shell
yarn data-index -a
```

Elle aura pour effet de créer un connecteur GraphDB -> ES par type RDF utilisé par Weever. Un connecteur verse ses données dans un index ES préfixé pa valeur de la variable d'environnement `INDEX_PREFIX_TYPES_WITH`

##### Créer un Realm Keycloak

Compléter le template disponible [/documentation/templates/realm.json](documentation/templates/realm.json) et charger le dans l'interface de création de Realm Keycloak.


### Mettre sur pied une instance Weever

Weever Core expose un certain nombre d'éléments pour démarrer une instance Weever rapidement.

#### Préparation

Weever Core expose l'utilitaire [/src/server/launch.js](src/server/launch.js) pour lancer le backend de Weever.

L'exemple de lancement le plus simple possible se trouve dans le fichier [entrypoint](mnemotix/weever-community/weever-demo/-/blob/master/src/server/entrypoint.js) de l'instance Weever Démo.

```js
import { launch } from "@mnemotix/weever-core/server";
import Package from "../../package.json";
import environmentDefinition from "./config/environment";
import webpackConfig from "../../webpack.config";

launch({
  Package,
  environmentDefinition,
  locales: {},
  webpackConfig
});
```

 - `Package` fait référence au fichier `package.json` importé dans un objet JS
 - `environmentDefinition` est un fichier surchargeant (et ajoutant) la définition des variables d'environnement par défaut et disponibles dans le fichier [/src/server/config/environment.js](src/server/config/environment.js) (la description des variables est définie dans le fichier).

> Note: Il est possible de surcharger les variables directement dans le fichier `.env`.

 - `locales` est un objet JS au format i18n permettant de surcharger les fichiers de langues par défaut et disponibles dans le fichier [/src/locales/index.js](src/locales/index.js) 

> Note: Le premier niveau de clés cet objet est la liste des langues disponible. Pour l'instant `fr|en`.

 - `webpackConfig` est un objet JS au format Webpack explicité ci-dessous. Webpack est le bundler retenu pour générer (et optimiser) les fichiers JS/CSS/Assets à envoyer au navigateur Web pour démarrer le Front-End de Weever

L'exemple le plus minimal est celui-ci :

```js
const path = require("path");
const { generateWebpackConfig } = require("@mnemotix/weever-core/server");

module.exports = generateWebpackConfig({
  distPath: path.resolve(__dirname, "dist"),
  entries: [
    {
      name: "weever",
      entry: path.resolve(__dirname, "./src/client/launchWeever.js"),
      html: {
        template: path.resolve(__dirname, "./src/client/index.tpl.html"),
        filename: "index.html",
      },
    }
  ],
});
```

Weever Core expose la méthode utilitaire [generateWebpackConfig](src/server/middlewares/generateWebpackConfig.js) qui attend le paramètre `distPath` (répertoire dans lequel générer les fichiers compilés et assemblés) et `entries` (les points d'entrées du code Front-End JS).

L'exemple ci-dessus va générer dans le répertoire `/dist` les assets (JS/CSS/Images/Font/autres) compilés et assemblés à partir du point d'entrée JS `./src/client/launchWeever.js` injecté dans le template HTML `./src/client/index.tpl.html`.

Le template HTML minimal `./src/client/index.tpl.html` à créer est celui-ci :

```html
<!doctype html>
<html lang="fr">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
  </head>

  <body>
    <div id="react-root"/>
  </body>
</html>
```

La seule balise importante à ne pas omettre est `<div id="react-root"/>`. C'est la balise d'injection du code ReactJS définissant le code HTML Front-End de l'instance Weever.

Quant au point d'entrée JS de Weever `./src/client/launchWeever.js` à définir, le code minimal est celui-ci :

```js
import {launchWeever} from "@mnemotix/weever-core/launchWeever";
import {theme} from "./theme";
import possibleTypes from "./gql/possibleTypes.json";

launchWeever({
  possibleTypes,
  theme,
  extensions: []
});
```

- `possibleTypes` est un object JS [nécéssaire au client GraphQL Apollo](https://www.apollographql.com/docs/react/data/fragments/#using-fragments-with-unions-and-interfaces) pour résoudre les fragments GQL. Ce fichier doit être généré à chaque changement de définition du schéma GraphQL. Pour cela deux options :
  - Si votre modèle ne spécifie pas celui de base. Copier-coller le fichier [/documentation/possibleTypes.json](documentation/possibleTypes.json) dans `./src/client/gql/possibleTypes.json` fera l'affaire.
  - Sinon, appellez la commande `yarn gql-fragments [chemin de votre définition de modèle]` qui le fera pour vous.
- `theme` est un objet JS définissant le thème graphique de l'application au [format MUI](https://mui.com/material-ui/customization/theming).
- `extensions` est la liste des extensions pour surcharger le code FrontEnd de l'application décrit dans Weeever Core. Voir la section `Extensions` dédiée.

#### Lancement

Une fois la préparation terminée. Deux méthodes de lancement.

- Utiliser l'utilitaire [@babel/node](https://babeljs.io/docs/en/babel-node) 

```shell
babel-node ./src/server/entrypoint.js
```

- Utiliser la librairie JS [@babel/register](https://babeljs.io/docs/en/babel-register) et créer un fichier de transpilation intermédiaire à la racine du projet `entrypoint.js`

```js
require("@babel/register");
require("./src/server/entrypoint");
```

Puis lancer la commande :

```shell
node ./entrypoint.js
```

> Note : Tous les fichiers décrits dans la section `Lancement` et `Préparation` sont disponibles dans le dépôt de l'instance [Weever de démo](https://gitlab.com/mnemotix/weever-community/weever-demo) avec plus d'options et de détails (notamment l'intégration de Koncept dans les points d'entrée du fichier de configuration de Webpack)

### Étendre Weever

#### Extensions

Comme évoqué dans la section précédente l'utilitaire [@mnemotix/weever-core/launchWeever](src/client/launchWeever.js) peut attendre le paramètre `extensions`.

C'est une liste d'instance d'objet [Extension](https://gitlab.com/mnemotix/synaptix-js/synaptix-ui/-/blob/main/src/extensions/Extension.js).

Un exemple étant le plus parlant, on peut reprendre l'exemple ci-dessus en ajoutant une extension :

```js
import {ROUTES} from "@mnemotix/weever-core";
import {Extension} from "@synaptix/ui";
import {Route} from "react-router-dom";
import loadable from "@loadable/component";
import {launchWeever} from "@mnemotix/weever-core/launchWeever";
import {theme} from "./theme";
import possibleTypes from "./gql/possibleTypes.json";

// Routes are code splitted to decrease the size of initial laaded JS bundle file

const InjectedFragment = loadable(() => import("./components/InjectedFragment"));
const OverridedComponent = loadable(() => import("./components/OverridedComponent"));
const AddedComponent = loadable(() => import("./components/AddedComponent"));

const extension = new Extension({
  name: "MyCoolExtension",
  generateTopRoutes: ({isContributor, isAdmin, isEditor} = {}) => (
    <>
      <Route exact path={ROUTES["route_to_override"]} component={OverridedComponent} />
      
      <If condition={isContributor}>
        <Route exact path={"/new_route"} component={AddedComponent} />
      </If>
    </>
  ),
  fragments: {
    "Project.Heading.AfterTags": InjectedComponent,
  },
  settings: {
    "ProjectsListVisibleColumnNames": ["image", "title", "tags", "status", "updatedAt", "accessPolicy"]
  }
});

launchWeever({
  possibleTypes,
  theme,
  extensions: [extension]
});
```

Dans cet exemple, trois types d'extensions sont utilisées.

##### Surchage de haut niveau

La surchage complète et/ou ajout d'une route de haut niveau se fait dans le paramètre `generateTopRoutes` qui prend une fonction et attend une liste de routes à ajouter au début des [routes déjà existantes](src/client/Weever.js#L148). React Router s'arrêtant sur la première route matchant l'URL demandée, si deux routes avec le même chemin sont définies, la première l'emporte.

##### Surchage d'une portion de composant

La Surchage d'une portion de composant se fait dans le paramètre `fragments` qui est un object de clés/valeurs. Les clés étant les identifiants des points d'injection dans l'application et les valeurs, le composant React à monter. 

Dans le code de Weever Core vous pouvez trouver des choses comme dans [src/client/components/routes/Project/Project.js](src/client/components/routes/Project/Project.js#106) :

```js
useExtensionFragment("Project.Heading.AfterTags", {projectId: "..."});
```

Il s'agit d'un point d'injection possible défini par l'identifiant `Project.Heading.AfterTags`. 

Dans l'exemple ci-dessus, le composant `InjectedComponent` est monté sous la forme `<InjectedComponent projectId="..."/>` au niveau de ce point d'injection.

Le hook correspondant pour récupérer ce composant est exposé dans @synaptix/ui et s'appelle [useExtensionFragment](https://gitlab.com/mnemotix/synaptix-js/synaptix-ui/-/blob/main/src/extensions/useExtensionFragment.js) :

##### Surchage d'une configuration particulière

D'autres portions de l'application sont pilotées grâce à des configurations simples. Dans l'exemple plus haut la clé `ProjectsListVisibleColumnNames` concerne les [colonnes visibles par défaut](https://gitlab.com/mnemotix/synaptix-js/synaptix-ui/-/blob/main/src/components/base/CollectionView/TableView/Table.js#L141) de la liste des projets.

Le hook correspondant pour récupérer cette valeur est exposé dans @synaptix/ui et s'appelle [useExtensionSettings](https://gitlab.com/mnemotix/synaptix-js/synaptix-ui/-/blob/main/src/extensions/useExtensionSettings.js) :

```js
useExtensionSettings("ProjectsListVisibleColumnNames")
```