From 7182b73c9e3b666956c1d5e4bec1bcf83bd8c3a8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?R=C3=A9mi=20Pailharey?= <remipailharey@gmail.com>
Date: Mon, 12 Jul 2021 17:23:56 +0200
Subject: [PATCH] feat: create a new post Save a new post in a SQLite database

---
 .gitignore                        |  1 +
 .vscode/launch.json               |  1 +
 go.mod                            |  2 +
 go.sum                            | 19 +++++-----
 internal/backoffice/backoffice.go | 22 +++++++++++
 internal/post/post.go             | 35 +++++++++++++++++
 internal/rootmux/rootmux.go       |  7 ++++
 web/components/navbar/navbar.js   | 25 +++++++++----
 web/components/post/post.js       | 62 +++++++++++++++++++++++++++++++
 web/main.js                       |  6 +++
 10 files changed, 163 insertions(+), 17 deletions(-)
 create mode 100644 .gitignore
 create mode 100644 internal/backoffice/backoffice.go
 create mode 100644 internal/post/post.go
 create mode 100644 web/components/post/post.js

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..8eef7ae
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+backoffice.db
\ No newline at end of file
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 1cb8083..9263750 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -22,6 +22,7 @@
         "USERINFO_URL": "http://localhost:8090/admininfo",
         "LOGOUT_URL": "/",
         "HOSTNAME": "localhost",
+        "ADMIN_ROLE" : "ADMINS",
         "INMEMORY_TOKEN_LIFE_DAYS": "2",
         "DEBUG_MODE": "true",
         "HTTPS_PORT": "1443"
diff --git a/go.mod b/go.mod
index 4508620..d310687 100644
--- a/go.mod
+++ b/go.mod
@@ -6,4 +6,6 @@ require (
 	github.com/nicolaspernoud/vestibule v0.0.0-20210626100803-e2554e116746
 	golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e
 	golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914
+	gorm.io/driver/sqlite v1.1.4
+	gorm.io/gorm v1.20.7
 )
diff --git a/go.sum b/go.sum
index 62d5fd1..c977981 100644
--- a/go.sum
+++ b/go.sum
@@ -40,7 +40,6 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
 github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
@@ -102,26 +101,27 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m
 github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
+github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
+github.com/jinzhu/now v1.1.1 h1:g39TucaRWyV3dwDO++eEc6qf8TVIQ/Da48WmqjZ3i7E=
+github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
 github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
-github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
-github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/mattn/go-sqlite3 v1.14.5 h1:1IdxlwTNazvbKJQSxoJ5/9ECbEeaTTyeU7sEAZ5KKTQ=
+github.com/mattn/go-sqlite3 v1.14.5/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
 github.com/nicolaspernoud/vestibule v0.0.0-20210626100803-e2554e116746 h1:mMpAetOOm54X87qjKq+RiSNutdULgFWp1knqhUeYf4s=
 github.com/nicolaspernoud/vestibule v0.0.0-20210626100803-e2554e116746/go.mod h1:zQIZ4A7ZYJBcS/DBZpMadr5N8WrATlj7267VlvKSX88=
-github.com/oschwald/maxminddb-golang v1.8.0 h1:Uh/DSnGoxsyp/KYbY1AuP0tYEwfs0sCph9p/UMXK/Hk=
 github.com/oschwald/maxminddb-golang v1.8.0/go.mod h1:RXZtst0N6+FY/3qCNmZMBApR19cdQj43/NM9VkrNAis=
-github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/secure-io/sio-go v0.3.1/go.mod h1:+xbkjDzPjwh4Axd07pRKSNriS9SCiYksWnZqdnfpQxs=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
-github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -244,7 +244,6 @@ golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210608053332-aa57babbf139/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -381,12 +380,14 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
 google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gorm.io/driver/sqlite v1.1.4 h1:PDzwYE+sI6De2+mxAneV9Xs11+ZyKV6oxD3wDGkaNvM=
