diff --git a/.vscode/launch.json b/.vscode/launch.json index 2809ea083b7da96d08431b4a79bef511e6309e43..b912ea4ebd915887b84fb3787cc56098cc570533 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 0000000000000000000000000000000000000000..3920d9ac878d69bc836ee66729cb1d027ed5b498 --- /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 4c494f0f08e45b6cd8c98314131f99f2e6a2ed11..0e177bcf853d222569fe1bed2d6f7e168482aa5a 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 6dbdd2a401fefe57f72c4ba809bd26bdda3a7f0f..9dfab41d54f633be81d88eff748e849409f46d95 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 c490bfda65c7f529e366156f87117d47bffadb79..008ba4250e85a4b9941a2542d44fe5b93d46ac2d 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 7113933d37f7e262cea072d17415a2c7aa8e2351..2750037b7e63c608d8a2e7ea259c144efeb1701b 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 d934a4cb1566a90c7407a20b7297e0a450c04a4f..f04f0ee829f9effc0bd6d62ff3e8ffbae3ddc3ad 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 40618d32e64e0bd97f132123d254a0c12dba649d..d7b469a3bb8818126a0cd44e269f37616061c8e8 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 0b57990f613bb17c37d2ad6832f87c762147ff3b..c8bf031a98af49170c48b4c95a0361b8bc387fd5 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 33c0d21fc81be02a286429603919486d5f7d6a42..3abddcd02c7db237d8f5b25ce21335d86023e363 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 be94d44bb21cd3e9af065cbc36643be085a8f62c..2c472140fd20a49df787e42702d8a8286e54c529 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 a2b4ea0e1408af0215daa148d48cddb7773d66ff..bf953dd7577437503a03d9c7347f197e1d7869de 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 748a948353d047e3b674557eba19bcc726cf7317..2e595cb221088b2c210b00df4fa81633a58d1685 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 4382231b7ba57a97b607f6edbbac784f63a6506c..003301d5d01a2d880526012bda73628f94daedca 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 92af623d1968f3fcb08ac6f911d5e881a8e08084..5f659bf7f69c699a172baf582600de8e74470490 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 4a2de302b74e6b77aa2183841650b8a74853a8a1..edbd6f2b065a5b0c77b5205c67d0635f6d1b3dbe 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 eb75341bcd4f331416523f1ed148f97de326ed77..6f97cd18aaf967cd5d505f724ca1fc670d2f7709 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 83952ae525c3beb0c9cf67552c121ef7192323c8..bab32ef7b264646094822aba257e1000e3b95df5 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 = [];