From 3f6ebf23692269c756f0aa86c46314faf20973a9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timoth=C3=A9e=20Poussard?= <tpoussard@neogeo.fr>
Date: Mon, 19 Jul 2021 17:56:50 +0200
Subject: [PATCH] Reorganize base in App.js, with store & router and use login
 template

---
 package-lock.json                   |  19 +++-
 package.json                        |   3 +-
 src/App.vue                         | 149 +++++++++++++++++++++++++--
 src/main.js                         |   2 +
 src/router/index.js                 |  19 ++--
 src/store/index.js                  | 103 +++++++++++++++++++
 src/views/Base.vue                  | 151 ----------------------------
 src/{components => views}/Index.vue |  46 +++------
 src/views/Login.vue                 |  92 +++++++++++++++++
 9 files changed, 380 insertions(+), 204 deletions(-)
 create mode 100644 src/store/index.js
 delete mode 100644 src/views/Base.vue
 rename src/{components => views}/Index.vue (75%)
 create mode 100644 src/views/Login.vue

diff --git a/package-lock.json b/package-lock.json
index b312f8d4..3782d704 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,5 +1,5 @@
 {
-  "name": "geocontrib-pwa",
+  "name": "geocontrib-frontend",
   "version": "0.1.0",
   "lockfileVersion": 2,
   "requires": true,
@@ -11,7 +11,8 @@
         "core-js": "^3.6.5",
         "register-service-worker": "^1.7.1",
         "vue": "^2.6.11",
-        "vue-router": "^3.2.0"
+        "vue-router": "^3.2.0",
+        "vuex": "^3.6.2"
       },
       "devDependencies": {
         "@vue/cli-plugin-babel": "~4.5.0",
@@ -14274,6 +14275,14 @@
       "integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==",
       "dev": true
     },
+    "node_modules/vuex": {
+      "version": "3.6.2",
+      "resolved": "https://registry.npmjs.org/vuex/-/vuex-3.6.2.tgz",
+      "integrity": "sha512-ETW44IqCgBpVomy520DT5jf8n0zoCac+sxWnn+hMe/CzaSejb/eVw2YToiXYX+Ex/AuHHia28vWTq4goAexFbw==",
+      "peerDependencies": {
+        "vue": "^2.0.0"
+      }
+    },
     "node_modules/watchpack": {
       "version": "1.7.5",
       "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz",
@@ -27043,6 +27052,12 @@
       "integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==",
       "dev": true
     },