+gorm.io/driver/sqlite v1.1.4/go.mod h1:mJCeTFr7+crvS+TRnWc5Z3UvwxUN1BGBLMrf5LA9DYw=
+gorm.io/gorm v1.20.7 h1:rMS4CL3pNmYq1V5/X+nHHjh1Dx6dnf27+Cai5zabo+M=
+gorm.io/gorm v1.20.7/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/internal/backoffice/backoffice.go b/internal/backoffice/backoffice.go
new file mode 100644
index 0000000..f89523a
--- /dev/null
+++ b/internal/backoffice/backoffice.go
@@ -0,0 +1,22 @@
+package backoffice
+
+import (
+	"io/ioutil"
+	"net/http"
+
+	"forge.grandlyon.com/web-et-numerique/llle_project/backoffice-server/internal/post"
+)
+
+func HandleCreatePost(w http.ResponseWriter, r *http.Request) {
+	if r.Method != http.MethodPost {
+		w.WriteHeader(http.StatusMethodNotAllowed)
+		return
+	}
+
+	body, err := ioutil.ReadAll(r.Body)
+	if err != nil {
+		w.WriteHeader(http.StatusInternalServerError)
+		return
+	}
+	post.Create(string(body))
+}
diff --git a/internal/post/post.go b/internal/post/post.go
new file mode 100644
index 0000000..d135d46
--- /dev/null
+++ b/internal/post/post.go
@@ -0,0 +1,35 @@
+package post
+
+import (
+	"gorm.io/driver/sqlite"
+	"gorm.io/gorm"
+)
+
+type Post struct {
+	gorm.Model
+	Content string
+}
+
+var db *gorm.DB
+
+func init() {
+	var err error
+	db, err = gorm.Open(sqlite.Open("backoffice.db"), &gorm.Config{})
+	if err != nil {
+		panic("failed to connect database")
+	}
+	// Migrate the schema
+	db.AutoMigrate(&Post{})
+}
+
+func Exists(content string) bool {
+	var post Post
+	if err := db.Where("content = ?", content).First(&post).Error; err != nil {
+		return false
+	}
+	return true
+}
+
+func Create(content string) {
+	db.Create(&Post{Content: content})
+}
diff --git a/internal/rootmux/rootmux.go b/internal/rootmux/rootmux.go
index 48e0676..e628d18 100644
--- a/internal/rootmux/rootmux.go
+++ b/internal/rootmux/rootmux.go
@@ -2,8 +2,10 @@ package rootmux
 
 import (
 	"net/http"
+	"os"
 
 	"forge.grandlyon.com/web-et-numerique/llle_project/backoffice-server/internal/auth"
+	"forge.grandlyon.com/web-et-numerique/llle_project/backoffice-server/internal/backoffice"
 	"forge.grandlyon.com/web-et-numerique/llle_project/backoffice-server/internal/common"
 	"github.com/nicolaspernoud/vestibule/pkg/middlewares"
 )
