From 6425603d68911aab5e597ada1a12ba3b502fc9dd Mon Sep 17 00:00:00 2001 From: Tovi Jaeschke-Rogers Date: Wed, 30 Mar 2022 22:29:05 +1030 Subject: [PATCH] Add router authetication and redirection Add admin top navbar, and start adding datalists --- Api/Auth/Login.go | 13 + Api/Auth/Me.go | 36 +++ Api/Auth/Session.go | 4 +- Api/Routes.go | 41 ++- Api/Users.go | 11 +- Api/Users_test.go | 2 +- Database/Users.go | 9 +- Frontend/GetFrontendAssets_dev.go | 13 + Frontend/GetFrontendAssets_prod.go | 26 ++ Frontend/Routes.go | 49 ++++ Frontend/vue/package-lock.json | 236 ++++++++++++++++-- Frontend/vue/package.json | 14 +- Frontend/vue/src/App.vue | 16 +- Frontend/vue/src/assets/css/admin.css | 0 .../vue/src/components/admin/AdminList.vue | 55 ++++ .../vue/src/components/admin/AdminLogin.vue | 107 ++++++++ .../vue/src/components/admin/AdminNavbar.vue | 84 +++++++ .../vue/src/components/admin/AdminSignup.vue | 141 +++++++++++ .../components/admin/users/AdminUsersList.vue | 32 +++ Frontend/vue/src/main.js | 19 +- Frontend/vue/src/router/index.js | 61 +++++ Frontend/vue/src/store/admin/index.js | 28 +++ Frontend/vue/src/utils/http/index.js | 27 ++ Util/EmailValidation.go | 10 + main.go | 29 +-- 25 files changed, 970 insertions(+), 93 deletions(-) create mode 100644 Api/Auth/Me.go create mode 100644 Frontend/GetFrontendAssets_dev.go create mode 100644 Frontend/GetFrontendAssets_prod.go create mode 100644 Frontend/Routes.go create mode 100644 Frontend/vue/src/assets/css/admin.css create mode 100644 Frontend/vue/src/components/admin/AdminList.vue create mode 100644 Frontend/vue/src/components/admin/AdminLogin.vue create mode 100644 Frontend/vue/src/components/admin/AdminNavbar.vue create mode 100644 Frontend/vue/src/components/admin/AdminSignup.vue create mode 100644 Frontend/vue/src/components/admin/users/AdminUsersList.vue create mode 100644 Frontend/vue/src/router/index.js create mode 100644 Frontend/vue/src/store/admin/index.js create mode 100644 Frontend/vue/src/utils/http/index.js create mode 100644 Util/EmailValidation.go diff --git a/Api/Auth/Login.go b/Api/Auth/Login.go index 0ae4a0a..03f72e2 100644 --- a/Api/Auth/Login.go +++ b/Api/Auth/Login.go @@ -2,11 +2,13 @@ package Auth import ( "encoding/json" + "log" "net/http" "time" "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Database" "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Models" + "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Util" "github.com/gofrs/uuid" ) @@ -22,6 +24,7 @@ func Login(w http.ResponseWriter, r *http.Request) { userData Models.User sessionToken uuid.UUID expiresAt time.Time + returnJson []byte err error ) @@ -62,5 +65,15 @@ func Login(w http.ResponseWriter, r *http.Request) { Expires: expiresAt, }) + userData.Password = "" + + returnJson, err = json.MarshalIndent(userData, "", " ") + if err != nil { + log.Printf("An error occured: %s\n", err.Error()) + Util.JsonReturn(w, 500, "An error occured") + return + } + w.WriteHeader(http.StatusOK) + w.Write(returnJson) } diff --git a/Api/Auth/Me.go b/Api/Auth/Me.go new file mode 100644 index 0000000..c99073d --- /dev/null +++ b/Api/Auth/Me.go @@ -0,0 +1,36 @@ +package Auth + +import ( + "encoding/json" + "log" + "net/http" + + "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Models" + "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Util" +) + +func Me(w http.ResponseWriter, r *http.Request) { + var ( + userData Models.User + returnJson []byte + err error + ) + + userData, err = CheckCookieCurrentUser(w, r) + if err != nil { + Util.JsonReturn(w, 401, "NO ERROR") + return + } + + returnJson, err = json.MarshalIndent(userData, "", " ") + if err != nil { + log.Printf("An error occured: %s\n", err.Error()) + Util.JsonReturn(w, 500, "An error occured") + return + } + + // Return updated json + w.WriteHeader(http.StatusOK) + w.Write(returnJson) + +} diff --git a/Api/Auth/Session.go b/Api/Auth/Session.go index b647376..526a026 100644 --- a/Api/Auth/Session.go +++ b/Api/Auth/Session.go @@ -5,8 +5,8 @@ import ( "net/http" "time" + "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Database" "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Models" - "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Util" ) var ( @@ -66,7 +66,7 @@ func CheckCookieCurrentUser(w http.ResponseWriter, r *http.Request) (Models.User return userData, err } - userData, err = Util.GetUserById(w, r) + userData, err = Database.GetUserById(userSession.UserID) if err != nil { return userData, err } diff --git a/Api/Routes.go b/Api/Routes.go index e71544f..7fc3acc 100644 --- a/Api/Routes.go +++ b/Api/Routes.go @@ -8,40 +8,39 @@ import ( "github.com/gorilla/mux" ) -func InitApiEndpoints() *mux.Router { +func InitApiEndpoints(router *mux.Router) { var ( - router *mux.Router + api *mux.Router ) log.Println("Initializing API routes...") - router = mux.NewRouter() + api = router.PathPrefix("/api/v1/").Subrouter() // Define routes for posts api - router.HandleFunc("/post", getPosts).Methods("GET") - router.HandleFunc("/post", createPost).Methods("POST") - router.HandleFunc("/post/{postID}", getPost).Methods("GET") - router.HandleFunc("/post/{postID}", updatePost).Methods("PUT") - router.HandleFunc("/post/{postID}", deletePost).Methods("DELETE") + api.HandleFunc("/post", getPosts).Methods("GET") + api.HandleFunc("/post", createPost).Methods("POST") + api.HandleFunc("/post/{postID}", getPost).Methods("GET") + api.HandleFunc("/post/{postID}", updatePost).Methods("PUT") + api.HandleFunc("/post/{postID}", deletePost).Methods("DELETE") - router.HandleFunc("/frontPagePosts", getFrontPagePosts).Methods("GET") + api.HandleFunc("/frontPagePosts", getFrontPagePosts).Methods("GET") - router.HandleFunc("/post/{postID}/image", createPostImage).Methods("POST") - router.HandleFunc("/post/{postID}/image/{imageID}", deletePostImage).Methods("DELETE") + api.HandleFunc("/post/{postID}/image", createPostImage).Methods("POST") + api.HandleFunc("/post/{postID}/image/{imageID}", deletePostImage).Methods("DELETE") // Define routes for users api - router.HandleFunc("/admin/user", getUsers).Methods("GET") - router.HandleFunc("/admin/user", createUser).Methods("POST") - router.HandleFunc("/admin/user/{userID}", getUser).Methods("GET") - router.HandleFunc("/admin/user/{userID}", updatePost).Methods("PUT") - router.HandleFunc("/admin/user/{userID}", deletePost).Methods("DELETE") - router.HandleFunc("/admin/user/{userID}/update-password", Auth.UpdatePassword).Methods("PUT") + api.HandleFunc("/admin/user", getUsers).Methods("GET") + api.HandleFunc("/admin/user", createUser).Methods("POST") + api.HandleFunc("/admin/user/{userID}", getUser).Methods("GET") + api.HandleFunc("/admin/user/{userID}", updatePost).Methods("PUT") + api.HandleFunc("/admin/user/{userID}", deletePost).Methods("DELETE") + api.HandleFunc("/admin/user/{userID}/update-password", Auth.UpdatePassword).Methods("PUT") // Define routes for authentication - router.HandleFunc("/admin/login", Auth.Login).Methods("POST") - router.HandleFunc("/admin/logout", Auth.Logout).Methods("GET") + api.HandleFunc("/admin/login", Auth.Login).Methods("POST") + api.HandleFunc("/admin/logout", Auth.Logout).Methods("GET") + api.HandleFunc("/admin/me", Auth.Me).Methods("GET") //router.PathPrefix("/").Handler(http.StripPrefix("/images/", http.FileServer(http.Dir("./uploads")))) - - return router } diff --git a/Api/Users.go b/Api/Users.go index 9ce2e79..5a7dd06 100644 --- a/Api/Users.go +++ b/Api/Users.go @@ -116,8 +116,17 @@ func createUser(w http.ResponseWriter, r *http.Request) { return } + if userData.FirstName == "" || + userData.LastName == "" || + userData.Email == "" || + userData.Password == "" || + userData.ConfirmPassword == "" { + Util.JsonReturn(w, http.StatusUnprocessableEntity, "Invalid data") + return + } + err = Database.CheckUniqueEmail(userData.Email) - if err != nil { + if err != nil || !Util.IsEmailValid(userData.Email) { Util.JsonReturn(w, 405, "invalid_email") return } diff --git a/Api/Users_test.go b/Api/Users_test.go index 1125999..0aeb4ea 100644 --- a/Api/Users_test.go +++ b/Api/Users_test.go @@ -190,7 +190,7 @@ func Test_getUsers(t *testing.T) { createTestUser(true) } - req, err := http.NewRequest("GET", ts.URL+"/user?page=1&pageSize=10", nil) + req, err := http.NewRequest("GET", ts.URL+"/user?page=0&pageSize=10", nil) if err != nil { t.Errorf("Expected nil, recieved %s", err.Error()) diff --git a/Database/Users.go b/Database/Users.go index 7dc8b03..8ea9680 100644 --- a/Database/Users.go +++ b/Database/Users.go @@ -44,10 +44,6 @@ func GetUsers(page, pageSize int) ([]Models.User, error) { err error ) - if page == 0 { - page = 1 - } - switch { case pageSize > 100: pageSize = 100 @@ -55,8 +51,9 @@ func GetUsers(page, pageSize int) ([]Models.User, error) { pageSize = 10 } - err = DB.Offset(page). - Limit(pageSize). + err = DB.Model(Models.User{}). + Offset(0). + Limit(10). Find(&users). Error diff --git a/Frontend/GetFrontendAssets_dev.go b/Frontend/GetFrontendAssets_dev.go new file mode 100644 index 0000000..168e64a --- /dev/null +++ b/Frontend/GetFrontendAssets_dev.go @@ -0,0 +1,13 @@ +//go:build !prod +// +build !prod + +package Frontend + +import ( + "io/fs" + "os" +) + +func GetFrontendAssets() fs.FS { + return os.DirFS("Frontend/vue/dist") +} diff --git a/Frontend/GetFrontendAssets_prod.go b/Frontend/GetFrontendAssets_prod.go new file mode 100644 index 0000000..085623a --- /dev/null +++ b/Frontend/GetFrontendAssets_prod.go @@ -0,0 +1,26 @@ +//go:build prod +// +build prod + +package Frontend + +import ( + "embed" + "io/fs" + "log" +) + +//go:embed Frontend/vue/dist +var frontend embed.FS + +func GetFrontendAssets() fs.FS { + var ( + stripped fs.FS + err error + ) + stripped, err = fs.Sub(frontend, "Frontend/vue/dist") + if err != nil { + log.Fatalln(err) + } + + return stripped +} diff --git a/Frontend/Routes.go b/Frontend/Routes.go new file mode 100644 index 0000000..11b33f0 --- /dev/null +++ b/Frontend/Routes.go @@ -0,0 +1,49 @@ +package Frontend + +import ( + "io/fs" + "net/http" + + "github.com/gorilla/mux" +) + +const ( + indexPath = "Frontend/vue/dist/index.html" +) + +var ( + routes []string = []string{ + "/admin/login", + "/admin/signup", + "/admin/users", + } +) + +func indexHandler(entrypoint string) func(w http.ResponseWriter, r *http.Request) { + fn := func(w http.ResponseWriter, r *http.Request) { + http.ServeFile(w, r, entrypoint) + } + + return http.HandlerFunc(fn) +} + +func InitFrontendRoutes(router *mux.Router) { + var ( + frontendFS http.Handler + stripped fs.FS + route string + ) + + stripped = GetFrontendAssets() + + frontendFS = http.FileServer(http.FS(stripped)) + + for _, route = range routes { + router. + PathPrefix(route). + HandlerFunc(indexHandler(indexPath)) + } + + router.PathPrefix("/").Handler(frontendFS) + +} diff --git a/Frontend/vue/package-lock.json b/Frontend/vue/package-lock.json index e14c7c3..702f696 100644 --- a/Frontend/vue/package-lock.json +++ b/Frontend/vue/package-lock.json @@ -8,8 +8,15 @@ "name": "vue", "version": "0.1.0", "dependencies": { + "axios": "^0.26.1", + "bootstrap": "^5.1.3", "core-js": "^3.8.3", - "vue": "^3.2.13" + "vue": "^3.2.13", + "vue-axios": "^3.4.1", + "vue-router": "^4.0.13", + "vue3-cookies": "^1.0.6", + "vuex": "^4.0.2", + "vuex-persistedstate": "^4.1.0" }, "devDependencies": { "@babel/core": "^7.12.16", @@ -18,7 +25,7 @@ "@vue/cli-plugin-eslint": "~5.0.0", "@vue/cli-service": "~5.0.0", "eslint": "^7.32.0", - "eslint-plugin-vue": "^8.0.3" + "eslint-plugin-vue": "^8.5.0" } }, "node_modules/@ampproject/remapping": { @@ -1832,6 +1839,16 @@ "integrity": "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==", "dev": true }, + "node_modules/@popperjs/core": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.4.tgz", + "integrity": "sha512-q/ytXxO5NKvyT37pmisQAItCFqA7FD/vNb8dgaJy3/630Fsc+Mz9/9f2SziBoIZ30TJooXyTwZmhi1zjXmObYg==", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, "node_modules/@sideway/address": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.3.tgz", @@ -2778,6 +2795,11 @@ "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", "dev": true }, + "node_modules/@vue/devtools-api": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.1.3.tgz", + "integrity": "sha512-79InfO2xHv+WHIrH1bHXQUiQD/wMls9qBk6WVwGCbdwP7/3zINtvqPNMtmSHXsIKjvUAHc8L0ouOj6ZQQRmcXg==" + }, "node_modules/@vue/reactivity": { "version": "3.2.31", "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.31.tgz", @@ -3336,6 +3358,14 @@ "postcss": "^8.1.0" } }, + "node_modules/axios": { + "version": "0.26.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz", + "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", + "dependencies": { + "follow-redirects": "^1.14.8" + } + }, "node_modules/babel-loader": { "version": "8.2.3", "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.3.tgz", @@ -3535,6 +3565,18 @@ "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", "dev": true }, + "node_modules/bootstrap": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.1.3.tgz", + "integrity": "sha512-fcQztozJ8jToQWXxVuEyXWW+dSo8AiXWKwiSSrKWsRB/Qt+Ewwza+JWoLKiTuQLaEPhdNAJ7+Dosc9DOIqNy7Q==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + }, + "peerDependencies": { + "@popperjs/core": "^2.10.2" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -5958,7 +6000,6 @@ "version": "1.14.9", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==", - "dev": true, "funding": [ { "type": "individual", @@ -9641,6 +9682,11 @@ "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==", "dev": true }, + "node_modules/shvl": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/shvl/-/shvl-2.0.3.tgz", + "integrity": "sha512-V7C6S9Hlol6SzOJPnQ7qzOVEWUQImt3BNmmzh40wObhla3XOYMe4gGiYzLrJd5TFa+cI2f9LKIRJTTKZSTbWgw==" + }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -10497,6 +10543,15 @@ "@vue/shared": "3.2.31" } }, + "node_modules/vue-axios": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/vue-axios/-/vue-axios-3.4.1.tgz", + "integrity": "sha512-8YZYUOQrBEJktxoQtrM4rr2LfVcDaWfJqv8MqtLlgLlkuBvCYKFSZSo6AXQ4YcCzdgccDqstmuaEh68lcH9xWA==", + "peerDependencies": { + "axios": ">= 0.20.0", + "vue": "^3.0.0 || ^2.0.0" + } + }, "node_modules/vue-eslint-parser": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-8.3.0.tgz", @@ -10685,6 +10740,20 @@ "node": ">=8" } }, + "node_modules/vue-router": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.0.13.tgz", + "integrity": "sha512-LmXrC+BkDRLak+d5xTMgUYraT3Nj0H/vCbP+7usGvIl9Viqd1UP6AsP0i69pSbn9O0dXK/xCdp4yPw21HqV9Jw==", + "dependencies": { + "@vue/devtools-api": "^6.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, "node_modules/vue-style-loader": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.3.tgz", @@ -10707,6 +10776,46 @@ "integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==", "dev": true }, + "node_modules/vue3-cookies": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/vue3-cookies/-/vue3-cookies-1.0.6.tgz", + "integrity": "sha512-a1UvVD0qIgxyOqjlSOwnLnqAnz8ASltugEv8yX+96i/WGZAN9fEDci7xO4HIWZE1uToUnRq9JnFhvfDCSo45OA==", + "dependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/vuex": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vuex/-/vuex-4.0.2.tgz", + "integrity": "sha512-M6r8uxELjZIK8kTKDGgZTYX/ahzblnzC4isU1tpmEuOIIKmV+TRdc+H4s8ds2NuZ7wpUTdGRzJRtoj+lI+pc0Q==", + "dependencies": { + "@vue/devtools-api": "^6.0.0-beta.11" + }, + "peerDependencies": { + "vue": "^3.0.2" + } + }, + "node_modules/vuex-persistedstate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/vuex-persistedstate/-/vuex-persistedstate-4.1.0.tgz", + "integrity": "sha512-3SkEj4NqwM69ikJdFVw6gObeB0NHyspRYMYkR/EbhR0hbvAKyR5gksVhtAfY1UYuWUOCCA0QNGwv9pOwdj+XUQ==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "dependencies": { + "deepmerge": "^4.2.2", + "shvl": "^2.0.3" + }, + "peerDependencies": { + "vuex": "^3.0 || ^4.0.0-rc" + } + }, + "node_modules/vuex-persistedstate/node_modules/deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/watchpack": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.3.1.tgz", @@ -12727,6 +12836,12 @@ "integrity": "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==", "dev": true }, + "@popperjs/core": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.4.tgz", + "integrity": "sha512-q/ytXxO5NKvyT37pmisQAItCFqA7FD/vNb8dgaJy3/630Fsc+Mz9/9f2SziBoIZ30TJooXyTwZmhi1zjXmObYg==", + "peer": true + }, "@sideway/address": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.3.tgz", @@ -13247,7 +13362,8 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/@vue/cli-plugin-vuex/-/cli-plugin-vuex-5.0.1.tgz", "integrity": "sha512-5J/n+Ht4r2eVuncwCXcZPHzYCz/2haktle4WcggWiKeg3jSQVUJbjviPBs6sOo3y/LG3CEfZMP9bPJjVDbexpQ==", - "dev": true + "dev": true, + "requires": {} }, "@vue/cli-service": { "version": "5.0.1", @@ -13515,6 +13631,11 @@ } } }, + "@vue/devtools-api": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.1.3.tgz", + "integrity": "sha512-79InfO2xHv+WHIrH1bHXQUiQD/wMls9qBk6WVwGCbdwP7/3zINtvqPNMtmSHXsIKjvUAHc8L0ouOj6ZQQRmcXg==" + }, "@vue/reactivity": { "version": "3.2.31", "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.31.tgz", @@ -13773,13 +13894,15 @@ "version": "1.8.0", "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", - "dev": true + "dev": true, + "requires": {} }, "acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true + "dev": true, + "requires": {} }, "acorn-walk": { "version": "8.2.0", @@ -13848,7 +13971,8 @@ "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true + "dev": true, + "requires": {} }, "ansi-colors": { "version": "4.1.1", @@ -13961,6 +14085,14 @@ "postcss-value-parser": "^4.2.0" } }, + "axios": { + "version": "0.26.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz", + "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", + "requires": { + "follow-redirects": "^1.14.8" + } + }, "babel-loader": { "version": "8.2.3", "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.3.tgz", @@ -14120,6 +14252,12 @@ "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", "dev": true }, + "bootstrap": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.1.3.tgz", + "integrity": "sha512-fcQztozJ8jToQWXxVuEyXWW+dSo8AiXWKwiSSrKWsRB/Qt+Ewwza+JWoLKiTuQLaEPhdNAJ7+Dosc9DOIqNy7Q==", + "requires": {} + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -14861,7 +14999,8 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-3.1.0.tgz", "integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==", - "dev": true + "dev": true, + "requires": {} }, "csso": { "version": "4.2.0", @@ -15921,8 +16060,7 @@ "follow-redirects": { "version": "1.14.9", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", - "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==", - "dev": true + "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==" }, "forwarded": { "version": "0.2.0", @@ -16299,7 +16437,8 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", - "dev": true + "dev": true, + "requires": {} }, "ieee754": { "version": "1.2.1", @@ -17768,25 +17907,29 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.1.1.tgz", "integrity": "sha512-5JscyFmvkUxz/5/+TB3QTTT9Gi9jHkcn8dcmmuN68JQcv3aQg4y88yEHHhwFB52l/NkaJ43O0dbksGMAo49nfQ==", - "dev": true + "dev": true, + "requires": {} }, "postcss-discard-duplicates": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz", "integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==", - "dev": true + "dev": true, + "requires": {} }, "postcss-discard-empty": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz", "integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==", - "dev": true + "dev": true, + "requires": {} }, "postcss-discard-overridden": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz", "integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==", - "dev": true + "dev": true, + "requires": {} }, "postcss-loader": { "version": "6.2.1", @@ -17876,7 +18019,8 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", - "dev": true + "dev": true, + "requires": {} }, "postcss-modules-local-by-default": { "version": "4.0.0", @@ -17911,7 +18055,8 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz", "integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==", - "dev": true + "dev": true, + "requires": {} }, "postcss-normalize-display-values": { "version": "5.1.0", @@ -18615,6 +18760,11 @@ "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==", "dev": true }, + "shvl": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/shvl/-/shvl-2.0.3.tgz", + "integrity": "sha512-V7C6S9Hlol6SzOJPnQ7qzOVEWUQImt3BNmmzh40wObhla3XOYMe4gGiYzLrJd5TFa+cI2f9LKIRJTTKZSTbWgw==" + }, "signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -19268,6 +19418,12 @@ "@vue/shared": "3.2.31" } }, + "vue-axios": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/vue-axios/-/vue-axios-3.4.1.tgz", + "integrity": "sha512-8YZYUOQrBEJktxoQtrM4rr2LfVcDaWfJqv8MqtLlgLlkuBvCYKFSZSo6AXQ4YcCzdgccDqstmuaEh68lcH9xWA==", + "requires": {} + }, "vue-eslint-parser": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-8.3.0.tgz", @@ -19406,6 +19562,14 @@ } } }, + "vue-router": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.0.13.tgz", + "integrity": "sha512-LmXrC+BkDRLak+d5xTMgUYraT3Nj0H/vCbP+7usGvIl9Viqd1UP6AsP0i69pSbn9O0dXK/xCdp4yPw21HqV9Jw==", + "requires": { + "@vue/devtools-api": "^6.0.0" + } + }, "vue-style-loader": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.3.tgz", @@ -19430,6 +19594,38 @@ "integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==", "dev": true }, + "vue3-cookies": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/vue3-cookies/-/vue3-cookies-1.0.6.tgz", + "integrity": "sha512-a1UvVD0qIgxyOqjlSOwnLnqAnz8ASltugEv8yX+96i/WGZAN9fEDci7xO4HIWZE1uToUnRq9JnFhvfDCSo45OA==", + "requires": { + "vue": "^3.0.0" + } + }, + "vuex": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vuex/-/vuex-4.0.2.tgz", + "integrity": "sha512-M6r8uxELjZIK8kTKDGgZTYX/ahzblnzC4isU1tpmEuOIIKmV+TRdc+H4s8ds2NuZ7wpUTdGRzJRtoj+lI+pc0Q==", + "requires": { + "@vue/devtools-api": "^6.0.0-beta.11" + } + }, + "vuex-persistedstate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/vuex-persistedstate/-/vuex-persistedstate-4.1.0.tgz", + "integrity": "sha512-3SkEj4NqwM69ikJdFVw6gObeB0NHyspRYMYkR/EbhR0hbvAKyR5gksVhtAfY1UYuWUOCCA0QNGwv9pOwdj+XUQ==", + "requires": { + "deepmerge": "^4.2.2", + "shvl": "^2.0.3" + }, + "dependencies": { + "deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==" + } + } + }, "watchpack": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.3.1.tgz", @@ -19743,7 +19939,8 @@ "version": "8.5.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==", - "dev": true + "dev": true, + "requires": {} } } }, @@ -19870,7 +20067,8 @@ "version": "7.5.7", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.7.tgz", "integrity": "sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==", - "dev": true + "dev": true, + "requires": {} }, "y18n": { "version": "5.0.8", diff --git a/Frontend/vue/package.json b/Frontend/vue/package.json index 579a8d3..edd6444 100644 --- a/Frontend/vue/package.json +++ b/Frontend/vue/package.json @@ -5,11 +5,19 @@ "scripts": { "serve": "vue-cli-service serve", "build": "vue-cli-service build", - "lint": "vue-cli-service lint" + "lint": "vue-cli-service lint", + "watch": "vue-cli-service build --watch" }, "dependencies": { + "axios": "^0.26.1", + "bootstrap": "^5.1.3", "core-js": "^3.8.3", - "vue": "^3.2.13" + "vue": "^3.2.13", + "vue-axios": "^3.4.1", + "vue-router": "^4.0.13", + "vue3-cookies": "^1.0.6", + "vuex": "^4.0.2", + "vuex-persistedstate": "^4.1.0" }, "devDependencies": { "@babel/core": "^7.12.16", @@ -18,7 +26,7 @@ "@vue/cli-plugin-eslint": "~5.0.0", "@vue/cli-service": "~5.0.0", "eslint": "^7.32.0", - "eslint-plugin-vue": "^8.0.3" + "eslint-plugin-vue": "^8.5.0" }, "eslintConfig": { "root": true, diff --git a/Frontend/vue/src/App.vue b/Frontend/vue/src/App.vue index 591a031..43da0b3 100644 --- a/Frontend/vue/src/App.vue +++ b/Frontend/vue/src/App.vue @@ -1,26 +1,12 @@ diff --git a/Frontend/vue/src/assets/css/admin.css b/Frontend/vue/src/assets/css/admin.css new file mode 100644 index 0000000..e69de29 diff --git a/Frontend/vue/src/components/admin/AdminList.vue b/Frontend/vue/src/components/admin/AdminList.vue new file mode 100644 index 0000000..f84a8ef --- /dev/null +++ b/Frontend/vue/src/components/admin/AdminList.vue @@ -0,0 +1,55 @@ + + + diff --git a/Frontend/vue/src/components/admin/AdminLogin.vue b/Frontend/vue/src/components/admin/AdminLogin.vue new file mode 100644 index 0000000..7b8995e --- /dev/null +++ b/Frontend/vue/src/components/admin/AdminLogin.vue @@ -0,0 +1,107 @@ + + + + + diff --git a/Frontend/vue/src/components/admin/AdminNavbar.vue b/Frontend/vue/src/components/admin/AdminNavbar.vue new file mode 100644 index 0000000..62c5db3 --- /dev/null +++ b/Frontend/vue/src/components/admin/AdminNavbar.vue @@ -0,0 +1,84 @@ + + + + + diff --git a/Frontend/vue/src/components/admin/AdminSignup.vue b/Frontend/vue/src/components/admin/AdminSignup.vue new file mode 100644 index 0000000..40ff79c --- /dev/null +++ b/Frontend/vue/src/components/admin/AdminSignup.vue @@ -0,0 +1,141 @@ + + + + + diff --git a/Frontend/vue/src/components/admin/users/AdminUsersList.vue b/Frontend/vue/src/components/admin/users/AdminUsersList.vue new file mode 100644 index 0000000..6ac4f40 --- /dev/null +++ b/Frontend/vue/src/components/admin/users/AdminUsersList.vue @@ -0,0 +1,32 @@ + + diff --git a/Frontend/vue/src/main.js b/Frontend/vue/src/main.js index 01433bc..4efeaeb 100644 --- a/Frontend/vue/src/main.js +++ b/Frontend/vue/src/main.js @@ -1,4 +1,21 @@ import { createApp } from 'vue' +import axios from './utils/http' +import VueAxios from 'vue-axios' +import VueCookies from "vue3-cookies"; + import App from './App.vue' +import router from './router' +import admin from './store/admin/index.js' + +import 'bootstrap/dist/css/bootstrap.min.css' +import 'bootstrap/dist/js/bootstrap.bundle.js' + +const app = createApp(App) +router.app = app + +app.use(router) +app.use(VueAxios, axios) +app.use(VueCookies) +app.use(admin) -createApp(App).mount('#app') +app.mount('#app') diff --git a/Frontend/vue/src/router/index.js b/Frontend/vue/src/router/index.js new file mode 100644 index 0000000..7de4b7d --- /dev/null +++ b/Frontend/vue/src/router/index.js @@ -0,0 +1,61 @@ +import { createWebHistory, createRouter } from "vue-router"; +import HelloWorld from "@/components/HelloWorld.vue"; +import AdminLogin from "@/components/admin/AdminLogin.vue"; +import AdminSignup from "@/components/admin/AdminSignup.vue"; +import AdminUsersList from "@/components/admin/users/AdminUsersList.vue"; + +import admin from '@/store/admin/index.js' + +const routes = [ + { + path: "/", + name: "Home", + component: HelloWorld, + }, + { + path: "/admin/login", + name: "AdminLogin", + component: AdminLogin, + }, + { + path: "/admin/signup", + name: "AdminSignup", + component: AdminSignup, + }, + { + path: "/admin/users", + name: "AdminUsersList", + component: AdminUsersList, + meta: { + requiresAuth: true, + } + }, +]; + +const router = createRouter({ + history: createWebHistory(), + routes, +}); + +router.beforeEach((to, from, next) => { + const user = admin.getters.getUser; + + if ((to.name == 'AdminLogin' || to.name == 'AdminSignup') && user !== null) { + next({ name: 'AdminUsersList' }); + return; + } + + if (!to.meta.requiresAuth) { + next(); + return; + } + + if (user === null) { + next({ name: 'AdminLogin' }); + return; + } + + next(); +}); + +export default router; diff --git a/Frontend/vue/src/store/admin/index.js b/Frontend/vue/src/store/admin/index.js new file mode 100644 index 0000000..23279e7 --- /dev/null +++ b/Frontend/vue/src/store/admin/index.js @@ -0,0 +1,28 @@ +import { createStore } from 'vuex'; +import createPersistedState from "vuex-persistedstate"; + +export default createStore({ + plugins: [createPersistedState()], + + state: { + user: {}, + }, + + mutations: { + UPDATE_USER(state, user ){ + state.user = user + } + }, + + actions: { + setUser(context, user) { + context.commit('UPDATE_USER', user) + } + }, + + getters: { + getUser (state) { + return state.user; + } + } +}) diff --git a/Frontend/vue/src/utils/http/index.js b/Frontend/vue/src/utils/http/index.js new file mode 100644 index 0000000..4fe3ab7 --- /dev/null +++ b/Frontend/vue/src/utils/http/index.js @@ -0,0 +1,27 @@ +import axios from 'axios' +import router from '@/router' + +const instance = axios.create({ + baseURL: "http://localhost:8080/api/v1/", + headers: { + "Content-Type": "application/json", + }, +}); + +instance.interceptors.response.use( + function (response) { + console.log(response) + return response; + }, + + function (error) { + if (error.response.status === 401) { + router.push({ name: 'AdminLogin' }) + return + } + + return Promise.reject(error); + } +); + +export default instance diff --git a/Util/EmailValidation.go b/Util/EmailValidation.go new file mode 100644 index 0000000..4a5ca9d --- /dev/null +++ b/Util/EmailValidation.go @@ -0,0 +1,10 @@ +package Util + +import ( + "net/mail" +) + +func IsEmailValid(email string) bool { + _, err := mail.ParseAddress(email) + return err == nil +} diff --git a/main.go b/main.go index 9a42bc5..c96778c 100644 --- a/main.go +++ b/main.go @@ -1,45 +1,26 @@ package main import ( - "embed" - "io/fs" - "log" "net/http" "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Api" "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Database" + "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Frontend" "github.com/gorilla/mux" ) -//go:embed Frontend/vue/dist -var frontend embed.FS - -type spaHandler struct { - staticFS embed.FS - staticPath string - indexPath string -} - func main() { var ( - router *mux.Router - stripped fs.FS - frontendFS http.Handler - err error + router *mux.Router ) Database.Init() - router = Api.InitApiEndpoints() - - stripped, err = fs.Sub(frontend, "Frontend/vue/dist") - if err != nil { - log.Fatalln(err) - } + router = mux.NewRouter() - frontendFS = http.FileServer(http.FS(stripped)) - router.PathPrefix("/").Handler(frontendFS) + Api.InitApiEndpoints(router) + Frontend.InitFrontendRoutes(router) http.ListenAndServe(":8080", router) }