From 4e757885df2f1866facb6946c06cfba49951ee93 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?R=C3=A9mi=20Pailharey?= <rpailharey@grandlyon.com>
Date: Mon, 26 Jul 2021 10:19:47 +0200
Subject: [PATCH] refactor: use gorilla mux router

---
 .vscode/launch.json                 |   3 +-
 configs/tokenskey.json              |   3 +
 go.mod                              |   1 +
 go.sum                              |   2 +
 internal/auth/auth.go               |   8 ++
 internal/database/database.go       |  40 ++++++++--
 internal/monthlyNews/monthlyNews.go | 120 +++++++++++++++++++++-------
 internal/rootmux/rootmux.go         |  34 ++++----
 internal/rootmux/rootmux_test.go    |  12 +--
 main.go                             |   4 +-
 web/components/login/login.js       |   2 +-
 web/components/navbar/navbar.js     |   2 +-
 web/components/post/post.js         |   6 +-
 web/main.js                         |  12 +--
 web/services/auth/auth.js           |   4 +-
 web/services/common/delete.js       |   2 +-
 web/services/common/errors.js       |   4 +-
 web/services/messages/messages.js   |   2 +-
 18 files changed, 188 insertions(+), 73 deletions(-)
 create mode 100644 configs/tokenskey.json

diff --git a/.vscode/launch.json b/.vscode/launch.json
index 2809ea0..b912ea4 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -20,7 +20,7 @@
         "AUTH_URL": "http://localhost:8090/auth",
         "TOKEN_URL": "http://localhost:8090/token",
         "USERINFO_URL": "http://localhost:8090/admininfo",
-        "LOGOUT_URL": "/",
+        "LOGOUT_URL": "/public/",
         "HOSTNAME": "localhost",
         "ADMIN_ROLE" : "ADMINS",
         "INMEMORY_TOKEN_LIFE_DAYS": "2",
@@ -29,6 +29,7 @@
         "DATABASE_USER": "root",
         "DATABASE_PASSWORD": "password",
         "DATABASE_NAME": "backoffice",
+        "DATABASE_HOST": "127.0.0.1",
       },
       "showLog": true
     },
