Browse Source

Add router authetication and redirection

Add admin top navbar, and start adding datalists
pull/3/head
Tovi Jaeschke-Rogers 3 years ago
parent
commit
6425603d68
25 changed files with 970 additions and 93 deletions
  1. +13
    -0
      Api/Auth/Login.go
  2. +36
    -0
      Api/Auth/Me.go
  3. +2
    -2
      Api/Auth/Session.go
  4. +20
    -21
      Api/Routes.go
  5. +10
    -1
      Api/Users.go
  6. +1
    -1
      Api/Users_test.go
  7. +3
    -6
      Database/Users.go
  8. +13
    -0
      Frontend/GetFrontendAssets_dev.go
  9. +26
    -0
      Frontend/GetFrontendAssets_prod.go
  10. +49
    -0
      Frontend/Routes.go
  11. +217
    -19
      Frontend/vue/package-lock.json
  12. +11
    -3
      Frontend/vue/package.json
  13. +1
    -15
      Frontend/vue/src/App.vue
  14. +0
    -0
      Frontend/vue/src/assets/css/admin.css
  15. +55
    -0
      Frontend/vue/src/components/admin/AdminList.vue
  16. +107
    -0
      Frontend/vue/src/components/admin/AdminLogin.vue
  17. +84
    -0
      Frontend/vue/src/components/admin/AdminNavbar.vue
  18. +141
    -0
      Frontend/vue/src/components/admin/AdminSignup.vue
  19. +32
    -0
      Frontend/vue/src/components/admin/users/AdminUsersList.vue
  20. +18
    -1
      Frontend/vue/src/main.js
  21. +61
    -0
      Frontend/vue/src/router/index.js
  22. +28
    -0
      Frontend/vue/src/store/admin/index.js
  23. +27
    -0
      Frontend/vue/src/utils/http/index.js
  24. +10
    -0
      Util/EmailValidation.go
  25. +5
    -24
      main.go

+ 13
- 0
Api/Auth/Login.go View File

@ -2,11 +2,13 @@ package Auth
import ( import (
"encoding/json" "encoding/json"
"log"
"net/http" "net/http"
"time" "time"
"git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Database" "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Database"
"git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Models" "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Models"
"git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Util"
"github.com/gofrs/uuid" "github.com/gofrs/uuid"
) )
@ -22,6 +24,7 @@ func Login(w http.ResponseWriter, r *http.Request) {
userData Models.User userData Models.User
sessionToken uuid.UUID sessionToken uuid.UUID
expiresAt time.Time expiresAt time.Time
returnJson []byte
err error err error
) )
@ -62,5 +65,15 @@ func Login(w http.ResponseWriter, r *http.Request) {
Expires: expiresAt, 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.WriteHeader(http.StatusOK)
w.Write(returnJson)
} }

+ 36
- 0
Api/Auth/Me.go View File

@ -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)
}

+ 2
- 2
Api/Auth/Session.go View File

@ -5,8 +5,8 @@ import (
"net/http" "net/http"
"time" "time"
"git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Database"
"git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Models" "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Models"
"git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Util"
) )
var ( var (
@ -66,7 +66,7 @@ func CheckCookieCurrentUser(w http.ResponseWriter, r *http.Request) (Models.User
return userData, err return userData, err
} }
userData, err = Util.GetUserById(w, r)
userData, err = Database.GetUserById(userSession.UserID)
if err != nil { if err != nil {
return userData, err return userData, err
} }


+ 20
- 21
Api/Routes.go View File

@ -8,40 +8,39 @@ import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
) )
func InitApiEndpoints() *mux.Router {
func InitApiEndpoints(router *mux.Router) {
var ( var (
router *mux.Router
api *mux.Router
) )
log.Println("Initializing API routes...") log.Println("Initializing API routes...")
router = mux.NewRouter()
api = router.PathPrefix("/api/v1/").Subrouter()
// Define routes for posts api // 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 // 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 // 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")))) //router.PathPrefix("/").Handler(http.StripPrefix("/images/", http.FileServer(http.Dir("./uploads"))))
return router
} }

+ 10
- 1
Api/Users.go View File

@ -116,8 +116,17 @@ func createUser(w http.ResponseWriter, r *http.Request) {
return 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) err = Database.CheckUniqueEmail(userData.Email)
if err != nil {
if err != nil || !Util.IsEmailValid(userData.Email) {
Util.JsonReturn(w, 405, "invalid_email") Util.JsonReturn(w, 405, "invalid_email")
return return
} }


