parent
35b2afba37
commit
5c8e734e81
@ -0,0 +1,105 @@
|
|||||||
|
package jwt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/dustinpianalto/errors"
|
||||||
|
"github.com/dustinpianalto/quartermaster"
|
||||||
|
"github.com/golang-jwt/jwt"
|
||||||
|
)
|
||||||
|
|
||||||
|
var secretKey = []byte(os.Getenv("JWT_KEY"))
|
||||||
|
|
||||||
|
func AuthenticateJWTToken(req *http.Request) (*quartermaster.Claims, error) {
|
||||||
|
const method = errors.Method("jwt/AuthenticateJWTToken")
|
||||||
|
jwtToken, err := extractJWTToken(req)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.E(method, "could not authenticate token", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
claims, err := ParseJWT(jwtToken, secretKey)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.E(method, "could not parse token", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return claims, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractJWTToken extracts bearer token from Authorization header
|
||||||
|
func extractJWTToken(req *http.Request) (string, error) {
|
||||||
|
const method = errors.Method("jwt/extractJWTToken")
|
||||||
|
tokenString := req.Header.Get("Authorization")
|
||||||
|
|
||||||
|
if tokenString == "" {
|
||||||
|
return "", errors.E(method, errors.Malformed, "token not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenString, err := stripTokenPrefix(tokenString)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.E(method, "error formatting token", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tokenString, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strips 'Token' or 'Bearer' prefix from token string
|
||||||
|
func stripTokenPrefix(tok string) (string, error) {
|
||||||
|
// split token to 2 parts
|
||||||
|
tokenParts := strings.Split(tok, " ")
|
||||||
|
|
||||||
|
if len(tokenParts) < 2 {
|
||||||
|
return tokenParts[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return tokenParts[1], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseJWT(tokenString string, key []byte) (*quartermaster.Claims, error) {
|
||||||
|
const method = errors.Method("jwt/ParseJWT")
|
||||||
|
var claims *quartermaster.Claims = &quartermaster.Claims{}
|
||||||
|
token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
|
||||||
|
|
||||||
|
// validate the alg is what is expected:
|
||||||
|
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||||
|
return nil, errors.E(errors.Method("jwt/ParseJWT/Parse"), errors.Malformed, fmt.Sprintf("unexpected signing method: %v", token.Header["alg"]))
|
||||||
|
}
|
||||||
|
return key, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.E(method, "error parsing token", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if token.Valid {
|
||||||
|
return claims, nil
|
||||||
|
|
||||||
|
} else if ve, ok := err.(*jwt.ValidationError); ok {
|
||||||
|
if ve.Errors&jwt.ValidationErrorMalformed != 0 {
|
||||||
|
return nil, errors.E(method, errors.Malformed, "token is malformed")
|
||||||
|
} else if ve.Errors&(jwt.ValidationErrorExpired|jwt.ValidationErrorNotValidYet) != 0 {
|
||||||
|
// Token is either expired or not active yet
|
||||||
|
return nil, errors.E(method, errors.Permission, "token is either expired or not yet valid")
|
||||||
|
} else {
|
||||||
|
return nil, errors.E(method, errors.Internal, "unknown error with token")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil, errors.E(method, errors.Permission, "token is not valid")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateJWTToken(claims quartermaster.Claims) (string, error) {
|
||||||
|
const method = errors.Method("jwt/CreateJWTToken")
|
||||||
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||||
|
tokenString, err := token.SignedString(secretKey)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.E(method, errors.Internal, "error signing token", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tokenString, nil
|
||||||
|
}
|
||||||
@ -1,113 +1,27 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/dustinpianalto/errors"
|
"github.com/dustinpianalto/errors"
|
||||||
"github.com/dustinpianalto/quartermaster"
|
"github.com/dustinpianalto/quartermaster"
|
||||||
"github.com/golang-jwt/jwt"
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
)
|
|
||||||
|
|
||||||
var jwtKey = []byte(os.Getenv("JWT_KEY"))
|
|
||||||
|
|
||||||
func Mount(r *mux.Router, path string, handler http.Handler) {
|
|
||||||
r.PathPrefix(path).Handler(
|
|
||||||
http.StripPrefix(
|
|
||||||
strings.TrimSuffix(path, "/"),
|
|
||||||
handler,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
type RootHandler func(http.ResponseWriter, *http.Request) error
|
|
||||||
|
|
||||||
func (rh RootHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
||||||
err := rh(w, r)
|
|
||||||
if err == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println(err)
|
|
||||||
|
|
||||||
e, ok := err.(*errors.Error)
|
|
||||||
if !ok {
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
||||||
|
|
||||||
if errors.Is(errors.Permission, e) {
|
|
||||||
body := getErrorBody("Permission Denied")
|
|
||||||
w.WriteHeader(http.StatusForbidden)
|
|
||||||
w.Write(body)
|
|
||||||
}
|
|
||||||
|
|
||||||
if errors.Is(errors.Incorrect, e) && strings.Contains(string(e.Method), "login") {
|
|
||||||
body := getErrorBody("Invalid Login Credentials")
|
|
||||||
w.WriteHeader(http.StatusUnauthorized)
|
|
||||||
w.Write(body)
|
|
||||||
}
|
|
||||||
|
|
||||||
if errors.Is(errors.Incorrect, e) && strings.Contains(string(e.Method), "refresh") {
|
func UnitFromString(s string) (quartermaster.Unit, error) {
|
||||||
body := getErrorBody("Missing or Invalid Cookie")
|
switch s {
|
||||||
w.WriteHeader(http.StatusUnauthorized)
|
case "Teaspoon":
|
||||||
w.Write(body)
|
return 0, nil
|
||||||
}
|
case "Tablespoon":
|
||||||
|
return 1, nil
|
||||||
if errors.Is(errors.Malformed, e) {
|
case "Cup":
|
||||||
body := getErrorBody("Bad Request")
|
return 2, nil
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
case "Ounce":
|
||||||
w.Write(body)
|
return 3, nil
|
||||||
}
|
case "Gram":
|
||||||
|
return 4, nil
|
||||||
if errors.Is(errors.Internal, e) {
|
case "Pound":
|
||||||
body := getErrorBody("Internal Server Error")
|
return 5, nil
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
case "Individual":
|
||||||
w.Write(body)
|
return 6, nil
|
||||||
}
|
default:
|
||||||
|
return -1, errors.E(errors.Method("utils/UnitFromString"), errors.Malformed, "not a valid unit")
|
||||||
if errors.Is(errors.Conflict, e) && strings.Contains(string(e.Method), "register") {
|
|
||||||
body := getErrorBody("User already exists")
|
|
||||||
w.WriteHeader(http.StatusConflict)
|
|
||||||
w.Write(body)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getErrorBody(s string) []byte {
|
|
||||||
return []byte(fmt.Sprintf("{\"error\": \"%s\"}", s))
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsAuthenticated(r *http.Request) (*jwt.Token, error) {
|
|
||||||
const method errors.Method = "utils/IsAuthenticated"
|
|
||||||
c, err := r.Cookie("token")
|
|
||||||
if err != nil {
|
|
||||||
if err == http.ErrNoCookie {
|
|
||||||
return nil, errors.E(method, errors.Incorrect, "cookie not found", err)
|
|
||||||
}
|
|
||||||
return nil, errors.E(method, errors.Malformed, "failed to get cookie data", err)
|
|
||||||
}
|
|
||||||
tknStr := c.Value
|
|
||||||
claims := &quartermaster.Claims{}
|
|
||||||
tkn, err := jwt.ParseWithClaims(tknStr, claims, func(token *jwt.Token) (interface{}, error) {
|
|
||||||
return jwtKey, nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
if err == jwt.ErrSignatureInvalid {
|
|
||||||
return nil, errors.E(method, errors.Incorrect, "cookie is invalid", err)
|
|
||||||
}
|
|
||||||
e, _ := err.(*jwt.ValidationError)
|
|
||||||
if e.Inner == jwt.ErrInvalidKeyType {
|
|
||||||
return nil, errors.E(method, errors.Internal, err)
|
|
||||||
} else if e.Inner == jwt.ErrHashUnavailable {
|
|
||||||
return nil, errors.E(method, errors.Internal, err)
|
|
||||||
}
|
|
||||||
return nil, errors.E(method, errors.Malformed, "failed to parse cookie", err)
|
|
||||||
}
|
}
|
||||||
return tkn, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,16 @@
|
|||||||
|
package items
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/dustinpianalto/quartermaster/pkg/utils"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetRouter() *mux.Router {
|
||||||
|
router := mux.NewRouter()
|
||||||
|
router.Handle("/", utils.AuthenticationMiddleware(items))
|
||||||
|
router.Handle("/getItemByBarcode/{barcode}", utils.AuthenticationMiddleware(getItemByBarcode))
|
||||||
|
router.Handle("/{id}/moveItem", utils.AuthenticationMiddleware(moveItem))
|
||||||
|
router.Handle("/{id}/removeItem", utils.AuthenticationMiddleware(removeItem))
|
||||||
|
router.Handle("/{id}/getLocationCount", utils.AuthenticationMiddleware(getLocationCount))
|
||||||
|
return router
|
||||||
|
}
|
||||||
@ -0,0 +1,203 @@
|
|||||||
|
package items
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/dustinpianalto/errors"
|
||||||
|
"github.com/dustinpianalto/quartermaster"
|
||||||
|
"github.com/dustinpianalto/quartermaster/pkg/services"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
type addItemRequest struct {
|
||||||
|
Location quartermaster.Location `json:"location"`
|
||||||
|
quartermaster.Item
|
||||||
|
}
|
||||||
|
|
||||||
|
type itemsResponse struct {
|
||||||
|
Count int `json:"count"`
|
||||||
|
*quartermaster.Item
|
||||||
|
}
|
||||||
|
|
||||||
|
func items(w *http.ResponseWriter, r *http.Request, user *quartermaster.User) error {
|
||||||
|
const method errors.Method = "items"
|
||||||
|
if r.Method == "POST" {
|
||||||
|
err := addItem(w, r, user)
|
||||||
|
if err != nil {
|
||||||
|
return errors.E(method, "there was a problem adding the item", err)
|
||||||
|
}
|
||||||
|
} else if r.Method == "GET" {
|
||||||
|
err := getItems(w, r, user)
|
||||||
|
if err != nil {
|
||||||
|
return errors.E(method, "there was a problem getting items", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return errors.E(method, errors.Malformed, "http method not allowed")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func addItem(w *http.ResponseWriter, r *http.Request, user *quartermaster.User) error {
|
||||||
|
const method errors.Method = "items/addItem"
|
||||||
|
var ir addItemRequest
|
||||||
|
err := json.NewDecoder(r.Body).Decode(&ir)
|
||||||
|
if err != nil {
|
||||||
|
return errors.E(method, errors.Malformed, "failed to decode item request", err)
|
||||||
|
}
|
||||||
|
if ir.Name == "" || ir.Description == "" || ir.Size == 0.0 || ir.Barcode == "" {
|
||||||
|
return errors.E(method, errors.Malformed, "name, description, and size, and barcode are requred")
|
||||||
|
}
|
||||||
|
i, err := services.ItemService.AddItem(&ir.Item, &ir.Location, user)
|
||||||
|
if err != nil {
|
||||||
|
return errors.E(method, errors.Internal, "error adding item to location", err)
|
||||||
|
}
|
||||||
|
iJson, err := json.Marshal(i)
|
||||||
|
if err != nil {
|
||||||
|
return errors.E(method, errors.Internal, "error marshalling item", err)
|
||||||
|
}
|
||||||
|
(*w).Header().Set("Content-Type", "application/json")
|
||||||
|
(*w).WriteHeader(http.StatusCreated)
|
||||||
|
(*w).Write(iJson)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getItems(w *http.ResponseWriter, r *http.Request, user *quartermaster.User) error {
|
||||||
|
const method errors.Method = "items/getItems"
|
||||||
|
var l *quartermaster.Location
|
||||||
|
err := json.NewDecoder(r.Body).Decode(&l)
|
||||||
|
if err != nil {
|
||||||
|
return errors.E(method, errors.Malformed, "failed to decode location", err)
|
||||||
|
}
|
||||||
|
if l.ID == 0 {
|
||||||
|
return errors.E(method, errors.Malformed, "id is required")
|
||||||
|
}
|
||||||
|
items, err := services.LocationService.GetItems(l, user)
|
||||||
|
if err != nil {
|
||||||
|
return errors.E(method, "problem getting items", err)
|
||||||
|
}
|
||||||
|
var itemsResp []itemsResponse
|
||||||
|
for i, c := range items {
|
||||||
|
itemsResp = append(itemsResp, itemsResponse{Count: c, Item: i})
|
||||||
|
}
|
||||||
|
itemsJson, err := json.Marshal(itemsResp)
|
||||||
|
if err != nil {
|
||||||
|
return errors.E(method, errors.Internal, "error marshalling items", err)
|
||||||
|
}
|
||||||
|
(*w).Header().Set("Content-Type", "application/json")
|
||||||
|
(*w).WriteHeader(http.StatusOK)
|
||||||
|
(*w).Write(itemsJson)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getItemByBarcode(w *http.ResponseWriter, r *http.Request, user *quartermaster.User) error {
|
||||||
|
const method errors.Method = "items/getItemByBarcode"
|
||||||
|
params := mux.Vars(r)
|
||||||
|
barcodeString := params["barcode"]
|
||||||
|
item, err := services.ItemService.GetItemByBarcode(barcodeString, user)
|
||||||
|
if err != nil {
|
||||||
|
return errors.E(method, "problem getting items", err)
|
||||||
|
}
|
||||||
|
if item != nil {
|
||||||
|
itemJson, err := json.Marshal(item)
|
||||||
|
if err != nil {
|
||||||
|
return errors.E(method, errors.Internal, "error marshalling items", err)
|
||||||
|
}
|
||||||
|
(*w).Header().Set("Content-Type", "application/json")
|
||||||
|
(*w).WriteHeader(http.StatusOK)
|
||||||
|
(*w).Write(itemJson)
|
||||||
|
} else {
|
||||||
|
(*w).Header().Set("Content-Type", "application/json")
|
||||||
|
(*w).WriteHeader(http.StatusNotFound)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func moveItem(w *http.ResponseWriter, r *http.Request, user *quartermaster.User) error {
|
||||||
|
const method errors.Method = "items/moveItem"
|
||||||
|
params := mux.Vars(r)
|
||||||
|
itemID, err := strconv.Atoi(params["id"])
|
||||||
|
if err != nil {
|
||||||
|
return errors.E(method, errors.Malformed, "id must be a valid number")
|
||||||
|
}
|
||||||
|
var req map[string]int
|
||||||
|
err = json.NewDecoder(r.Body).Decode(&req)
|
||||||
|
if err != nil {
|
||||||
|
return errors.E(method, errors.Malformed, "failed to decode request", err)
|
||||||
|
}
|
||||||
|
if _, ok := req["old_id"]; !ok {
|
||||||
|
return errors.E(method, errors.Malformed, "old_id is required")
|
||||||
|
}
|
||||||
|
if _, ok := req["new_id"]; !ok {
|
||||||
|
return errors.E(method, errors.Malformed, "new_id is required")
|
||||||
|
}
|
||||||
|
item := &quartermaster.Item{
|
||||||
|
ID: itemID,
|
||||||
|
}
|
||||||
|
old := &quartermaster.Location{
|
||||||
|
ID: req["old_id"],
|
||||||
|
}
|
||||||
|
new := &quartermaster.Location{
|
||||||
|
ID: req["new_id"],
|
||||||
|
}
|
||||||
|
err = services.ItemService.MoveItem(item, old, new, user)
|
||||||
|
if err != nil {
|
||||||
|
return errors.E(method, errors.Internal, "error moving item", err)
|
||||||
|
}
|
||||||
|
(*w).WriteHeader(http.StatusOK)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeItem(w *http.ResponseWriter, r *http.Request, user *quartermaster.User) error {
|
||||||
|
const method errors.Method = "items/removeItem"
|
||||||
|
params := mux.Vars(r)
|
||||||
|
itemID, err := strconv.Atoi(params["id"])
|
||||||
|
if err != nil {
|
||||||
|
return errors.E(method, errors.Malformed, "id must be a valid number")
|
||||||
|
}
|
||||||
|
var req map[string]int
|
||||||
|
err = json.NewDecoder(r.Body).Decode(&req)
|
||||||
|
if err != nil {
|
||||||
|
return errors.E(method, errors.Malformed, "failed to decode request", err)
|
||||||
|
}
|
||||||
|
if _, ok := req["location_id"]; !ok {
|
||||||
|
return errors.E(method, errors.Malformed, "location_id is required")
|
||||||
|
}
|
||||||
|
item := &quartermaster.Item{
|
||||||
|
ID: itemID,
|
||||||
|
}
|
||||||
|
location := &quartermaster.Location{
|
||||||
|
ID: req["location_id"],
|
||||||
|
}
|
||||||
|
err = services.ItemService.RemoveItem(item, location, user)
|
||||||
|
if err != nil {
|
||||||
|
return errors.E(method, errors.Internal, "error removing item", err)
|
||||||
|
}
|
||||||
|
(*w).WriteHeader(http.StatusOK)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getLocationCount(w *http.ResponseWriter, r *http.Request, user *quartermaster.User) error {
|
||||||
|
const method errors.Method = "items/getLocationCount"
|
||||||
|
params := mux.Vars(r)
|
||||||
|
itemID, err := strconv.Atoi(params["id"])
|
||||||
|
if err != nil {
|
||||||
|
return errors.E(method, errors.Malformed, "id must be a valid number", err)
|
||||||
|
}
|
||||||
|
i := &quartermaster.Item{
|
||||||
|
ID: itemID,
|
||||||
|
}
|
||||||
|
locations, err := services.ItemService.GetItemLocations(i, user)
|
||||||
|
if err != nil {
|
||||||
|
return errors.E(method, errors.Internal, "error getting locations", err)
|
||||||
|
}
|
||||||
|
locationsJson, err := json.Marshal(locations)
|
||||||
|
if err != nil {
|
||||||
|
return errors.E(method, errors.Internal, "error marshalling locations", err)
|
||||||
|
}
|
||||||
|
(*w).Header().Set("Content-Type", "application/json")
|
||||||
|
(*w).WriteHeader(http.StatusOK)
|
||||||
|
(*w).Write(locationsJson)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@ -1,12 +1,18 @@
|
|||||||
package locations
|
package locations
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/dustinpianalto/quartermaster/internal/utils"
|
"github.com/dustinpianalto/quartermaster/pkg/utils"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetRouter() *mux.Router {
|
func GetRouter() *mux.Router {
|
||||||
router := mux.NewRouter().StrictSlash(true)
|
router := mux.NewRouter()
|
||||||
router.Handle("/", utils.RootHandler(locations))
|
router.Handle("/", utils.AuthenticationMiddleware(locations))
|
||||||
|
router.Handle("/{id}/getChildren", utils.AuthenticationMiddleware(getChildren))
|
||||||
|
router.Handle("/{id}/items", utils.AuthenticationMiddleware(getItems))
|
||||||
|
router.Handle("/{id}/addItem/{item_id}", utils.AuthenticationMiddleware(addItem))
|
||||||
|
router.Handle("/{id}/removeItem/{item_id}", utils.AuthenticationMiddleware(removeItem))
|
||||||
|
router.Handle("/{id}/getItemCount/{item_id}", utils.AuthenticationMiddleware(getItemCount))
|
||||||
|
router.Handle("/{id}", utils.AuthenticationMiddleware(getLocation))
|
||||||
return router
|
return router
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,234 @@
|
|||||||
|
<template>
|
||||||
|
<v-card class="pa-5" width="600px">
|
||||||
|
<v-form dark v-model="valid" ref="form">
|
||||||
|
<StreamBarcodeReader v-if="barcodeReader != 'none'"
|
||||||
|
@decode="(a, b, c) => onDecode(a, b, c)"
|
||||||
|
@loaded="() => onLoad()"
|
||||||
|
></StreamBarcodeReader>
|
||||||
|
<v-text-field
|
||||||
|
v-model="barcode"
|
||||||
|
label="Barcode"
|
||||||
|
outlined
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
dark
|
||||||
|
counter="100"
|
||||||
|
:rules="[required('Barcode'), minLength('Barcode', 2), maxLength('Barcode', 100)]"
|
||||||
|
v-on:keyup.enter="submitFunc()"
|
||||||
|
append-icon="mdi-barcode-scan"
|
||||||
|
@click:append="barcodeReader = 'barcode'"
|
||||||
|
></v-text-field>
|
||||||
|
<v-text-field
|
||||||
|
v-model="name"
|
||||||
|
label="Name"
|
||||||
|
outlined
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
dark
|
||||||
|
counter="30"
|
||||||
|
:rules="[required('Name'), minLength('Name', 2), maxLength('Name', 30)]"
|
||||||
|
v-on:keyup.enter="submitFunc()"
|
||||||
|
></v-text-field>
|
||||||
|
<v-textarea
|
||||||
|
v-model="description"
|
||||||
|
label="Description"
|
||||||
|
outlined
|
||||||
|
dense
|
||||||
|
dark
|
||||||
|
filled
|
||||||
|
auto-grow
|
||||||
|
counter="true"
|
||||||
|
:rules="[required('Description')]"
|
||||||
|
v-on:keyup.enter="submitFunc()"
|
||||||
|
></v-textarea>
|
||||||
|
<v-text-field
|
||||||
|
v-model.number="size"
|
||||||
|
type="number"
|
||||||
|
label="Size"
|
||||||
|
outlined
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
dark
|
||||||
|
v-on:keyup.enter="submitFunc()"
|
||||||
|
></v-text-field>
|
||||||
|
<v-select
|
||||||
|
v-model="unit"
|
||||||
|
:items="units"
|
||||||
|
label="Units"
|
||||||
|
persistent-hint
|
||||||
|
outlined
|
||||||
|
dense
|
||||||
|
filled
|
||||||
|
return-object
|
||||||
|
single-line
|
||||||
|
></v-select>
|
||||||
|
<v-text-field
|
||||||
|
v-model="locationName"
|
||||||
|
label="Location"
|
||||||
|
outlined
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
dark
|
||||||
|
counter="100"
|
||||||
|
:readonly="true"
|
||||||
|
:rules="[required('Location'), minLength('Location', 2), maxLength('Location', 100)]"
|
||||||
|
v-on:keyup.enter="submitFunc()"
|
||||||
|
append-icon="mdi-barcode-scan"
|
||||||
|
@click:append="barcodeReader = 'location'"
|
||||||
|
></v-text-field>
|
||||||
|
<div class="text-center">
|
||||||
|
<v-btn
|
||||||
|
class="signin-btn"
|
||||||
|
rounded
|
||||||
|
color--text="white"
|
||||||
|
dark
|
||||||
|
:disabled="!valid"
|
||||||
|
@click="submitFunc()"
|
||||||
|
>
|
||||||
|
Add Item
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
|
</v-form>
|
||||||
|
<v-container class="pt-50">
|
||||||
|
<v-alert
|
||||||
|
v-model="success"
|
||||||
|
prominent
|
||||||
|
dense
|
||||||
|
type="success"
|
||||||
|
transition="slide-y-transition"
|
||||||
|
>
|
||||||
|
{{ successMessage }}
|
||||||
|
</v-alert>
|
||||||
|
<v-alert
|
||||||
|
v-model="error"
|
||||||
|
prominent
|
||||||
|
dense
|
||||||
|
type="error"
|
||||||
|
transition="slide-y-transition"
|
||||||
|
>
|
||||||
|
{{ errorMessage }}
|
||||||
|
</v-alert>
|
||||||
|
</v-container>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import validations from "@/utils/validations";
|
||||||
|
import { StreamBarcodeReader } from "vue-barcode-reader";
|
||||||
|
import { API } from "../utils/api_common";
|
||||||
|
import { mapState } from "vuex";
|
||||||
|
export default {
|
||||||
|
name: "AddItemForm",
|
||||||
|
data: () => ({
|
||||||
|
valid: false,
|
||||||
|
name: "",
|
||||||
|
description: "",
|
||||||
|
size: null,
|
||||||
|
unit: "",
|
||||||
|
units: [
|
||||||
|
{ value: 0, text: "Teaspoon" },
|
||||||
|
{ value: 1, text: "Tablespoon" },
|
||||||
|
{ value: 2, text: "Cup" },
|
||||||
|
{ value: 3, text: "Ounce" },
|
||||||
|
{ value: 4, text: "Gram" },
|
||||||
|
{ value: 5, text: "Pound" },
|
||||||
|
{ value: 6, text: "Individual" },
|
||||||
|
],
|
||||||
|
barcode: "",
|
||||||
|
barcodeReader: "none",
|
||||||
|
location: null,
|
||||||
|
locationName: "",
|
||||||
|
successMessage: null,
|
||||||
|
errorMessage: null,
|
||||||
|
success: false,
|
||||||
|
error: false,
|
||||||
|
open: [],
|
||||||
|
...validations,
|
||||||
|
}),
|
||||||
|
components: {
|
||||||
|
StreamBarcodeReader
|
||||||
|
},
|
||||||
|
props: ["defaultLocation", "closeFunc"],
|
||||||
|
mounted() {
|
||||||
|
//TODO: Process defaultLocation
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
items() {
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onDecode(a, b, c) {
|
||||||
|
if (this.barcodeReader == 'barcode') {
|
||||||
|
this.barcode = a
|
||||||
|
API.get("api/v1/items/getItemByBarcode/" + a,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: localStorage.getItem("token"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
).then((response) => {
|
||||||
|
if (response.status == 200) {
|
||||||
|
this.name = response.data.name
|
||||||
|
this.description = response.data.description
|
||||||
|
this.size = response.data.size
|
||||||
|
this.unit = response.data.unit
|
||||||
|
console.log(this.unit)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else if (this.barcodeReader == 'location') {
|
||||||
|
var loc = JSON.parse(a);
|
||||||
|
this.location = loc.id
|
||||||
|
this.locationName = loc.name
|
||||||
|
}
|
||||||
|
this.barcodeReader = 'none'
|
||||||
|
},
|
||||||
|
onLoad() {
|
||||||
|
console.log("barcode reader loaded")
|
||||||
|
},
|
||||||
|
submitFunc() {
|
||||||
|
let data = {
|
||||||
|
name: this.name,
|
||||||
|
description: this.description,
|
||||||
|
size: this.size,
|
||||||
|
unit: this.unit.id,
|
||||||
|
location: {"id": this.location},
|
||||||
|
barcode: this.barcode,
|
||||||
|
};
|
||||||
|
console.log(data)
|
||||||
|
API
|
||||||
|
.post("api/v1/items/", data, {
|
||||||
|
headers: {
|
||||||
|
Authorization: localStorage.getItem("token"),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
if (response.status == 201) {
|
||||||
|
this.successMessage = "Item Added";
|
||||||
|
this.success = true;
|
||||||
|
this.$refs.form.reset();
|
||||||
|
console.log(response.data)
|
||||||
|
return
|
||||||
|
setTimeout(() => {
|
||||||
|
this.$router.push({
|
||||||
|
name: "Item",
|
||||||
|
params: { id: response.data.id },
|
||||||
|
});
|
||||||
|
this.success = false;
|
||||||
|
this.successMessage = "";
|
||||||
|
this.closeFunc("addItem");
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.errorMessage = error.response.data.error;
|
||||||
|
this.error = true;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.errorMessage = null;
|
||||||
|
this.error = false;
|
||||||
|
}, 3000);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@ -0,0 +1,185 @@
|
|||||||
|
<template>
|
||||||
|
<v-card class="pa-5" width="600px">
|
||||||
|
<v-form dark v-model="valid" ref="form">
|
||||||
|
<v-text-field
|
||||||
|
v-model="name"
|
||||||
|
label="Name"
|
||||||
|
outlined
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
dark
|
||||||
|
counter="30"
|
||||||
|
:rules="[required('Name'), minLength('Name', 2), maxLength('Name', 30)]"
|
||||||
|
v-on:keyup.enter="submitFunc()"
|
||||||
|
></v-text-field>
|
||||||
|
<v-textarea
|
||||||
|
v-model="description"
|
||||||
|
label="Description"
|
||||||
|
outlined
|
||||||
|
dense
|
||||||
|
dark
|
||||||
|
filled
|
||||||
|
auto-grow
|
||||||
|
counter="true"
|
||||||
|
:rules="[required('Description')]"
|
||||||
|
v-on:keyup.enter="submitFunc()"
|
||||||
|
></v-textarea>
|
||||||
|
<v-treeview
|
||||||
|
:active.sync="active"
|
||||||
|
:items="items"
|
||||||
|
:load-children="fetchChildren"
|
||||||
|
:open.sync="open"
|
||||||
|
activatable
|
||||||
|
selection-type="independent"
|
||||||
|
dark
|
||||||
|
color="warning"
|
||||||
|
transition
|
||||||
|
>
|
||||||
|
<template v-slot:prepend="{ item }">
|
||||||
|
<v-icon v-if="!item.children"> mdi-map-marker </v-icon>
|
||||||
|
</template>
|
||||||
|
</v-treeview>
|
||||||
|
<div class="text-center">
|
||||||
|
<v-btn
|
||||||
|
class="signin-btn"
|
||||||
|
rounded
|
||||||
|
color--text="white"
|
||||||
|
dark
|
||||||
|
:disabled="!valid"
|
||||||
|
@click="submitFunc()"
|
||||||
|
>
|
||||||
|
Add Location
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
|
</v-form>
|
||||||
|
<v-container class="pt-50">
|
||||||
|
<v-alert
|
||||||
|
v-model="success"
|
||||||
|
prominent
|
||||||
|
dense
|
||||||
|
type="success"
|
||||||
|
transition="slide-y-transition"
|
||||||
|
>
|
||||||
|
{{ successMessage }}
|
||||||
|
</v-alert>
|
||||||
|
<v-alert
|
||||||
|
v-model="error"
|
||||||
|
prominent
|
||||||
|
dense
|
||||||
|
type="error"
|
||||||
|
transition="slide-y-transition"
|
||||||
|
>
|
||||||
|
{{ errorMessage }}
|
||||||
|
</v-alert>
|
||||||
|
</v-container>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import validations from "@/utils/validations";
|
||||||
|
import { API } from "../utils/api_common";
|
||||||
|
import { mapState } from "vuex";
|
||||||
|
export default {
|
||||||
|
name: "AddLocationForm",
|
||||||
|
data: () => ({
|
||||||
|
valid: false,
|
||||||
|
name: "",
|
||||||
|
description: "",
|
||||||
|
parent: null,
|
||||||
|
successMessage: null,
|
||||||
|
errorMessage: null,
|
||||||
|
success: false,
|
||||||
|
error: false,
|
||||||
|
active: [],
|
||||||
|
open: [],
|
||||||
|
...validations,
|
||||||
|
...mapState(["topLocations"]),
|
||||||
|
}),
|
||||||
|
props: ["defaultParent", "closeFunc"],
|
||||||
|
computed: {
|
||||||
|
items() {
|
||||||
|
let locations = [];
|
||||||
|
if (this.topLocations() == null) {
|
||||||
|
this.$store.dispatch("loadTopLocations");
|
||||||
|
}
|
||||||
|
for (let l of this.topLocations()) {
|
||||||
|
locations.push(l);
|
||||||
|
}
|
||||||
|
return locations;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$refs.form.reset();
|
||||||
|
this.$store.dispatch("loadTopLocations");
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async fetchChildren(item) {
|
||||||
|
await API
|
||||||
|
.get(
|
||||||
|
"api/v1/locations/" + item.id + "/getChildren",
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: localStorage.getItem("token"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then((response) => {
|
||||||
|
if (response.status == 200 && response.data) {
|
||||||
|
for (let l of response.data) {
|
||||||
|
l.children = [];
|
||||||
|
}
|
||||||
|
item.children = response.data;
|
||||||
|
} else {
|
||||||
|
item.children = null;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log(error.response.data);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
submitFunc() {
|
||||||
|
let data = {
|
||||||
|
name: this.name,
|
||||||
|
description: this.description,
|
||||||
|
parent: null,
|
||||||
|
};
|
||||||
|
if (this.active.length == 1) {
|
||||||
|
data.parent = {
|
||||||
|
id: this.active[0],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
API
|
||||||
|
.post("api/v1/locations/", data, {
|
||||||
|
headers: {
|
||||||
|
Authorization: localStorage.getItem("token"),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
if (response.status == 201) {
|
||||||
|
this.successMessage = "Location Added";
|
||||||
|
this.success = true;
|
||||||
|
this.$refs.form.reset();
|
||||||
|
setTimeout(() => {
|
||||||
|
this.$router.push({
|
||||||
|
name: "Location",
|
||||||
|
params: { id: response.data.id },
|
||||||
|
});
|
||||||
|
this.success = false;
|
||||||
|
this.successMessage = "";
|
||||||
|
this.closeFunc("addLocation");
|
||||||
|
this.$store.dispatch("loadTopLocations");
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.errorMessage = error.response.data.error;
|
||||||
|
this.error = true;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.errorMessage = null;
|
||||||
|
this.error = false;
|
||||||
|
}, 3000);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@ -0,0 +1,73 @@
|
|||||||
|
<template>
|
||||||
|
<v-container>
|
||||||
|
<v-card class="pa-5" width="600px">
|
||||||
|
<StreamBarcodeReader v-if="barcodeReader"
|
||||||
|
@decode="(a, b, c) => onDecode(a, b, c)"
|
||||||
|
@loaded="() => onLoad()"
|
||||||
|
></StreamBarcodeReader>
|
||||||
|
</v-card>
|
||||||
|
<v-dialog dark transition="dialog-bottom-transition" v-model="itemDetailsDialog" width="auto">
|
||||||
|
<ItemDetailsCard :closeFunc="closeDialog" :item="item"/>
|
||||||
|
</v-dialog>
|
||||||
|
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { StreamBarcodeReader } from "vue-barcode-reader";
|
||||||
|
import { API } from "../utils/api_common";
|
||||||
|
import ItemDetailsCard from "./ItemDetailsCard.vue"
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "BarcodeDialog",
|
||||||
|
data: () => ({
|
||||||
|
item: null,
|
||||||
|
barcodeReader: true,
|
||||||
|
itemDetailsDialog: false,
|
||||||
|
}),
|
||||||
|
components: {
|
||||||
|
StreamBarcodeReader,
|
||||||
|
ItemDetailsCard,
|
||||||
|
},
|
||||||
|
props: ["closeFunc"],
|
||||||
|
mounted() {
|
||||||
|
this.barcodeReader = true
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onDecode(a, b, c) {
|
||||||
|
try{
|
||||||
|
let json = JSON.parse(a);
|
||||||
|
if (json.uri) {
|
||||||
|
this.barcodeReader = false;
|
||||||
|
this.closeFunc("barcode")
|
||||||
|
this.barcodeReader = true
|
||||||
|
|
||||||
|
window.location.href = json.uri
|
||||||
|
}
|
||||||
|
}catch{
|
||||||
|
API.get("api/v1/items/getItemByBarcode/" + a,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: localStorage.getItem("token"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
).then((response) => {
|
||||||
|
if (response.status == 200) {
|
||||||
|
this.item = response.data
|
||||||
|
this.itemDetailsDialog = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.barcodeReader = false
|
||||||
|
this.closeFunc("barcode")
|
||||||
|
this.barcodeReader = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLoad() {
|
||||||
|
console.log("barcode reader loaded")
|
||||||
|
},
|
||||||
|
closeDialog() {
|
||||||
|
this.itemDetailsDialog = false;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@ -1,151 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-container>
|
|
||||||
<v-row class="text-center">
|
|
||||||
<v-col cols="12">
|
|
||||||
<v-img
|
|
||||||
:src="require('../assets/logo.svg')"
|
|
||||||
class="my-3"
|
|
||||||
contain
|
|
||||||
height="200"
|
|
||||||
/>
|
|
||||||
</v-col>
|
|
||||||
|
|
||||||
<v-col class="mb-4">
|
|
||||||
<h1 class="display-2 font-weight-bold mb-3">
|
|
||||||
Welcome to Vuetify
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<p class="subheading font-weight-regular">
|
|
||||||
For help and collaboration with other Vuetify developers,
|
|
||||||
<br>please join our online
|
|
||||||
<a
|
|
||||||
href="https://community.vuetifyjs.com"
|
|
||||||
target="_blank"
|
|
||||||
>Discord Community</a>
|
|
||||||
</p>
|
|
||||||
</v-col>
|
|
||||||
|
|
||||||
<v-col
|
|
||||||
class="mb-5"
|
|
||||||
cols="12"
|
|
||||||
>
|
|
||||||
<h2 class="headline font-weight-bold mb-3">
|
|
||||||
What's next?
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<v-row justify="center">
|
|
||||||
<a
|
|
||||||
v-for="(next, i) in whatsNext"
|
|
||||||
:key="i"
|
|
||||||
:href="next.href"
|
|
||||||
class="subheading mx-3"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
{{ next.text }}
|
|
||||||
</a>
|
|
||||||
</v-row>
|
|
||||||
</v-col>
|
|
||||||
|
|
||||||
<v-col
|
|
||||||
class="mb-5"
|
|
||||||
cols="12"
|
|
||||||
>
|
|
||||||
<h2 class="headline font-weight-bold mb-3">
|
|
||||||
Important Links
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<v-row justify="center">
|
|
||||||
<a
|
|
||||||
v-for="(link, i) in importantLinks"
|
|
||||||
:key="i"
|
|
||||||
:href="link.href"
|
|
||||||
class="subheading mx-3"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
{{ link.text }}
|
|
||||||
</a>
|
|
||||||
</v-row>
|
|
||||||
</v-col>
|
|
||||||
|
|
||||||
<v-col
|
|
||||||
class="mb-5"
|
|
||||||
cols="12"
|
|
||||||
>
|
|
||||||
<h2 class="headline font-weight-bold mb-3">
|
|
||||||
Ecosystem
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<v-row justify="center">
|
|
||||||
<a
|
|
||||||
v-for="(eco, i) in ecosystem"
|
|
||||||
:key="i"
|
|
||||||
:href="eco.href"
|
|
||||||
class="subheading mx-3"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
{{ eco.text }}
|
|
||||||
</a>
|
|
||||||
</v-row>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-container>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'HelloWorld',
|
|
||||||
|
|
||||||
data: () => ({
|
|
||||||
ecosystem: [
|
|
||||||
{
|
|
||||||
text: 'vuetify-loader',
|
|
||||||
href: 'https://github.com/vuetifyjs/vuetify-loader',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'github',
|
|
||||||
href: 'https://github.com/vuetifyjs/vuetify',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'awesome-vuetify',
|
|
||||||
href: 'https://github.com/vuetifyjs/awesome-vuetify',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
importantLinks: [
|
|
||||||
{
|
|
||||||
text: 'Documentation',
|
|
||||||
href: 'https://vuetifyjs.com',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Chat',
|
|
||||||
href: 'https://community.vuetifyjs.com',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Made with Vuetify',
|
|
||||||
href: 'https://madewithvuejs.com/vuetify',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Twitter',
|
|
||||||
href: 'https://twitter.com/vuetifyjs',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Articles',
|
|
||||||
href: 'https://medium.com/vuetify',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
whatsNext: [
|
|
||||||
{
|
|
||||||
text: 'Explore components',
|
|
||||||
href: 'https://vuetifyjs.com/components/api-explorer',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Select a layout',
|
|
||||||
href: 'https://vuetifyjs.com/getting-started/pre-made-layouts',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Frequently Asked Questions',
|
|
||||||
href: 'https://vuetifyjs.com/getting-started/frequently-asked-questions',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@ -0,0 +1,51 @@
|
|||||||
|
<template>
|
||||||
|
<v-container>
|
||||||
|
<v-card dark class="pa-5" @click="itemDetailsDialog = true">
|
||||||
|
<v-list-item three-line>
|
||||||
|
<v-list-item-content>
|
||||||
|
<div class="text-right">
|
||||||
|
{{this.item.count}}
|
||||||
|
</div>
|
||||||
|
<v-list-item-title class="text-h5 mb-1">
|
||||||
|
{{this.item.name}}
|
||||||
|
</v-list-item-title>
|
||||||
|
<v-list-item-subtitle>
|
||||||
|
{{this.item.description}}
|
||||||
|
</v-list-item-subtitle>
|
||||||
|
</v-list-item-content>
|
||||||
|
</v-list-item>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-spacer/>
|
||||||
|
<v-btn fab dark small @click="addFunc(item.id)">
|
||||||
|
<v-icon>mdi-plus</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
<v-btn fab dark small @click="removeFunc(item.id)">
|
||||||
|
<v-icon>mdi-close</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
<v-dialog dark transition="dialog-bottom-transition" v-model="itemDetailsDialog" width="auto">
|
||||||
|
<ItemDetailsCard :closeFunc="closeDialog" :item="item"/>
|
||||||
|
</v-dialog>
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import ItemDetailsCard from "./ItemDetailsCard.vue"
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "ItemCard",
|
||||||
|
props: ["item", "addFunc", "removeFunc"],
|
||||||
|
data: () => ({
|
||||||
|
itemDetailsDialog: false,
|
||||||
|
}),
|
||||||
|
methods: {
|
||||||
|
closeDialog() {
|
||||||
|
this.itemDetailsDialog = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
ItemDetailsCard
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@ -0,0 +1,134 @@
|
|||||||
|
<template>
|
||||||
|
<v-card dark class="pa-5" max-width="600px">
|
||||||
|
<v-card-title class="text-h3 mb-1">
|
||||||
|
{{this.item.name}}
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-subtitle>
|
||||||
|
Size: {{this.item.size}}
|
||||||
|
<br/>
|
||||||
|
Unit: {{this.units[this.item.unit]}}
|
||||||
|
</v-card-subtitle>
|
||||||
|
|
||||||
|
<v-card-text>
|
||||||
|
<p>
|
||||||
|
{{this.item.description}}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Barcode: {{this.item.barcode}}
|
||||||
|
</p>
|
||||||
|
</v-card-text>
|
||||||
|
|
||||||
|
<v-list>
|
||||||
|
<v-subheader>LOCATIONS</v-subheader>
|
||||||
|
<v-list-item
|
||||||
|
v-for="location in locations"
|
||||||
|
:key="location.name + location.count"
|
||||||
|
>
|
||||||
|
<v-list-item-content>
|
||||||
|
<v-list-item-title>{{location.name}} - {{location.count}}</v-list-item-title>
|
||||||
|
</v-list-item-content>
|
||||||
|
<v-btn fab dark small @click="addItem(item.id, location.id)">
|
||||||
|
<v-icon>mdi-plus</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
<v-btn fab dark small @click="removeItem(item.id, location.id)">
|
||||||
|
<v-icon>mdi-close</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { API } from '../utils/api_common';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "ItemCard",
|
||||||
|
props: ["item"],
|
||||||
|
data: () => ({
|
||||||
|
units: [
|
||||||
|
"Teaspoon",
|
||||||
|
"Tablespoon",
|
||||||
|
"Cup",
|
||||||
|
"Ounce",
|
||||||
|
"Gram",
|
||||||
|
"Pound",
|
||||||
|
"Individual",
|
||||||
|
],
|
||||||
|
locations: [],
|
||||||
|
}),
|
||||||
|
created() {
|
||||||
|
this.getLocations()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async getLocations() {
|
||||||
|
let response = await API.get("api/v1/items/" + this.item.id + "/getLocationCount",
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: localStorage.getItem("token"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if (response.status == 200) {
|
||||||
|
let locs = []
|
||||||
|
for (const locID in response.data) {
|
||||||
|
let count = response.data[locID]
|
||||||
|
let locResp = await API.get("api/v1/locations/" + locID,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: localStorage.getItem("token"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if (locResp.status == 200) {
|
||||||
|
locs.push({
|
||||||
|
id: locResp.data.id,
|
||||||
|
name: locResp.data.name,
|
||||||
|
count: count,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
locs.sort(function(a,b) {
|
||||||
|
if (a.name < b.name) {
|
||||||
|
return -1
|
||||||
|
} else if (a.name > b.name) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
})
|
||||||
|
|
||||||
|
this.locations = locs
|
||||||
|
}
|
||||||
|
},
|
||||||
|
addItem(id, loc) {
|
||||||
|
API
|
||||||
|
.post("api/v1/locations/" + loc + "/addItem/" + id, {},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: localStorage.getItem("token"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then((response) => {
|
||||||
|
if (response.status == 201) {
|
||||||
|
this.getLocations()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
removeItem(id, loc) {
|
||||||
|
API
|
||||||
|
.post("api/v1/locations/" + loc + "/removeItem/" + id, {},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: localStorage.getItem("token"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then((response) => {
|
||||||
|
if (response.status == 201) {
|
||||||
|
this.getLocations()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
<template>
|
||||||
|
<v-card dark class="pa-5" :to="{ name: 'Location', params: {id: location.id} }">
|
||||||
|
<v-list-item three-line>
|
||||||
|
<v-list-item-content>
|
||||||
|
<v-list-item-title class="text-h5 mb-1">
|
||||||
|
{{this.location.name}}
|
||||||
|
</v-list-item-title>
|
||||||
|
<v-list-item-subtitle>
|
||||||
|
{{this.location.description}}
|
||||||
|
</v-list-item-subtitle>
|
||||||
|
</v-list-item-content>
|
||||||
|
</v-list-item>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "LocationCard",
|
||||||
|
props: ["location"],
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
<template>
|
||||||
|
<v-card class="pa-5" width="600px">
|
||||||
|
<v-form dark v-model="valid" ref="form">
|
||||||
|
</v-form>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import validations from "@/utils/validations";
|
||||||
|
import { API } from "../utils/api_common";
|
||||||
|
import { mapState } from "vuex";
|
||||||
|
export default {
|
||||||
|
name: "SearchForm",
|
||||||
|
data: () => ({
|
||||||
|
}),
|
||||||
|
components: {
|
||||||
|
},
|
||||||
|
props: ["closeFunc"],
|
||||||
|
mounted() {
|
||||||
|
//TODO: Process defaultLocation
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
submitFunc() {
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
module.exports = {
|
||||||
|
NODE_ENV: '"development"',
|
||||||
|
VUE_APP_BASE_URL: "http://quartermaster.djpianalto.com/",
|
||||||
|
}
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
function getTitle (vm) {
|
||||||
|
const { title } = vm.$options
|
||||||
|
if (title) {
|
||||||
|
return typeof title === 'function'
|
||||||
|
? title.call(vm)
|
||||||
|
: title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default {
|
||||||
|
mounted () {
|
||||||
|
const title = getTitle(this)
|
||||||
|
if (title) {
|
||||||
|
document.title = "Quartermaster - " + title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
export const API = axios.create({
|
||||||
|
baseURL: 'https://quartermaster.djpianalto.com'
|
||||||
|
})
|
||||||
@ -1,35 +1,33 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-container>
|
<v-container>
|
||||||
<v-row>
|
<v-row :key="location.id + location.description + location.children.length" v-for="location in topLocations">
|
||||||
<template v-for="n in 4">
|
<v-col :key="location.id" class="mt-2" cols="12">
|
||||||
<v-col
|
<v-btn text class="grey--text lighten-4" :to="{ name: 'Location', params: {id: location.id} }">{{
|
||||||
:key="n"
|
location.name
|
||||||
class="mt-2"
|
}}</v-btn>
|
||||||
cols="12"
|
|
||||||
>
|
|
||||||
<strong class="grey--text lighten-4">Category {{ n }}</strong>
|
|
||||||
</v-col>
|
</v-col>
|
||||||
|
<v-col v-for="child in location.children" v-bind:key="`${child.id}`">
|
||||||
<v-col
|
<LocationCard :location="child"/>
|
||||||
v-for="j in 6"
|
|
||||||
:key="`${n}${j}`"
|
|
||||||
cols="6"
|
|
||||||
md="2"
|
|
||||||
>
|
|
||||||
<v-sheet height="150"></v-sheet>
|
|
||||||
</v-col>
|
</v-col>
|
||||||
</template>
|
|
||||||
</v-row>
|
</v-row>
|
||||||
|
|
||||||
</v-container>
|
</v-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { mapState } from "vuex";
|
||||||
|
import LocationCard from "@/components/LocationCard.vue"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Dashboard',
|
name: "Dashboard",
|
||||||
|
title: "Dashboard",
|
||||||
components: {
|
components: {
|
||||||
|
LocationCard,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState(["topLocations"]),
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.$store.dispatch("loadTopLocations");
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -1,15 +0,0 @@
|
|||||||
<template>
|
|
||||||
<hello-world />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import HelloWorld from '../components/HelloWorld'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'Home',
|
|
||||||
|
|
||||||
components: {
|
|
||||||
HelloWorld,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@ -0,0 +1,197 @@
|
|||||||
|
<template>
|
||||||
|
<v-container>
|
||||||
|
<strong class="grey--text text-h3 lighten-4" v-if="currentLocation">
|
||||||
|
{{currentLocation.name}}
|
||||||
|
</strong>
|
||||||
|
<br>
|
||||||
|
<v-btn dark class="ma-2" @click="displayQR = !displayQR">QR</v-btn>
|
||||||
|
<v-btn dark class="ma-2" @click="print">Print</v-btn>
|
||||||
|
<br>
|
||||||
|
<div id="qrcode-wrapper">
|
||||||
|
<img id="qrcode" v-show="this.displayQR"/>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<v-card
|
||||||
|
class="mx-auto grey darken-4"
|
||||||
|
max-width="800"
|
||||||
|
flat
|
||||||
|
>
|
||||||
|
<v-container fluid v-if="this.items.length > 0">
|
||||||
|
<strong class="grey--text text-h5 lighten-4">
|
||||||
|
Items
|
||||||
|
</strong>
|
||||||
|
<v-row dense class="mt-5">
|
||||||
|
<v-col
|
||||||
|
v-for="item in this.items"
|
||||||
|
:key="item.name + item.description + item.count"
|
||||||
|
:cols=6
|
||||||
|
>
|
||||||
|
<ItemCard :item="item" :addFunc="addItem" :removeFunc="removeItem"/>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-container>
|
||||||
|
<v-container fluid v-if="this.children.length > 0">
|
||||||
|
<strong class="grey--text text-h5 lighten-4">
|
||||||
|
Child Locations
|
||||||
|
</strong>
|
||||||
|
<v-row dense class="mt-5">
|
||||||
|
<v-col
|
||||||
|
v-for="location in this.children"
|
||||||
|
:key="location.name + location.description"
|
||||||
|
:cols=6
|
||||||
|
>
|
||||||
|
<LocationCard :location="location"/>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-container>
|
||||||
|
</v-card>
|
||||||
|
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapState } from "vuex";
|
||||||
|
import QRious from "qrious";
|
||||||
|
import ItemCard from "@/components/ItemCard.vue"
|
||||||
|
import LocationCard from "@/components/LocationCard.vue"
|
||||||
|
import { API } from '../utils/api_common';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Location",
|
||||||
|
title() {
|
||||||
|
return "Location"
|
||||||
|
},
|
||||||
|
data: () => ({
|
||||||
|
items: [],
|
||||||
|
children: [],
|
||||||
|
displayQR: false,
|
||||||
|
}),
|
||||||
|
components: {
|
||||||
|
ItemCard,
|
||||||
|
LocationCard,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState(["currentLocation"]),
|
||||||
|
},
|
||||||
|
async created() {
|
||||||
|
await this.$store.dispatch("loadCurrentLocation", {id: this.$route.params.id});
|
||||||
|
this.getItems();
|
||||||
|
this.getChildren();
|
||||||
|
this.generateQR();
|
||||||
|
this.$watch(
|
||||||
|
() => this.$route.params,
|
||||||
|
(toParams, fromParams) => {
|
||||||
|
if (toParams !== fromParams) {
|
||||||
|
this.$store.dispatch("loadCurrentLocation", {id: this.$route.params.id});
|
||||||
|
this.getItems();
|
||||||
|
this.getChildren();
|
||||||
|
this.generateQR();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
generateQR() {
|
||||||
|
var qr = {
|
||||||
|
"name": this.currentLocation.name,
|
||||||
|
"id": this.currentLocation.id,
|
||||||
|
"uri": "https://quartermaster.djpianalto.com/#/location/" + this.currentLocation.id
|
||||||
|
}
|
||||||
|
var qr = new QRious({
|
||||||
|
element: document.getElementById('qrcode'),
|
||||||
|
level: "M",
|
||||||
|
value: JSON.stringify(qr),
|
||||||
|
size: 150
|
||||||
|
});
|
||||||
|
},
|
||||||
|
print() {
|
||||||
|
this.$htmlToPaper('qrcode-wrapper')
|
||||||
|
},
|
||||||
|
getItems() {
|
||||||
|
API
|
||||||
|
.get("api/v1/locations/" + this.$route.params.id + "/items",
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: localStorage.getItem("token"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then((response) => {
|
||||||
|
if (response.status == 200) {
|
||||||
|
this.items = response.data
|
||||||
|
this.items.sort(function(a,b) {
|
||||||
|
if (a.name < b.name) {
|
||||||
|
return -1
|
||||||
|
} else if (a.name > b.name) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
getChildren() {
|
||||||
|
API
|
||||||
|
.get("api/v1/locations/" + this.$route.params.id + "/getChildren",
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: localStorage.getItem("token"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then((response) => {
|
||||||
|
if (response.status == 200) {
|
||||||
|
this.children = response.data
|
||||||
|
this.children.sort(function(a,b) {
|
||||||
|
if (a.name < b.name) {
|
||||||
|
return -1
|
||||||
|
} else if (a.name > b.name) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
addItem(id) {
|
||||||
|
for (let i = 0; i < this.items.length; i++) {
|
||||||
|
if (this.items[i].id == id) {
|
||||||
|
API
|
||||||
|
.post("api/v1/locations/" + this.$route.params.id + "/addItem/" + id, {},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: localStorage.getItem("token"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then((response) => {
|
||||||
|
if (response.status == 201) {
|
||||||
|
this.getItems()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
removeItem(id) {
|
||||||
|
for (let i = 0; i < this.items.length; i++) {
|
||||||
|
if (this.items[i].id == id) {
|
||||||
|
API
|
||||||
|
.post("api/v1/locations/" + this.$route.params.id + "/removeItem/" + id, {},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: localStorage.getItem("token"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then((response) => {
|
||||||
|
if (response.status == 201) {
|
||||||
|
this.getItems()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@ -0,0 +1,105 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/dustinpianalto/errors"
|
||||||
|
"github.com/dustinpianalto/quartermaster"
|
||||||
|
"github.com/dustinpianalto/quartermaster/internal/jwt"
|
||||||
|
"github.com/dustinpianalto/quartermaster/pkg/services"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Mount(r *mux.Router, path string, handler http.Handler) {
|
||||||
|
r.PathPrefix(path).Handler(
|
||||||
|
http.StripPrefix(
|
||||||
|
strings.TrimSuffix(path, "/"),
|
||||||
|
handler,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
type RootHandler func(*http.ResponseWriter, *http.Request) error
|
||||||
|
|
||||||
|
func (rh RootHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
err := rh(&w, r)
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println(err)
|
||||||
|
|
||||||
|
e, ok := err.(*errors.Error)
|
||||||
|
if !ok {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
|
||||||
|
if errors.Is(errors.Permission, e) {
|
||||||
|
body := getErrorBody("Permission Denied")
|
||||||
|
w.WriteHeader(http.StatusForbidden)
|
||||||
|
w.Write(body)
|
||||||
|
}
|
||||||
|
|
||||||
|
if errors.Is(errors.Incorrect, e) && strings.Contains(string(e.Method), "login") {
|
||||||
|
body := getErrorBody("Invalid Login Credentials")
|
||||||
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
w.Write(body)
|
||||||
|
}
|
||||||
|
|
||||||
|
if errors.Is(errors.Incorrect, e) && strings.Contains(string(e.Method), "refresh") {
|
||||||
|
body := getErrorBody("Missing or Invalid Cookie")
|
||||||
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
w.Write(body)
|
||||||
|
}
|
||||||
|
|
||||||
|
if errors.Is(errors.Malformed, e) {
|
||||||
|
body := getErrorBody("Bad Request")
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
w.Write(body)
|
||||||
|
}
|
||||||
|
|
||||||
|
if errors.Is(errors.Internal, e) {
|
||||||
|
body := getErrorBody("Internal Server Error")
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
w.Write(body)
|
||||||
|
}
|
||||||
|
|
||||||
|
if errors.Is(errors.Conflict, e) && strings.Contains(string(e.Method), "register") {
|
||||||
|
body := getErrorBody("User already exists")
|
||||||
|
w.WriteHeader(http.StatusConflict)
|
||||||
|
w.Write(body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getErrorBody(s string) []byte {
|
||||||
|
return []byte(fmt.Sprintf("{\"error\": \"%s\"}", s))
|
||||||
|
}
|
||||||
|
|
||||||
|
type authenticatedHandler func(*http.ResponseWriter, *http.Request, *quartermaster.User) error
|
||||||
|
|
||||||
|
func AuthenticationMiddleware(next authenticatedHandler) RootHandler {
|
||||||
|
const method = "utils/AuthenticationMiddleware"
|
||||||
|
return RootHandler(func(w *http.ResponseWriter, r *http.Request) error {
|
||||||
|
claims, err := jwt.AuthenticateJWTToken(r)
|
||||||
|
if err != nil {
|
||||||
|
return errors.E(method, errors.Permission, "invalid token", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
u, err := services.UserService.User(claims.Username)
|
||||||
|
if err != nil {
|
||||||
|
return errors.E(method, errors.Permission, "user not found", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = next(w, r, u)
|
||||||
|
if err != nil {
|
||||||
|
return errors.E(method, "error in handler", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
Loading…
Reference in new issue