@@ -17,11 +19,16 @@ func CreateRootMux(staticDir string) RootMux {
 
 	mainMux := http.NewServeMux()
 	m := auth.NewManager()
+
 	mainMux.HandleFunc("/OAuth2Login", m.HandleOAuth2Login)
 	mainMux.Handle("/OAuth2Callback", m.HandleOAuth2Callback())
 	mainMux.HandleFunc("/Logout", m.HandleLogout)
 	mainMux.Handle("/api/common/WhoAmI", auth.ValidateAuthMiddleware(auth.WhoAmI(), []string{"*"}, false))
 
+	adminMux := http.NewServeMux()
+	adminMux.HandleFunc("/newPost", backoffice.HandleCreatePost)
+	mainMux.Handle("/api/admin/", http.StripPrefix("/api/admin", auth.ValidateAuthMiddleware(adminMux, []string{os.Getenv("ADMIN_ROLE")}, true)))
+
 	// Serve static files falling back to serving index.html
 	mainMux.Handle("/", middlewares.NoCache(http.FileServer(&common.FallBackWrapper{Assets: http.Dir(staticDir)})))
 
diff --git a/web/components/navbar/navbar.js b/web/components/navbar/navbar.js
index c41e100..a2b4ea0 100644
--- a/web/components/navbar/navbar.js
+++ b/web/components/navbar/navbar.js
@@ -58,14 +58,23 @@ export class Navbar {
     this.menu.innerHTML = /* HTML */ `
       <div class="navbar-start">
         ${IsEmpty(this.user)
-          ? ""
-          : /* HTML */ `<a
-              id="navbar-accueil"
-              class="navbar-item"
-              href="#accueil"
-            >
-              <i class="navbar-menu-icon fas fa-home"></i>Accueil
-            </a>`}
+          ? ``
+          : /* HTML */ `
+              <a id="navbar-accueil" class="navbar-item" href="#accueil">
+                <i class="navbar-menu-icon fas fa-home"></i>Accueil
+              </a>
+              ${this.user.isAdmin
+                ? /* HTML */ `
+                    <a
+                      id="navbar-post"
+                      class="navbar-item"
+                      href="#post"
+                    >
+                      <i class="navbar-menu-icon fas fa-envelope-open-text"></i>Post
+                    </a>
+                  `
+                : ``}
+            `}
       </div>
       <div class="navbar-end">
         ${IsEmpty(this.user)
diff --git a/web/components/post/post.js b/web/components/post/post.js
new file mode 100644
index 0000000..58061be
--- /dev/null
+++ b/web/components/post/post.js
@@ -0,0 +1,62 @@
+// Imports
+import { HandleError } from "/services/common/errors.js";
+
+export async function mount(where, user) {
+  const postComponent = new Post(user);
+  await postComponent.mount(where);
+}
+
+class Post {
+  constructor(user) {
+    this.current_user = user;
+  }
+
+  // DOM elements
+  post_textarea;
+  new_post_button;
+
+  async mount(mountpoint) {
+    document.getElementById(mountpoint).innerHTML = /* HTML */ ` <div
+      class="container is-fluid"
+    >
+      <div id="textarea-control" class="control my-2">
+        <textarea
+          id="post-textarea"
+          class="textarea is-info is-medium has-fixed-size"
+          placeholder="Contenu du post"
+        ></textarea>
+      </div>
+      <button id="post-submit" class="button is-success">
+        Créer nouveau post
+      </button>
+    </div>`;
+    this.post_textarea = document.getElementById("post-textarea");
+    this.new_post_button = document.getElementById("post-submit");
+    this.new_post_button.addEventListener("click", async () => {
+      await this.submitPost();
+    });
+  }
+
+  async submitPost() {
+    const control = document.getElementById("textarea-control");
+    control.classList.add("is-loading");
+    try {
+      const response = await fetch("/api/admin/newPost", {
+        method: "POST",
+        headers: new Headers({
+          "XSRF-Token": this.current_user.xsrftoken,
+        }),
+        body: document.getElementById("post-textarea").value
+      });
+      if (response.status !== 200) {
+        throw new Error(
+          `Le post n'a pas pu être créé (code ${response.status})`
+        );
+      }
+    } catch (e) {
+      HandleError(e);
+      this.post_textarea.value = "Une erreur s'est produite !";
+    }
+    control.classList.remove("is-loading");
+  }
+}
diff --git a/web/main.js b/web/main.js
index c8ee00e..4382231 100644
--- a/web/main.js
+++ b/web/main.js
@@ -1,4 +1,5 @@
 import * as Accueil from "/components/accueil/accueil.js";
+import * as Post from "/components/post/post.js";
 import { Login } from "/components/login/login.js";
 import { Navbar } from "/components/navbar/navbar.js";
 import { AnimateCSS } from "/services/common/common.js";
@@ -30,6 +31,11 @@ async function navigate() {
         login.mount("main");
       });
       break;
+      case "#post":
+        load(mountPoint, async function () {
+          await Post.mount("main", user); 
+        });
+        break;
     default:
       load(mountPoint, async function () {
         location.hash = "#accueil";
-- 
GitLab