+ 1
- 1
Api/Users_test.go View File

@ -190,7 +190,7 @@ func Test_getUsers(t *testing.T) {
createTestUser(true) 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 { if err != nil {
t.Errorf("Expected nil, recieved %s", err.Error()) t.Errorf("Expected nil, recieved %s", err.Error())


+ 3
- 6
Database/Users.go View File

@ -44,10 +44,6 @@ func GetUsers(page, pageSize int) ([]Models.User, error) {
err error err error
) )
if page == 0 {
page = 1
}
switch { switch {
case pageSize > 100: case pageSize > 100:
pageSize = 100 pageSize = 100
@ -55,8 +51,9 @@ func GetUsers(page, pageSize int) ([]Models.User, error) {
pageSize = 10 pageSize = 10
} }
err = DB.Offset(page).
Limit(pageSize).
err = DB.Model(Models.User{}).
Offset(0).
Limit(10).
Find(&users). Find(&users).
Error Error


+ 13
- 0
Frontend/GetFrontendAssets_dev.go View File

@ -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")
}

+ 26
- 0
Frontend/GetFrontendAssets_prod.go View File

@ -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
}

+ 49
- 0
Frontend/Routes.go View File

@ -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)
}

+ 217
- 19
Frontend/vue/package-lock.json View File