+    "vuex": {
+      "version": "3.6.2",
+      "resolved": "https://registry.npmjs.org/vuex/-/vuex-3.6.2.tgz",
+      "integrity": "sha512-ETW44IqCgBpVomy520DT5jf8n0zoCac+sxWnn+hMe/CzaSejb/eVw2YToiXYX+Ex/AuHHia28vWTq4goAexFbw==",
+      "requires": {}
+    },
     "watchpack": {
       "version": "1.7.5",
       "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz",
diff --git a/package.json b/package.json
index 609cd047..17e4abf4 100644
--- a/package.json
+++ b/package.json
@@ -12,7 +12,8 @@
     "core-js": "^3.6.5",
     "register-service-worker": "^1.7.1",
     "vue": "^2.6.11",
-    "vue-router": "^3.2.0"
+    "vue-router": "^3.2.0",
+    "vuex": "^3.6.2"
   },
   "devDependencies": {
     "@vue/cli-plugin-babel": "~4.5.0",
diff --git a/src/App.vue b/src/App.vue
index 2bc8c171..748654ee 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -1,11 +1,146 @@
 <template>
-  <div id="app">
-   <!--  <div id="nav">
-      <router-link to="/">Home</router-link> |
-      <router-link to="/about">About</router-link>
-    </div> -->
-    <router-view/>
-  </div>
+  <body>
+    <header class="header-menu">
+      <div class="ui container">
+        <div class="ui inverted icon menu">
+          <a href="" class="header item">
+            <!-- <img class="ui mini right spaced image" :src="LOGO_PATH">
+          <img class="ui mini right spaced image" :src="$process.env.VUE_APP_LOGO_PATH"> -->
+            <img
+              class="ui mini right spaced image"
+              src="@/assets/logo-neogeo-circle.png"
+            />
+            {{ APPLICATION_NAME }}
+          </a>
+
+          <div v-if="project" class="ui dropdown item">
+            Projet&nbsp;: {{ project.title }}
+            <i class="dropdown icon"></i>
+            <div class="menu" style="z-index: 401">
+              <a
+                class="item"
+                href="{% url 'geocontrib:project' slug=project.slug %}"
+              >
+                <i class="home icon"></i>Accueil
+              </a>
+              <a
+                class="item"
+                href="{% url 'geocontrib:feature_list' slug=project.slug %}"
+              >
+                <i class="list icon"></i>Liste & Carte
+              </a>
+              {% if project and permissions|lookup:'is_project_administrator' %}
+              <a
+                class="item"
+                href="{% url 'geocontrib:project_mapping' slug=project.slug %}"
+              >
+                <i class="map icon"></i>Fonds cartographiques
+              </a>
+              <a
+                class="item"
+                href="{% url 'geocontrib:project_members' slug=project.slug %}"
+              >
+                <i class="users icon"></i>Membres
+              </a>
+              {% endif %}
+            </div>
+          </div>
+
+          <div class="right menu">
+            <!--  {% if user.is_authenticated %}
+          <a class="item" href="{% url 'geocontrib:my_account' %}">
+            {{ user.get_full_name|default:user.username }}
+          </a>
+          {% if project or user.is_administrator %}
+          <div class="item ui label">
+            {% if project %}{{ USER_LEVEL_PROJECTS|lookup:project.slug }}<br>{% endif %}
+            {% if user.is_administrator == True %}Gestionnaire métier{% endif %}
+          </div>
+          {% endif %}
+          {% if not SSO_SETTED %}
+          <a class="item" href="{% url 'geocontrib:logout' %}"><i class="ui logout icon"></i></a>
+          {% endif %}
+          {% elif not SSO_SETTED %} -->
+            <router-link to="/connexion" class="item">Se Connecter</router-link>
+            <!-- {% endif %} -->
+          </div>
+        </div>
+      </div>
+    </header>
+    <main>
+      <div class="ui stackable grid centered container">
+        <!--  {% if messages %} -->
+        <!-- <div v-if="false" class="row">
+        <div class="fourteen wide column">
+          {% for message in messages %}
+            {% if message.tags == 'success'%}
+              <div class="ui positive message">
+            {% else %}
+              <div class="ui info message">
+            {% endif %}
+          {% endfor %}
+            <div class="header">
+              <i class="info circle icon"></i> Informations
+            </div>
+            <ul class="list">
+              {% for message in messages %}
+              {{ message }}
+              {% endfor %}
+            </ul>
+          </div>
+        </div>
+      </div> -->
+        <!-- {% endif %} -->
+
+        <!--   <SignIn v-if="!user"/>  -->
+        <!-- <Index v-if="user" :user="user" :projects="projects" /> -->
+
+        <!--     <Login v-if="logging" /> -->
+        <!--  <Index v-else /> -->
+        <router-view />
+      </div>
+    </main>
+
+    <footer>
+      <div class="ui compact text menu">
+        <!-- // todo: ajouter liens -->
+        <a class="item" href="#"></a>
+        <a class="item" href="#"></a>
+        <p class="item">Version {{ PACKAGE_VERSION }}</p>
+      </div>
+    </footer>
+  </body>
 </template>
 
+<script>
+import { mapState } from "vuex";
+
+export default {
+  name: "App",
+  computed: {
+    ...mapState(["logging", "projects"]),
+    LOGO_PATH: () => process.env.VUE_APP_LOGO_PATH,
+    APPLICATION_NAME: () => process.env.VUE_APP_APPLICATION_NAME,
+    PACKAGE_VERSION: () => process.env.PACKAGE_VERSION || "0",
+  },
+  data() {
+    return {
+      user: null,
+      project: null,
+    };
+  },
+  created() {
+    this.$store.dispatch("GET_PROJECTS");
+    this.$store.dispatch("GET_COOKIE", "csrftoken"); // * ne récupère plus le cookie arès avoir vidé le cache ?!
+  },
+};
+</script>
 
+<style>
+@import "./assets/styles/base.css";
+@import "./assets/resources/semantic-ui-2.4.2/semantic.min.css";
+.header-menu {
+  min-width: 560px;
+}
+</style>
+ 
\ No newline at end of file
diff --git a/src/main.js b/src/main.js
index 9f15cebd..d12df39b 100644
--- a/src/main.js
+++ b/src/main.js
@@ -2,10 +2,12 @@ import Vue from 'vue'
 import App from './App.vue'
 import './registerServiceWorker'
 import router from './router'
+import store from './store'
 
 Vue.config.productionTip = false
 
 new Vue({
   router,
+  store,
   render: h => h(App)
 }).$mount('#app')
diff --git a/src/router/index.js b/src/router/index.js
index 007c7be7..563b6ec9 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -1,23 +1,24 @@
 import Vue from 'vue'
 import VueRouter from 'vue-router'
-import Base from '../views/Base.vue'
+import Index from '../views/Index.vue'
+//import Login from '../views/Login.vue'
 
 Vue.use(VueRouter)
 
 const routes = [
   {
     path: '/',
-    name: 'Base',
-    component: Base
+    name: 'Index',
+    component: Index
   },
-  //{
-  //  path: '/mon-compte',
-  //  name: 'My-account',
+  {
+    path: '/connexion',
+    name: 'Login',
     // route level code-splitting
-    // this generates a separate chunk (about.[hash].js) for this route
+    // this generates a separate chunk (login.[hash].js) for this route
     // which is lazy-loaded when the route is visited.
-  //  component: () => import(/* webpackChunkName: "about" */ '../views/My-account.vue')
-  //}
+    component: () => import(/* webpackChunkName: "login" */ '../views/Login.vue')
+  }
 ]
 
 const router = new VueRouter({
diff --git a/src/store/index.js b/src/store/index.js
new file mode 100644
index 00000000..45412bf6
--- /dev/null
+++ b/src/store/index.js
@@ -0,0 +1,103 @@
+const axios = require("axios");
+
+import Vue from 'vue';
+import Vuex from 'vuex';
+import router from '../router'
+//import modules from './modules';
+
+Vue.use(Vuex);
+
+const GET_PROJECT_USER = GET_PROJECT_USER;
+const GET_PROJECTS = GET_PROJECTS;
+const CHECK_LOGIN = CHECK_LOGIN;
+const SET_COOKIE = SET_COOKIE;
+
+
+export default new Vuex.Store({
+  //  modules,
+  state: {
+    cookie: null,
+    logged: false,
+    project: null,
+    projectMembers: null,
+    projects: [],
+  },
+
+  mutations: {
+    SET_PROJECTS(state, projects) {
+      state.projects = projects;
+    },
+    SET_PROJECT(state, project) {
+      state.project = project;
+    },
+    SET_PROJECT_MEMBERS(state, projectMembers) {
+      state.projectMembers = projectMembers;
+    },
+    SET_LOGGED(state, payload) {
+      state.logged = payload;
+    },
+    SET_COOKIE(state, cookie) {
+      state.cookie = cookie
+    }
+  },
+
+  actions: {
+    GET_PROJECTS({ commit }) {
+      axios
+        .get("http://localhost:8000/api/projects/")
+        .then((response) => (commit("SET_PROJECTS", response.data)))
+        .catch((error) => {
+          throw error;
+        });
+    },
+    GET_PROJECT({ commit }, project_slug) {
+      axios
+        .get(`http://localhost:8000/api/projet/${project_slug}/project`)
+        .then((response) => (commit("SET_PROJECT", response.data)))
+        .catch((error) => {
+          throw error;
+        });
+    },
+    GET_PROJECT_USER({ commit }, project_slug) {
+      axios
+        .get(`http://localhost:8000/api/projet/${project_slug}/utilisateurs`)
+        .then((response) => (commit("SET_PROJECT_MEMBERS", response.data.members)))
+        .catch((error) => {
+          throw error;
+        });
+    },
+    CHECK_LOGIN({ commit }, payload) {
+      if (payload.username && payload.password) {
+        axios
+          .post("http://localhost:8000/api/login/", {
+            username: payload.username,
+            password: payload.password,
+          })
+          .then(() => (commit("SET_LOGGED", true), router.push("/")))
+          .catch((error) => {
+            throw error;
+          });
+      }
+      console.log("router", router)
+    },
+    GET_COOKIE({ commit }, name) {
+      let cookieValue = null;
+
+      if (document.cookie && document.cookie !== "") {
+        const cookies = document.cookie.split(";");
+        for (let i = 0; i < cookies.length; i++) {
+          const cookie = cookies[i].trim();
+
+          // Does this cookie string begin with the name we want?
+          if (cookie.substring(0, name.length + 1) === name + "=") {
+            cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
+
+            break;
+          }
+        }
+      }
+      commit("SET_COOKIE", cookieValue);
+    },
+  }
+
+});
diff --git a/src/views/Base.vue b/src/views/Base.vue
deleted file mode 100644
index 99d3ca8f..00000000
--- a/src/views/Base.vue
+++ /dev/null
@@ -1,151 +0,0 @@
-<template>
-<body>
-  <header class="header-menu">
-    <div class="ui container">
-      <div class="ui inverted icon menu">
-        <a href="" class="header item">
-          <!-- <img class="ui mini right spaced image" :src="LOGO_PATH">
-          <img class="ui mini right spaced image" :src="$process.env.VUE_APP_LOGO_PATH"> -->
-          <img class="ui mini right spaced image" src="@/assets/logo-neogeo-circle.png">
-          {{ APPLICATION_NAME }}
-        </a>
-
-        <div v-if="project" class="ui dropdown item">
-          Projet&nbsp;: {{ project.title }}
-          <i class="dropdown icon"></i>
-          <div class="menu" style="z-index:401;">
-            <a class="item" href="{% url 'geocontrib:project' slug=project.slug %}">
-              <i class="home icon"></i>Accueil
-            </a>
-            <a class="item" href="{% url 'geocontrib:feature_list' slug=project.slug %}">
-              <i class="list icon"></i>Liste & Carte
-            </a>
-            {% if project and permissions|lookup:'is_project_administrator' %}
-            <a class="item" href="{% url 'geocontrib:project_mapping' slug=project.slug %}">
-              <i class="map icon"></i>Fonds cartographiques
-            </a>
-            <a class="item" href="{% url 'geocontrib:project_members' slug=project.slug %}">
-              <i class="users icon"></i>Membres
-            </a>
-            {% endif %}
-          </div>
-        </div>
-
-        <div class="right menu">
-         <!--  {% if user.is_authenticated %}
-          <a class="item" href="{% url 'geocontrib:my_account' %}">
-            {{ user.get_full_name|default:user.username }}
-          </a>
-          {% if project or user.is_administrator %}
-          <div class="item ui label">
-            {% if project %}{{ USER_LEVEL_PROJECTS|lookup:project.slug }}<br>{% endif %}
-            {% if user.is_administrator == True %}Gestionnaire métier{% endif %}
-          </div>
-          {% endif %}
-          {% if not SSO_SETTED %}
-          <a class="item" href="{% url 'geocontrib:logout' %}"><i class="ui logout icon"></i></a>
-          {% endif %}
-          {% elif not SSO_SETTED %}
-          <a class="item" href="{% url 'geocontrib:login' %}">Se Connecter</a>
-          {% endif %} -->
-        </div>
-      </div>
-    </div>
-  </header>
-    <main>
-    <div class="ui stackable grid centered container">
-     <!--  {% if messages %} -->
-      <!-- <div v-if="false" class="row">
-        <div class="fourteen wide column">
-          {% for message in messages %}
-            {% if message.tags == 'success'%}
-              <div class="ui positive message">
-            {% else %}
-              <div class="ui info message">
-            {% endif %}
-          {% endfor %}
-            <div class="header">
-              <i class="info circle icon"></i> Informations
-            </div>
-            <ul class="list">
-              {% for message in messages %}
-              {{ message }}
-              {% endfor %}
-            </ul>
-          </div>
-        </div>
-      </div> -->
-      <!-- {% endif %} -->
-
-      <Index :user="user" :projects="projects"/> 
-    </div>
-  </main>
-
-  <footer>
-    <div class="ui compact text menu">
-      <a class="item" href="{% url 'geocontrib:legal' %}">Mentions légales</a>
-      <a class="item" href="{% url 'geocontrib:help' %}">Aide</a>
-      <p class="item">Version {{PACKAGE_VERSION}}</p>
-    </div>
-  </footer>
-
-</body>
-
-</template>
-
-<script>
-//const LOGO_PATH = require(process.env.VUE_APP_LOGO_PATH);
-const axios = require("axios");
-import Index from "@/components/Index.vue";
-
-
-export default {
-  name: "App",
-  components: {
-    Index: Index
-  },
-  computed: {
-    LOGO_PATH: () => process.env.VUE_APP_LOGO_PATH,
-    APPLICATION_NAME: () => process.env.VUE_APP_APPLICATION_NAME,
-    PACKAGE_VERSION: () => process.env.PACKAGE_VERSION || '0',
-  },
-  data() {
-    return {
-      user: null,
-      project: null,
-      projects: null,
-    }
-  },
-  created() {
-    //this.getUser()
-    this.getProjects();
-  },
-  methods: {
-    getUser() {
-      axios.get("http://localhost:8000/api/projet/1-vuetification/utilisateurs")
-      .then(response => this.user = response.data.members)
-      .catch(error => {
-        throw(error)
-      })
-    },
-    getProjects() {
-      axios
-        .get("http://localhost:8000/api/projects/")
-        // .then((response) => (this.projects = response.data.projects))
-        .then((response) => (this.projects = response.data))
-        .catch((error) => {
-          throw(error);
-        });
-    },
-  },
-};
-</script>
-
-<style>
-  @import '../assets/styles/base.css';
-  @import '../assets/resources/semantic-ui-2.4.2/semantic.min.css';
-  .header-menu {
-    min-width: 560px;
-}
-</style>
- 
\ No newline at end of file
diff --git a/src/components/Index.vue b/src/views/Index.vue
similarity index 75%
rename from src/components/Index.vue
rename to src/views/Index.vue
index 25974163..6cd320ac 100644
--- a/src/components/Index.vue
+++ b/src/views/Index.vue
@@ -15,15 +15,11 @@
 
     <h4 id="les_projets" class="ui horizontal divider header">PROJETS</h4>
     <!-- {% if can_create_project %} -->
-    <a
-      class="ui green basic button"
-      href="{% url 'geocontrib:project_create' %}"
+    <a class="ui green basic button" href="#"
       ><!-- //todo : get base url and add the rest -->
       <i class="plus icon"></i> Créer un nouveau projet
     </a>
-    <a
-      class="ui blue basic button right floated"
-      href="{% url 'geocontrib:project_type_list' %}"
+    <a class="ui blue basic button right floated" href="#"
       ><!-- //todo : get base url and add the rest -->
       <i class="copy icon"></i> Accéder à la liste des modèles de projets
     </a>
@@ -31,14 +27,12 @@
     <div v-if="projects" class="ui divided items">
       <div v-for="project in projects" class="item" :key="project.slug">
         <div class="ui tiny image">
+          <!-- // todo: récupérer l'image sur serveur front (et non back) -->
           <img :src="project.thumbnail" />
+          <!-- // todo: récupérer l'image par défaut -->
         </div>
         <div class="middle aligned content">
-          <a
-            class="header"
-            href="{% url 'geocontrib:project' slug=project.slug %}"
-            >{{ project.title }}</a
-          ><!-- //todo : get base url and add the slug -->
+          <a class="header" @click="open_project(project.slug)">{{ project.title }}</a>
           <div class="description">
             <p>{{ project.description }}</p>
           </div>
@@ -54,7 +48,7 @@
             <span>
               Mon niveau d'autorisation :
               <!-- //todo: get this value -->
-              {{ USER_LEVEL_PROJECTS|lookup:project.slug }}
+              <!-- {{ USER_LEVEL_PROJECTS|lookup:project.slug }} -->
               <!-- //todo: get user -->
               {{
                 user && user.is_administrator == True
@@ -92,37 +86,21 @@
 </template>
 
 <script>
-//const axios = require("axios");
+import { mapState } from "vuex";
 
 export default {
   name: "Index",
-  /*   data() {
-    return {
-      projects: null,
-    };
-  }, */
-  props: ["user", "projects"],
   computed: {
+    ...mapState(["projects", "user"]),
     LOGO_PATH: () => process.env.VUE_APP_LOGO_PATH,
     APPLICATION_NAME: () => process.env.VUE_APP_APPLICATION_NAME,
     APPLICATION_ABSTRACT: () => process.env.VUE_APP_APPLICATION_ABSTRACT,
   },
-  created() {
-    // this.getProjects();
-  },
   methods: {
-    /* getProjects() {
-      axios
-        .get("http://localhost:8000/api/projects/")
-        .then((response) => (this.projects = response.data.projects))
-        .catch((error) => {
-          console.log(error);
-        });
-    }, */
-    /* toLocaleDate(dateStr) {
-        const dateObj = new Date(dateStr);
-        return dateObj.toLocaleDateString();
-    } */
+    open_project(project_slug) {
+      console.log("open",project_slug);
+      this.$store.dispatch("GET_PROJECT", project_slug)
+    }
   },
 };
 </script>
\ No newline at end of file
diff --git a/src/views/Login.vue b/src/views/Login.vue
new file mode 100644
index 00000000..a23831ba
--- /dev/null
+++ b/src/views/Login.vue
@@ -0,0 +1,92 @@
+<template>
+  <div>
+    <div class="row">
+      <div class="fourteen wide column">
+        <img
+          class="ui centered small image"
+          src="@/assets/logo-neogeo-circle.png"
+        />
+        <h2 class="ui center aligned icon header">
+          <div class="content">
+            {{ APPLICATION_NAME }}
+            <div class="sub header">{{ APPLICATION_ABSTRACT }}</div>
+          </div>
+        </h2>
+      </div>
+    </div>
+    <div class="row">
+      <div class="six wide column">
+        <h3 class="ui horizontal divider header">CONNEXION</h3>
+
+        <div v-if="form.errors" class="ui warning message">
+          <div class="header">
+            Les informations d'identification sont incorrectes.
+          </div>
+          NB: Seuls les comptes actifs peuvent se connecter.
+        </div>
+
+        <form class="ui form" role="form" type="post" @submit.prevent="login">
+          <!-- {% csrf_token %} -->
+          <input name="csrfmiddlewaretoken" :value="$store.csrftoken" type="hidden" />
+
+          <div class="ui stacked secondary segment">
+            <div class="six field required">
+              <div class="ui left icon input">
+                <i class="user icon"></i>
+                <input
+                  v-model="username_value"
+                  type="text"
+                  name="username"
+                  placeholder="Utilisateur"
+                />
+              </div>
+            </div>
+            <div class="six field required">
+              <div class="ui left icon input">
+                <i class="lock icon"></i>
+                <input
+                  v-model="password_value"
+                  type="password"
+                  name="password"
+                  placeholder="Mot de passe"
+                />
+              </div>
+            </div>
+            <button class="ui fluid large teal submit button" type="submit">
+              Login
+            </button>
+          </div>
+        </form>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "Login",
+  data() {
+    return {
+      username_value: null,
+      password_value: null,
+      logged: false,
+      form: {
+        errors: null,
+      },
+    };
+  },
+  computed: {
+    LOGO_PATH: () => process.env.VUE_APP_LOGO_PATH,
+    APPLICATION_NAME: () => process.env.VUE_APP_APPLICATION_NAME,
+    APPLICATION_ABSTRACT: () => process.env.VUE_APP_APPLICATION_ABSTRACT,
+  },
+  methods: {
+    login() {
+      this.$store.dispatch("CHECK_LOGIN", {
+        username: this.username_value,
+        password: this.password_value,
+      });
+    },
+  },
+};
+</script>
\ No newline at end of file
-- 
GitLab