diff --git a/configs/tokenskey.json b/configs/tokenskey.json
new file mode 100644
index 0000000..3920d9a
--- /dev/null
+++ b/configs/tokenskey.json
@@ -0,0 +1,3 @@
+{
+	"Key": "2ioa6+gmlILIQcsG/HmBqDSsszPCe4GWKjCfyrtgLek="
+}
\ No newline at end of file
diff --git a/go.mod b/go.mod
index 4c494f0..0e177bc 100644
--- a/go.mod
+++ b/go.mod
@@ -3,6 +3,7 @@ module forge.grandlyon.com/web-et-numerique/llle_project/backoffice-server
 go 1.15
 
 require (
+	github.com/gorilla/mux v1.8.0
 	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
diff --git a/go.sum b/go.sum
index 6dbdd2a..9dfab41 100644
--- a/go.sum
+++ b/go.sum
@@ -100,6 +100,8 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
 github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
+github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
+github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
 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=
diff --git a/internal/auth/auth.go b/internal/auth/auth.go
index c490bfd..008ba42 100644
--- a/internal/auth/auth.go
+++ b/internal/auth/auth.go
@@ -52,6 +52,14 @@ type TokenData struct {
 	XSRFToken        string `json:"xsrftoken,omitempty"`
 }
 
+func AdminAuthMiddleware(next http.Handler) http.Handler {
+	return ValidateAuthMiddleware(next, []string{os.Getenv("ADMIN_ROLE")}, true)
+}
+
+func CommonAuthMiddleware(next http.Handler) http.Handler {
+	return ValidateAuthMiddleware(next, []string{"*"}, false)
+}
+
 // ValidateAuthMiddleware validates that the token is valid and that the user has the correct roles
 func ValidateAuthMiddleware(next http.Handler, allowedRoles []string, checkXSRF bool) http.Handler {
 	roleChecker := func(w http.ResponseWriter, r *http.Request) {
diff --git a/internal/database/database.go b/internal/database/database.go
index 7113933..2750037 100644
--- a/internal/database/database.go
+++ b/internal/database/database.go
@@ -2,6 +2,7 @@ package database
 
 import (
 	"fmt"
+	"log"
 
 	"forge.grandlyon.com/web-et-numerique/llle_project/backoffice-server/internal/common"
 	"gorm.io/driver/mysql"
@@ -11,8 +12,8 @@ import (
 
 type MonthlyReport struct {
 	gorm.Model
-	Month       int
 	Year        int
+	Month       int
 	ContentType string
 	Content     string
 }
@@ -22,17 +23,21 @@ var (
 	dbUser     = common.StringValueFromEnv("DATABASE_USER", "")
 	dbPassword = common.StringValueFromEnv("DATABASE_PASSWORD", "")
 	dbName     = common.StringValueFromEnv("DATABASE_NAME", "")
+	dbHost     = common.StringValueFromEnv("DATABASE_HOST", "")
 )
 
 func init() {
 	var err error
+	// If database crendential missing, use SQL Lite (used for testing purposes)
+
 	if dbUser == "" || dbPassword == "" || dbName == "" {
 		db, err = gorm.Open(sqlite.Open("backoffice.db"), &gorm.Config{})
 		if err != nil {
 			panic("failed to connect database")
 		}
 	} else {
-		dsn := fmt.Sprintf("%v:%v@tcp(127.0.0.1:3306)/%v?charset=utf8mb4&parseTime=True&loc=Local", dbUser, dbPassword, dbName)
+		dsn := fmt.Sprintf("%v:%v@tcp(%v:3306)/%v?charset=utf8mb4&parseTime=True&loc=Local", dbUser, dbPassword, dbHost, dbName)
+		log.Println(dsn)
 		db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
 		if err != nil {
 			panic("failed to connect database")
@@ -42,16 +47,39 @@ func init() {
 	db.AutoMigrate(&MonthlyReport{})
 }
 
-func Exists(month int, year int, contentType string, content string) bool {
+func Exists(year int, month int, contentType string, content string) bool {
 	var monthlyReport MonthlyReport
-	if err := db.Where("month = ? AND year = ? AND content_type = ? AND content = ?", content).First(&monthlyReport).Error; err != nil {
+	if err := db.Where("year = ? AND month = ? AND content_type = ? AND content = ?", year, month, contentType, content).First(&monthlyReport).Error; err != nil {
 		return false
 	}
 	return true
 }
 
-func Create(month int, year int, contentType string, content string) error {
-	if err := db.Create(&MonthlyReport{Month: month, Year: year, ContentType: contentType, Content: content}).Error; err != nil {
+func Get(year int, month int, contentType string) MonthlyReport {
+	var monthlyReport MonthlyReport
+	db.Where("year = ? AND month = ? AND content_type = ?", year, month, contentType).First(&monthlyReport)
+	return monthlyReport
+}
+
+func Create(year int, month int, contentType string, content string) error {
+	if err := db.Create(&MonthlyReport{Year: year, Month: month, ContentType: contentType, Content: content}).Error; err != nil {
+		return err
+	}
+	return nil
+}
+
+func Delete(year int, month int) error {
+	var monthlyReport MonthlyReport
+
+	if err := db.Where("year = ? AND month = ?", year, month).Delete(&monthlyReport).Error; err != nil {
+		return err
+	}
+	return nil
+}
+
+func Update(year int, month int, contentType string, content string) error {
+	var monthlyReport MonthlyReport
+	if err := db.Where("year = ? AND month = ? AND content_type = ? AND content = ?", year, month, contentType, content).First(&monthlyReport).Error; err != nil {
 		return err
 	}
 	return nil
diff --git a/internal/monthlyNews/monthlyNews.go b/internal/monthlyNews/monthlyNews.go
index d934a4c..f04f0ee 100644
--- a/internal/monthlyNews/monthlyNews.go
+++ b/internal/monthlyNews/monthlyNews.go
@@ -5,23 +5,51 @@ import (
 	"fmt"
 	"log"
 	"net/http"
+	"strconv"
 
 	"forge.grandlyon.com/web-et-numerique/llle_project/backoffice-server/internal/database"
+	"github.com/gorilla/mux"
 )
 
-func ProcessMonthlyNews(w http.ResponseWriter, r *http.Request) {
-	switch method := r.Method; method {
-	case "GET":
-		SendMonthlyNews(w, r)
-	case "POST":
-		AddMonthlyNews(w, r)
-	case "DELETE":
-		DeleteMonthlyNews(w, r)
-	case "PUT":
-		UpdateMonthlyNews(w, r)
-	default:
-		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
+type MonthlyNews struct {
+	Month  int    `json:"month"`
+	Year   int    `json:"year"`
+	Header string `json:"header"`
+	Quote  string `json:"quote"`
+}
+
+func GetAllMonthlyNews(w http.ResponseWriter, r *http.Request) {
+	log.Printf("| all monthly news | %v", r.RemoteAddr)
+}
+
+func GetSingleMonthlyNews(w http.ResponseWriter, r *http.Request) {
+	vars := mux.Vars(r)
+	yearStr := vars["year"]
+	monthStr := vars["month"]
+
+	if yearStr == "" || monthStr == "" {
+		http.Error(w, "missing query element", http.StatusBadRequest)
+		return
+	}
+
+	year, err := strconv.Atoi(monthStr)
+	if err != nil {
+		http.Error(w, "year is not an integer", http.StatusBadRequest)
+		return
+	}
+	month, err := strconv.Atoi(monthStr)
+	if err != nil {
+		http.Error(w, "month is not an integer", http.StatusBadRequest)
+		return
 	}
+
+	header := database.Get(year, month, "header").Content
+	quote := database.Get(year, month, "quote").Content
+
+	w.Header().Set("Content-Type", "application/json")
+	monthlyNews := MonthlyNews{Year: year, Month: month, Header: header, Quote: quote}
+	log.Println(monthlyNews)
+	json.NewEncoder(w).Encode(monthlyNews)
 }
 
 func AddMonthlyNews(w http.ResponseWriter, r *http.Request) {
@@ -31,13 +59,6 @@ func AddMonthlyNews(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	type MonthlyNews struct {
-		Month  int    `json:"month"`
-		Year   int    `json:"year"`
-		Header string `json:"header"`
-		Quote  string `json:"quote"`
-	}
-
 	decoder := json.NewDecoder(r.Body)
 	var monthlyNews MonthlyNews
 	err := decoder.Decode(&monthlyNews)
@@ -45,27 +66,72 @@ func AddMonthlyNews(w http.ResponseWriter, r *http.Request) {
 		fmt.Println(err)
 	}
 
-	err = database.Create(monthlyNews.Month, monthlyNews.Year, "header", monthlyNews.Header)
+	err = database.Create(monthlyNews.Year, monthlyNews.Month, "header", monthlyNews.Header)
 	if err != nil {
 		w.WriteHeader(http.StatusInternalServerError)
 		return
 	}
 
-	err = database.Create(monthlyNews.Month, monthlyNews.Year, "quote", monthlyNews.Quote)
+	err = database.Create(monthlyNews.Year, monthlyNews.Month, "quote", monthlyNews.Quote)
 	if err != nil {
 		w.WriteHeader(http.StatusInternalServerError)
 		return
 	}
+	w.WriteHeader(http.StatusCreated)
 
 	log.Printf("| new monthly news | %v", r.RemoteAddr)
 }
 
-func SendMonthlyNews(w http.ResponseWriter, r *http.Request) {}
+func UpdateMonthlyNews(w http.ResponseWriter, r *http.Request) {
+	if r.Body == http.NoBody {
+		http.Error(w, "request body is empty", http.StatusBadRequest)
+		return
+	}
+
+	decoder := json.NewDecoder(r.Body)
+	var monthlyNews MonthlyNews
+	err := decoder.Decode(&monthlyNews)
+	if err != nil {
+		fmt.Println(err)
+	}
+
+	err = database.Update(monthlyNews.Year, monthlyNews.Month, "header", monthlyNews.Header)
+	if err != nil {
+		w.WriteHeader(http.StatusInternalServerError)
+		return
+	}
+
+	err = database.Update(monthlyNews.Year, monthlyNews.Month, "quote", monthlyNews.Quote)
+	if err != nil {
+		w.WriteHeader(http.StatusInternalServerError)
+		return
+	}
+}
+
+func DeleteMonthlyNews(w http.ResponseWriter, r *http.Request) {
+	vars := mux.Vars(r)
+	yearStr := vars["year"]
+	monthStr := vars["month"]
 
-// TODO:
-func UpdateMonthlyNews(w http.ResponseWriter, r *http.Request) {}
+	if yearStr == "" || monthStr == "" {
+		http.Error(w, "missing query element", http.StatusBadRequest)
+		return
+	}
 
-// TODO:
-func DeleteMonthlyNews(w http.ResponseWriter, r *http.Request) {}
+	year, err := strconv.Atoi(monthStr)
+	if err != nil {
+		http.Error(w, "year is not an integer", http.StatusBadRequest)
+		return
+	}
+	month, err := strconv.Atoi(monthStr)
+	if err != nil {
+		http.Error(w, "month is not an integer", http.StatusBadRequest)
+		return
+	}
 
-// TODO:
+	err = database.Delete(year, month)
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		return
+	}
+}
diff --git a/internal/rootmux/rootmux.go b/internal/rootmux/rootmux.go
index 40618d3..d7b469a 100644
--- a/internal/rootmux/rootmux.go
+++ b/internal/rootmux/rootmux.go
@@ -2,35 +2,39 @@ 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/common"
 	"forge.grandlyon.com/web-et-numerique/llle_project/backoffice-server/internal/monthlyNews"
+	"github.com/gorilla/mux"
 	"github.com/nicolaspernoud/vestibule/pkg/middlewares"
 )
 
 type RootMux struct {
-	Mux     http.Handler
+	Router  *mux.Router
 	Manager *auth.Manager
 }
 
 func CreateRootMux(staticDir string) RootMux {
 
-	mainMux := http.NewServeMux()
+	r := mux.NewRouter()
 	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("/monthlyNews/", monthlyNews.ProcessMonthlyNews)
-	mainMux.Handle("/api/admin/", http.StripPrefix("/api/admin", auth.ValidateAuthMiddleware(adminMux, []string{os.Getenv("ADMIN_ROLE")}, true)))
-
+	r.HandleFunc("/OAuth2Login", m.HandleOAuth2Login)
+	r.Handle("/OAuth2Callback", m.HandleOAuth2Callback())
+	r.HandleFunc("/Logout", m.HandleLogout)
+	r.Handle("/api/common/WhoAmI", auth.ValidateAuthMiddleware(auth.WhoAmI(), []string{"*"}, false))
 	// Serve static files falling back to serving index.html
-	mainMux.Handle("/", middlewares.NoCache(http.FileServer(&common.FallBackWrapper{Assets: http.Dir(staticDir)})))
-
-	return RootMux{mainMux, &m}
+	fs := http.StripPrefix("/public/", middlewares.NoCache(http.FileServer(&common.FallBackWrapper{Assets: http.Dir(staticDir)})))
+	r.PathPrefix("/public/").Handler(fs)
+	http.Handle("/", r)
+
+	apiAdmin := r.PathPrefix("/api/admin").Subrouter()
+	apiAdmin.Use(auth.AdminAuthMiddleware)
+	apiAdmin.HandleFunc("/monthlyNews", monthlyNews.AddMonthlyNews).Methods(http.MethodPost)
+	apiAdmin.HandleFunc("/monthlyNews/{year}/{month}", monthlyNews.DeleteMonthlyNews).Methods(http.MethodDelete)
+	apiAdmin.HandleFunc("/monthlyNews/{year}/{month}", monthlyNews.GetSingleMonthlyNews).Methods(http.MethodGet)
+	apiAdmin.HandleFunc("/monthlyNews", monthlyNews.GetAllMonthlyNews).Methods(http.MethodGet)
+
+	return RootMux{r, &m}
 }
diff --git a/internal/rootmux/rootmux_test.go b/internal/rootmux/rootmux_test.go
index 0b57990..c8bf031 100644
--- a/internal/rootmux/rootmux_test.go
+++ b/internal/rootmux/rootmux_test.go
@@ -69,7 +69,7 @@ func unloggedTests(t *testing.T) {
 	defer ts.Close() // Close the tester
 
 	// Try to create a monthlyNews (must fail)
-	do("POST", "/api/admin/monthlyNews/", noH, newMonthlyNews, http.StatusUnauthorized, "error extracting token")
+	do("POST", "/api/admin/monthlyNews", noH, newMonthlyNews, http.StatusUnauthorized, "error extracting token")
 }
 
 /**
@@ -86,9 +86,11 @@ func adminTests(t *testing.T) {
 		json.Unmarshal([]byte(response), &token)
 		xsrfHeader := map[string]string{"XSRF-TOKEN": token.XSRFToken}
 		// Try to create a monthly news without the XSRF-TOKEN (must fail)
-		do("POST", "/api/admin/monthlyNews/", noH, newMonthlyNews, http.StatusUnauthorized, "XSRF")
+		do("POST", "/api/admin/monthlyNews", noH, newMonthlyNews, http.StatusUnauthorized, "XSRF protection triggered")
+		// Try to create a monthly news without body (must fail)
+		do("POST", "/api/admin/monthlyNews", xsrfHeader, "", http.StatusBadRequest, "request body is empty")
 		// Try to create an app (must pass)
-		do("POST", "/api/admin/monthlyNews/", xsrfHeader, newMonthlyNews, http.StatusOK, "")
+		do("POST", "/api/admin/monthlyNews", xsrfHeader, newMonthlyNews, http.StatusCreated, "")
 	}
 	// Try to login (must pass)
 	do("GET", "/OAuth2Login", noH, "", http.StatusOK, "<!DOCTYPE html>")
@@ -97,13 +99,13 @@ func adminTests(t *testing.T) {
 	// Try to logout (must pass)
 	do("GET", "/Logout", noH, "", http.StatusOK, "Logout OK")
 	// Try to create a monthly news again (must fail)
-	do("GET", "/api/admin/monthlyNews/", noH, "", http.StatusUnauthorized, "error extracting token")
+	do("GET", "/api/admin/monthlyNews", noH, "", http.StatusUnauthorized, "error extracting token")
 }
 
 func createTester(t *testing.T) (*httptest.Server, tester.DoFn, tester.DoFn) {
 	// Create the server
 	mux := CreateRootMux("../../web")
-	ts := httptest.NewServer(mux.Mux)
+	ts := httptest.NewServer(mux.Router)
 	url, _ := url.Parse(ts.URL)
 	port := url.Port()
 	mux.Manager.Config.RedirectURL = "http://" + os.Getenv("HOSTNAME") + ":" + port + "/OAuth2Callback"
diff --git a/main.go b/main.go
index 33c0d21..3abddcd 100644
--- a/main.go
+++ b/main.go
@@ -51,7 +51,7 @@ func main() {
 		mockAPIPort := ":8091"
 		go http.ListenAndServe(mockAPIPort, mocks.CreateMockAPI())
 		fmt.Println("Mock API server Listening on: http://localhost" + mockAPIPort)
-		log.Fatal(http.ListenAndServeTLS(":"+strconv.Itoa(httpsPort), "./dev_certificates/localhost.crt", "./dev_certificates/localhost.key", rootMux.Mux))
+		log.Fatal(http.ListenAndServeTLS(":"+strconv.Itoa(httpsPort), "./dev_certificates/localhost.crt", "./dev_certificates/localhost.key", rootMux.Router))
 	} else {
 		certManager := autocert.Manager{
 			Prompt: autocert.AcceptTOS,
@@ -60,7 +60,7 @@ func main() {
 
 		server := &http.Server{
 			Addr:    ":" + strconv.Itoa(httpsPort),
-			Handler: rootMux.Mux,
+			Handler: rootMux.Router,
 			TLSConfig: &tls.Config{
 				GetCertificate: certManager.GetCertificate,
 				MinVersion:     tls.VersionTLS12,
diff --git a/web/components/login/login.js b/web/components/login/login.js
index be94d44..2c47214 100644
--- a/web/components/login/login.js
+++ b/web/components/login/login.js
@@ -1,6 +1,6 @@
 // Imports
 // import { loginModes } from "/assets/brand/brand.js";
-import { IsEmpty } from "/services/common/common.js";
+import { IsEmpty } from "/public/services/common/common.js";
 
 export class Login {
   constructor(user, navbar) {
diff --git a/web/components/navbar/navbar.js b/web/components/navbar/navbar.js
index a2b4ea0..bf953dd 100644
--- a/web/components/navbar/navbar.js
+++ b/web/components/navbar/navbar.js
@@ -1,6 +1,6 @@
 // Imports
 //import * as brand from "/assets/brand/brand.js";
-import { AnimateCSS, IsEmpty } from "/services/common/common.js";
+import { AnimateCSS, IsEmpty } from "/public/services/common/common.js";
 
 export class Navbar {
   constructor(user) {
diff --git a/web/components/post/post.js b/web/components/post/post.js
index 748a948..2e595cb 100644
--- a/web/components/post/post.js
+++ b/web/components/post/post.js
@@ -1,6 +1,6 @@
 // Imports
-import { HandleError } from "/services/common/errors.js";
-import * as Messages from "/services/messages/messages.js";
+import { HandleError } from "/public/services/common/errors.js";
+import * as Messages from "/public/services/messages/messages.js";
 
 export async function mount(where, user) {
   const postComponent = new Post(user);
@@ -50,7 +50,7 @@ class Post {
           quote: document.getElementById("quote").value,
         })
       });
-      if (response.status !== 200) {
+      if (response.status !== 201) {
         throw new Error(
           `Le post n'a pas pu être créé (code ${response.status})`
         );
diff --git a/web/main.js b/web/main.js
index 4382231..003301d 100644
--- a/web/main.js
+++ b/web/main.js
@@ -1,9 +1,9 @@
-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";
-import * as Auth from "/services/auth/auth.js";
+import * as Accueil from "/public/components/accueil/accueil.js";
+import * as Post from "/public/components/post/post.js";
+import { Login } from "/public/components/login/login.js";
+import { Navbar } from "/public/components/navbar/navbar.js";
+import { AnimateCSS } from "/public/services/common/common.js";
+import * as Auth from "/public/services/auth/auth.js";
 
 const mountPoint = document.getElementById("main");
 const spinner = document.getElementById("spinner");
diff --git a/web/services/auth/auth.js b/web/services/auth/auth.js
index 92af623..5f659bf 100644
--- a/web/services/auth/auth.js
+++ b/web/services/auth/auth.js
@@ -1,6 +1,6 @@
 // Imports
-import { HandleError } from "/services/common/errors.js";
-import { IsEmpty } from "/services/common/common.js";
+import { HandleError } from "/public/services/common/errors.js";
+import { IsEmpty } from "/public/services/common/common.js";
 
 // Local variables
 let user = {};
diff --git a/web/services/common/delete.js b/web/services/common/delete.js
index 4a2de30..edbd6f2 100644
--- a/web/services/common/delete.js
+++ b/web/services/common/delete.js
@@ -1,5 +1,5 @@
 // Imports
-import { AnimateCSS } from "/services/common/common.js";
+import { AnimateCSS } from "/public/services/common/common.js";
 
 export class Delete {
   constructor(okFunction, what) {
diff --git a/web/services/common/errors.js b/web/services/common/errors.js
index eb75341..6f97cd1 100644
--- a/web/services/common/errors.js
+++ b/web/services/common/errors.js
@@ -1,6 +1,6 @@
 // Imports
-import * as Messages from "/services/messages/messages.js";
-import { DeleteUser } from "/services/auth/auth.js";
+import * as Messages from "/public/services/messages/messages.js";
+import { DeleteUser } from "/public/services/auth/auth.js";
 
 export function HandleError(error) {
   if (error.message.includes("401")) {
diff --git a/web/services/messages/messages.js b/web/services/messages/messages.js
index 83952ae..bab32ef 100644
--- a/web/services/messages/messages.js
+++ b/web/services/messages/messages.js
@@ -1,5 +1,5 @@
 // Imports
-import { AnimateCSS } from "/services/common/common.js";
+import { AnimateCSS } from "/public/services/common/common.js";
 
 let offset = 0;
 let messages = [];
-- 
GitLab