@ -8,8 +8,15 @@
"name": "vue", "name": "vue",
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"axios": "^0.26.1",
"bootstrap": "^5.1.3",
"core-js": "^3.8.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": { "devDependencies": {
"@babel/core": "^7.12.16", "@babel/core": "^7.12.16",
@ -18,7 +25,7 @@
"@vue/cli-plugin-eslint": "~5.0.0", "@vue/cli-plugin-eslint": "~5.0.0",
"@vue/cli-service": "~5.0.0", "@vue/cli-service": "~5.0.0",
"eslint": "^7.32.0", "eslint": "^7.32.0",
"eslint-plugin-vue": "^8.0.3"
"eslint-plugin-vue": "^8.5.0"
} }
}, },
"node_modules/@ampproject/remapping": { "node_modules/@ampproject/remapping": {
@ -1832,6 +1839,16 @@
"integrity": "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==", "integrity": "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==",
"dev": true "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": { "node_modules/@sideway/address": {
"version": "4.1.3", "version": "4.1.3",
"resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.3.tgz", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.3.tgz",
@ -2778,6 +2795,11 @@
"integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=",
"dev": true "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": { "node_modules/@vue/reactivity": {
"version": "3.2.31", "version": "3.2.31",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.31.tgz", "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.31.tgz",
@ -3336,6 +3358,14 @@
"postcss": "^8.1.0" "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": { "node_modules/babel-loader": {
"version": "8.2.3", "version": "8.2.3",
"resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.3.tgz", "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.3.tgz",
@ -3535,6 +3565,18 @@
"integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=",
"dev": true "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": { "node_modules/brace-expansion": {
"version": "1.1.11", "version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@ -5958,7 +6000,6 @@
"version": "1.14.9", "version": "1.14.9",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz",
"integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==", "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==",
"dev": true,
"funding": [ "funding": [
{ {
"type": "individual", "type": "individual",
@ -9641,6 +9682,11 @@
"integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==", "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==",
"dev": true "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": { "node_modules/signal-exit": {
"version": "3.0.7", "version": "3.0.7",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
@ -10497,6 +10543,15 @@
"@vue/shared": "3.2.31" "@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": { "node_modules/vue-eslint-parser": {
"version": "8.3.0", "version": "8.3.0",
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-8.3.0.tgz", "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-8.3.0.tgz",
@ -10685,6 +10740,20 @@
"node": ">=8" "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": { "node_modules/vue-style-loader": {
"version": "4.1.3", "version": "4.1.3",
"resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.3.tgz", "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==", "integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==",
"dev": true "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": { "node_modules/watchpack": {
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.3.1.tgz", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.3.1.tgz",
@ -12727,6 +12836,12 @@
"integrity": "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==", "integrity": "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==",
"dev": true "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": { "@sideway/address": {
"version": "4.1.3", "version": "4.1.3",
"resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.3.tgz", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.3.tgz",
@ -13247,7 +13362,8 @@
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/@vue/cli-plugin-vuex/-/cli-plugin-vuex-5.0.1.tgz", "resolved": "https://registry.npmjs.org/@vue/cli-plugin-vuex/-/cli-plugin-vuex-5.0.1.tgz",
"integrity": "sha512-5J/n+Ht4r2eVuncwCXcZPHzYCz/2haktle4WcggWiKeg3jSQVUJbjviPBs6sOo3y/LG3CEfZMP9bPJjVDbexpQ==", "integrity": "sha512-5J/n+Ht4r2eVuncwCXcZPHzYCz/2haktle4WcggWiKeg3jSQVUJbjviPBs6sOo3y/LG3CEfZMP9bPJjVDbexpQ==",
"dev": true
"dev": true,
"requires": {}
}, },
"@vue/cli-service": { "@vue/cli-service": {
"version": "5.0.1", "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": { "@vue/reactivity": {
"version": "3.2.31", "version": "3.2.31",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.31.tgz", "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.31.tgz",
@ -13773,13 +13894,15 @@
"version": "1.8.0", "version": "1.8.0",
"resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz",
"integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==",
"dev": true
"dev": true,
"requires": {}
}, },
"acorn-jsx": { "acorn-jsx": {
"version": "5.3.2", "version": "5.3.2",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
"integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
"dev": true
"dev": true,
"requires": {}
}, },
"acorn-walk": { "acorn-walk": {
"version": "8.2.0", "version": "8.2.0",
@ -13848,7 +13971,8 @@
"version": "3.5.2", "version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
"dev": true
"dev": true,
"requires": {}
}, },
"ansi-colors": { "ansi-colors": {
"version": "4.1.1", "version": "4.1.1",
@ -13961,6 +14085,14 @@
"postcss-value-parser": "^4.2.0" "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": { "babel-loader": {
"version": "8.2.3", "version": "8.2.3",
"resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.3.tgz", "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.3.tgz",
@ -14120,6 +14252,12 @@
"integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=",
"dev": true "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": { "brace-expansion": {
"version": "1.1.11", "version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@ -14861,7 +14999,8 @@
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-3.1.0.tgz", "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-3.1.0.tgz",
"integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==", "integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==",
"dev": true
"dev": true,
"requires": {}
}, },
"csso": { "csso": {
"version": "4.2.0", "version": "4.2.0",
@ -15921,8 +16060,7 @@
"follow-redirects": { "follow-redirects": {
"version": "1.14.9", "version": "1.14.9",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", "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": { "forwarded": {
"version": "0.2.0", "version": "0.2.0",
@ -16299,7 +16437,8 @@
"version": "5.1.0", "version": "5.1.0",
"resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz",
"integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==",
"dev": true
"dev": true,
"requires": {}
}, },
"ieee754": { "ieee754": {
"version": "1.2.1", "version": "1.2.1",
@ -17768,25 +17907,29 @@
"version": "5.1.1", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.1.1.tgz", "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.1.1.tgz",
"integrity": "sha512-5JscyFmvkUxz/5/+TB3QTTT9Gi9jHkcn8dcmmuN68JQcv3aQg4y88yEHHhwFB52l/NkaJ43O0dbksGMAo49nfQ==", "integrity": "sha512-5JscyFmvkUxz/5/+TB3QTTT9Gi9jHkcn8dcmmuN68JQcv3aQg4y88yEHHhwFB52l/NkaJ43O0dbksGMAo49nfQ==",
"dev": true
"dev": true,
"requires": {}
}, },
"postcss-discard-duplicates": { "postcss-discard-duplicates": {
"version": "5.1.0", "version": "5.1.0",
"resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz", "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz",
"integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==", "integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==",
"dev": true
"dev": true,
"requires": {}
}, },
"postcss-discard-empty": { "postcss-discard-empty": {
"version": "5.1.1", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz", "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz",
"integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==", "integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==",
"dev": true
"dev": true,
"requires": {}
}, },
"postcss-discard-overridden": { "postcss-discard-overridden": {
"version": "5.1.0", "version": "5.1.0",
"resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz", "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz",
"integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==", "integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==",
"dev": true
"dev": true,
"requires": {}
}, },
"postcss-loader": { "postcss-loader": {
"version": "6.2.1", "version": "6.2.1",
@ -17876,7 +18019,8 @@
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz",
"integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==",
"dev": true
"dev": true,
"requires": {}
}, },
"postcss-modules-local-by-default": { "postcss-modules-local-by-default": {
"version": "4.0.0", "version": "4.0.0",
@ -17911,7 +18055,8 @@
"version": "5.1.0", "version": "5.1.0",
"resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz", "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz",
"integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==", "integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==",
"dev": true
"dev": true,
"requires": {}
}, },
"postcss-normalize-display-values": { "postcss-normalize-display-values": {
"version": "5.1.0", "version": "5.1.0",
@ -18615,6 +18760,11 @@
"integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==", "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==",
"dev": true "dev": true
}, },
"shvl": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/shvl/-/shvl-2.0.3.tgz",
"integrity": "sha512-V7C6S9Hlol6SzOJPnQ7qzOVEWUQImt3BNmmzh40wObhla3XOYMe4gGiYzLrJd5TFa+cI2f9LKIRJTTKZSTbWgw=="
},
"signal-exit": { "signal-exit": {
"version": "3.0.7", "version": "3.0.7",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
@ -19268,6 +19418,12 @@
"@vue/shared": "3.2.31" "@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": { "vue-eslint-parser": {
"version": "8.3.0", "version": "8.3.0",
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-8.3.0.tgz", "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": { "vue-style-loader": {
"version": "4.1.3", "version": "4.1.3",
"resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.3.tgz", "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==", "integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==",
"dev": true "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": { "watchpack": {
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.3.1.tgz", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.3.1.tgz",
@ -19743,7 +19939,8 @@
"version": "8.5.0", "version": "8.5.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz",
"integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==", "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==",
"dev": true
"dev": true,
"requires": {}
} }
} }
}, },
@ -19870,7 +20067,8 @@
"version": "7.5.7", "version": "7.5.7",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.7.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.7.tgz",
"integrity": "sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==", "integrity": "sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==",
"dev": true
"dev": true,
"requires": {}
}, },
"y18n": { "y18n": {
"version": "5.0.8", "version": "5.0.8",


+ 11
- 3
Frontend/vue/package.json View File

@ -5,11 +5,19 @@
"scripts": { "scripts": {
"serve": "vue-cli-service serve", "serve": "vue-cli-service serve",
"build": "vue-cli-service build", "build": "vue-cli-service build",
"lint": "vue-cli-service lint"
"lint": "vue-cli-service lint",
"watch": "vue-cli-service build --watch"
}, },
"dependencies": { "dependencies": {
"axios": "^0.26.1",
"bootstrap": "^5.1.3",
"core-js": "^3.8.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": { "devDependencies": {
"@babel/core": "^7.12.16", "@babel/core": "^7.12.16",
@ -18,7 +26,7 @@
"@vue/cli-plugin-eslint": "~5.0.0", "@vue/cli-plugin-eslint": "~5.0.0",
"@vue/cli-service": "~5.0.0", "@vue/cli-service": "~5.0.0",
"eslint": "^7.32.0", "eslint": "^7.32.0",
"eslint-plugin-vue": "^8.0.3"
"eslint-plugin-vue": "^8.5.0"
}, },
"eslintConfig": { "eslintConfig": {
"root": true, "root": true,


+ 1
- 15
Frontend/vue/src/App.vue View File

@ -1,26 +1,12 @@
<template> <template>
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
<router-view />
</template> </template>
<script> <script>
import HelloWorld from './components/HelloWorld.vue'
export default { export default {
name: 'App', name: 'App',
components: {
HelloWorld
}
} }
</script> </script>
<style> <style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style> </style>

+ 0
- 0
Frontend/vue/src/assets/css/admin.css View File


+ 55
- 0
Frontend/vue/src/components/admin/AdminList.vue View File

@ -0,0 +1,55 @@
<template>
<div class="container table-responsive py-5">
<table class="table table-bordered table-hover">
<thead class="thead-dark">
<tr>
<th scope="col" v-for="tableHeader in tableHeaders">{{ tableHeader }}</th>
</tr>
</thead>
<tbody>
<tr v-for="item in items">
<td v-for="index in dataIndexes">{{ doThing(item, index) }}</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
export default {
props: {
sourceUrl: String,
listHeaders: {},
},
data() {
return {
tableHeaders: Object.values(this.listHeaders),
dataIndexes: Object.keys(this.listHeaders),
items: {},
}
},
created () {
this.getData()
},
methods: {
async getData () {
try {
const response = await this.axios.get(this.sourceUrl + '?page=0&pageSize=10')
if (response.status) {
this.items = response.data
}
} catch (error) {
console.log(error)
}
},
doThing(x, y) {
return x[y]
},
}
}
</script>

+ 107
- 0
Frontend/vue/src/components/admin/AdminLogin.vue View File

@ -0,0 +1,107 @@
<template>
<section class="vh-100 gradient-custom">
<div class="container py-5 h-100">
<div class="row justify-content-center align-items-center h-100">
<div class="col-12 col-lg-9 col-xl-7">
<div class="card shadow-2-strong card-registration" style="border-radius: 15px;">
<div class="card-body p-4 p-md-5">
<h3 class="mb-4 pb-2 pb-md-0 mb-md-5">Login</h3>
<form @submit.prevent>
<div class="row">
<div class="col-md-12 mb-4 pb-2">
<div class="form-outline">
<input v-model="email" type="email" id="emailAddress" class="form-control form-control-lg" />
<label class="form-label" for="emailAddress">Email</label>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12 mb-4 pb-2">
<div class="form-outline">
<input v-model="password" type="password" id="password" class="form-control form-control-lg" />
<label class="form-label" for="password">Password</label>
</div>
</div>
</div>
<div class="mt-2 pt-2 center-align">
<button class="btn btn-primary btn-lg" @click="login">Login</button>
</div>
<div class="mt-2 pt-2 center-align">
<p style="padding-right: 10px;">Don't have an account? </p><router-link :to='{"name": "AdminSignup"}'>Sign up</router-link>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</section>
</template>
<script>
export default {
name: 'AdminLogin',
data() {
return {
email: '',
password: '',
}
},
methods: {
async login () {
try {
const response = await this.axios.post(
'/admin/login',
{
email: this.email,
password: this.password,
}
)
if (response.status === 200) {
this.$store.dispatch('setUser', response.data)
this.$router.push({ name: 'AdminUsersList' })
}
} catch (error) {
console.log(error)
}
}
}
}
</script>
<style>
.gradient-custom {
/* fallback for old browsers */
background: #f093fb;
/* Chrome 10-25, Safari 5.1-6 */
background: -webkit-linear-gradient(to bottom right, rgba(240, 147, 251, 1), rgba(245, 87, 108, 1));
/* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
background: linear-gradient(to bottom right, rgba(240, 147, 251, 1), rgba(245, 87, 108, 1))
}
.card-registration .select-input.form-control[readonly]:not([disabled]) {
font-size: 1rem;
line-height: 2.15;
padding-left: .75em;
padding-right: .75em;
}
.card-registration .select-arrow {
top: 13px;
}
.center-align {
text-align: center;
}
.center-align * {
display: inline-block;
}
</style>

+ 84
- 0
Frontend/vue/src/components/admin/AdminNavbar.vue View File

@ -0,0 +1,84 @@
<template>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container-fluid">
<!-- TODO: Replace with logo -->
<router-link
:to="{ name: 'AdminUsersList' }"
class="nav-item"
>
Sudden Impact
</router-link>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-md-auto gap-2">
<li class="nav-item rounded">
<router-link
:to="{ name: 'AdminUsersList' }"
class="nav-link"
aria-current="page"
>
<i class="bi bi-house-fill me-2"></i>
Home
</router-link>
</li>
<li class="nav-item dropdown rounded">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false"><i class="bi bi-person-fill me-2"></i>Profile</a>
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
<li>
<router-link to="#" class="dropdown-item">Account</router-link>
</li>
<li>
<hr class="dropdown-divider">
</li>
<li>
<div v-on:click="logout" class="dropdown-item">Logout</div>
</li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
</template>
<script>
export default {
methods: {
async logout () {
try {
const response = await this.axios.get('/admin/logout');
if (response.status === 200) {
this.$store.dispatch('setUser', null);
this.$router.push({ name: 'AdminLogin' })
}
} catch (error) {
console.log(error)
}
}
}
}
</script>
<style>
body {
font-family: Montserrat, sans-serif;
}
.navbar-nav .nav-item:hover {
background-color: rgba(180, 190, 203, 0.4);
}
.navbar-dark .navbar-nav .nav-link.router-link-active {
color: #fff;
}
</style>

+ 141
- 0
Frontend/vue/src/components/admin/AdminSignup.vue View File

@ -0,0 +1,141 @@
<template>
<section class="vh-100 gradient-custom">
<div class="container py-5 h-100">
<div class="row justify-content-center align-items-center h-100">
<div class="col-12 col-lg-9 col-xl-7">
<div class="card shadow-2-strong card-registration" style="border-radius: 15px;">
<div class="card-body p-4 p-md-5">
<h3 class="mb-4 pb-2 pb-md-0 mb-md-5">Sign Up</h3>
<form @submit.prevent>
<div class="row">
<div class="col-md-6 mb-4">
<div class="form-outline">
<input v-model="first_name" type="text" id="firstName" class="form-control form-control-lg" />
<label class="form-label" for="firstName">First Name</label>
</div>
</div>
<div class="col-md-6 mb-4">
<div class="form-outline">
<input v-model="last_name" type="text" id="lastName" class="form-control form-control-lg" />
<label class="form-label" for="lastName">Last Name</label>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12 mb-4 pb-2">
<div class="form-outline">
<input v-model="email" type="email" id="emailAddress" class="form-control form-control-lg" />
<label class="form-label" for="emailAddress">Email</label>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12 mb-4 pb-2">
<div class="form-outline">
<input v-model="password" type="password" id="password" class="form-control form-control-lg" />
<label class="form-label" for="password">Password</label>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12 mb-4 pb-2">
<div class="form-outline">
<input v-model="confirm_password" type="password" id="confirmPassword" class="form-control form-control-lg" />
<label class="form-label" for="confirmPassword">ConfirmPassword</label>
</div>
</div>
</div>
<div class="mt-2 pt-2 center-align">
<button class="btn btn-primary btn-lg" @click="signup">Sign Up</button>
</div>
<div class="mt-2 pt-2 center-align">
<p style="padding-right: 10px;">Already have an account? </p><router-link :to='{"name": "AdminLogin"}'>Login</router-link>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</section>
</template>
<script>
import '@/assets/css/admin.css'
export default {
name: 'AdminSignup',
data() {
return {
first_name: '',
last_name: '',
email: '',
password: '',
confirm_password: ''
}
},
methods: {
async signup () {
try {
const response = await this.axios.post(
'/api/v1/admin/user',
{
first_name: this.first_name,
last_name: this.last_name,
email: this.email,
password: this.password,
confirm_password: this.confirm_password,
}
)
if (response.status === 200) {
this.$router.push({ name: 'AdminLogin' })
}
} catch (error) {
console.log(error)
}
}
}
}
</script>
<style>
.gradient-custom {
/* fallback for old browsers */
background: #f093fb;
/* Chrome 10-25, Safari 5.1-6 */
background: -webkit-linear-gradient(to bottom right, rgba(240, 147, 251, 1), rgba(245, 87, 108, 1));
/* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
background: linear-gradient(to bottom right, rgba(240, 147, 251, 1), rgba(245, 87, 108, 1))
}
.card-registration .select-input.form-control[readonly]:not([disabled]) {
font-size: 1rem;
line-height: 2.15;
padding-left: .75em;
padding-right: .75em;
}
.card-registration .select-arrow {
top: 13px;
}
.center-align {
text-align: center;
}
.center-align * {
display: inline-block;
}
</style>

+ 32
- 0
Frontend/vue/src/components/admin/users/AdminUsersList.vue View File

@ -0,0 +1,32 @@
<template>
<div>
<admin-navbar/>
<admin-list
:listHeaders="listHeaders"
:sourceUrl="sourceUrl"
/>
</div>
</template>
<script>
import AdminNavbar from '@/components/admin/AdminNavbar'
import AdminList from '@/components/admin/AdminList'
export default {
data () {
return {
sourceUrl: '/admin/user',
listHeaders: {
'first_name': 'First Name',
'last_name': 'Last Name',
'email': 'Email',
'last_login': 'Last Login',
}
}
},
components: {
AdminNavbar,
AdminList,
},
}
</script>

+ 18
- 1
Frontend/vue/src/main.js View File

@ -1,4 +1,21 @@
import { createApp } from 'vue' 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 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')

+ 61
- 0
Frontend/vue/src/router/index.js View File

@ -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;

+ 28
- 0
Frontend/vue/src/store/admin/index.js View File

@ -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;
}
}
})

+ 27
- 0
Frontend/vue/src/utils/http/index.js View File

@ -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

+ 10
- 0
Util/EmailValidation.go View File

@ -0,0 +1,10 @@
package Util
import (
"net/mail"
)
func IsEmailValid(email string) bool {
_, err := mail.ParseAddress(email)
return err == nil
}

+ 5
- 24
main.go View File

@ -1,45 +1,26 @@
package main package main
import ( import (
"embed"
"io/fs"
"log"
"net/http" "net/http"
"git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Api" "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Api"
"git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Database" "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Database"
"git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Frontend"
"github.com/gorilla/mux" "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() { func main() {
var ( var (
router *mux.Router
stripped fs.FS
frontendFS http.Handler
err error
router *mux.Router
) )
Database.Init() 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) http.ListenAndServe(":8080", router)
} }

Loading…
Cancel
Save