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