diff options
author | Michael Hunteman <huntemanmt@gmail.com> | 2025-01-31 12:32:09 -0600 |
---|---|---|
committer | Michael Hunteman <huntemanmt@gmail.com> | 2025-01-31 12:32:09 -0600 |
commit | 6150d079ea06ba55e5f145b67f02065d94b757f9 (patch) | |
tree | d8994e3793fc55e052c7d7a780112b323128de13 /server/guest/handler.go | |
parent | ff05f3941721de3ff343421968422d216d82e952 (diff) |
Improve logging and error handling
Diffstat (limited to 'server/guest/handler.go')
-rw-r--r-- | server/guest/handler.go | 279 |
1 files changed, 123 insertions, 156 deletions
diff --git a/server/guest/handler.go b/server/guest/handler.go index 6829086..f596b05 100644 --- a/server/guest/handler.go +++ b/server/guest/handler.go @@ -2,12 +2,12 @@ package guest import ( "encoding/json" - "errors" "net/http" "os" "regexp" "time" + "git.huntm.net/wedding/server/errors" "github.com/golang-jwt/jwt/v5" ) @@ -28,314 +28,281 @@ type GuestStore interface { Delete(id string) error } -type appError struct { - Error error - Message string - Code int -} - -func NewGuestHandler(guestStore GuestStore) *GuestHandler { +func NewGuestHandler(s GuestStore) *GuestHandler { return &GuestHandler{ - guestStore, + s, } } -func (handler *GuestHandler) ServeHTTP(responseWriter http.ResponseWriter, - request *http.Request) { +func (g *GuestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { switch { - case request.Method == http.MethodOptions: - responseWriter.WriteHeader(http.StatusOK) - case request.Method == http.MethodPost && request.URL.Path == "/api/guests/login": - handler.handleLogIn(responseWriter, request) - case request.Method == http.MethodPut && guestIdRegex.MatchString(request.URL.Path): - handler.handlePut(responseWriter, request) - case request.Method == http.MethodGet && guestRegex.MatchString(request.URL.Path): - handler.handleGet(responseWriter, request) - case request.Method == http.MethodPost && guestRegex.MatchString(request.URL.Path): - handler.handlePost(responseWriter, request) - case request.Method == http.MethodDelete && guestIdRegex.MatchString(request.URL.Path): - handler.handleDelete(responseWriter, request) + case r.Method == http.MethodOptions: + w.WriteHeader(http.StatusOK) + case r.Method == http.MethodPost && r.URL.Path == "/api/guests/login": + g.handleLogIn(w, r) + case r.Method == http.MethodPut && guestIdRegex.MatchString(r.URL.Path): + g.handlePut(w, r) + case r.Method == http.MethodGet && guestRegex.MatchString(r.URL.Path): + g.handleGet(w, r) + case r.Method == http.MethodPost && guestRegex.MatchString(r.URL.Path): + g.handlePost(w, r) + case r.Method == http.MethodDelete && guestIdRegex.MatchString(r.URL.Path): + g.handleDelete(w, r) default: - responseWriter.WriteHeader(http.StatusNotFound) + w.WriteHeader(http.StatusNotFound) } } -func (handler *GuestHandler) handleLogIn(responseWriter http.ResponseWriter, - request *http.Request) { - token, err := handler.logIn(request) +func (g *GuestHandler) handleLogIn(w http.ResponseWriter, r *http.Request) { + token, err := g.logIn(r) if err != nil { - http.Error(responseWriter, err.Message, err.Code) + http.Error(w, string(err.Message), err.Status) } else { - responseWriter.Write(token) + w.Write(token) } } -func (handler *GuestHandler) handlePut(responseWriter http.ResponseWriter, - request *http.Request) { - if err := handler.putGuest(request); err != nil { - http.Error(responseWriter, err.Message, err.Code) +func (g *GuestHandler) handlePut(w http.ResponseWriter, r *http.Request) { + if err := g.putGuest(r); err != nil { + http.Error(w, string(err.Message), err.Status) } else { - responseWriter.WriteHeader(http.StatusOK) + w.WriteHeader(http.StatusOK) } } -func (handler *GuestHandler) handleGet(responseWriter http.ResponseWriter, - request *http.Request) { - guests, err := handler.getGuests(request) +func (g *GuestHandler) handleGet(w http.ResponseWriter, r *http.Request) { + guests, err := g.getGuests(r) if err != nil { - http.Error(responseWriter, err.Message, err.Code) + http.Error(w, string(err.Message), err.Status) } else { - responseWriter.Write(guests) + w.Write(guests) } } -func (handler *GuestHandler) handlePost(responseWriter http.ResponseWriter, - request *http.Request) { - if err := handler.postGuest(request); err != nil { - http.Error(responseWriter, err.Message, err.Code) +func (g *GuestHandler) handlePost(w http.ResponseWriter, r *http.Request) { + if err := g.postGuest(r); err != nil { + http.Error(w, string(err.Message), err.Status) } else { - responseWriter.WriteHeader(http.StatusOK) + w.WriteHeader(http.StatusOK) } } -func (handler *GuestHandler) handleDelete(responseWriter http.ResponseWriter, +func (g *GuestHandler) handleDelete(w http.ResponseWriter, request *http.Request) { - if err := handler.deleteGuest(request); err != nil { - http.Error(responseWriter, err.Message, err.Code) + if err := g.deleteGuest(request); err != nil { + http.Error(w, string(err.Message), err.Status) } else { - responseWriter.WriteHeader(http.StatusOK) + w.WriteHeader(http.StatusOK) } } -func (handler *GuestHandler) logIn(request *http.Request) ([]byte, *appError) { - name, err := handler.decodeName(request) +func (g *GuestHandler) logIn(r *http.Request) ([]byte, *errors.AppError) { + name, err := g.decodeName(r) if err != nil { - return []byte{}, &appError{err, "{ \" message\": \"Failed to unmarshal name\" }", - http.StatusBadRequest} + return nil, errors.NewAppError(http.StatusBadRequest, err.Error()) } - guest, err := handler.store.Find(name) + guest, err := g.store.Find(name) if err != nil { - return []byte{}, &appError{err, "{ \"message\": \"Guest not found\" }", - http.StatusUnauthorized} + return nil, errors.NewAppError(http.StatusUnauthorized, err.Error()) } - expirationTime := handler.setExpirationTime() - claims := handler.createClaims(name, expirationTime) - key, err := handler.readGuestKey() + expirationTime := g.setExpirationTime() + claims := g.createClaims(name, expirationTime) + key, err := g.readGuestKey() if err != nil { - return []byte{}, &appError{err, "{ \"message\": \"Failed to read secret key\" }", - http.StatusInternalServerError} + return nil, errors.NewAppError(http.StatusInternalServerError, err.Error()) } - token, err := handler.createToken(claims, key) + token, err := g.createToken(claims, key) if err != nil { - return []byte{}, &appError{err, "{ \"message\": \"Failed to create token\" }", - http.StatusInternalServerError} + return nil, errors.NewAppError(http.StatusInternalServerError, err.Error()) } - jsonBytes, err := handler.marshalResponse(guest, token) + jsonBytes, err := g.marshalResponse(guest, token) if err != nil { - return []byte{}, &appError{err, "{ \"message\": \"Failed to marshal response\" }", - http.StatusInternalServerError} + return nil, errors.NewAppError(http.StatusInternalServerError, err.Error()) } return jsonBytes, nil } -func (handler *GuestHandler) decodeName(request *http.Request) (Name, error) { +func (g *GuestHandler) decodeName(r *http.Request) (Name, error) { var name Name - err := json.NewDecoder(request.Body).Decode(&name) - defer request.Body.Close() + err := json.NewDecoder(r.Body).Decode(&name) + r.Body.Close() return name, err } -func (handler *GuestHandler) setExpirationTime() time.Time { +func (g *GuestHandler) setExpirationTime() time.Time { return time.Now().Add(15 * time.Minute) } -func (handler *GuestHandler) createClaims(name Name, expirationTime time.Time) *Claims { +func (g *GuestHandler) createClaims(name Name, time time.Time) *Claims { return &Claims{ Name: name, RegisteredClaims: jwt.RegisteredClaims{ - ExpiresAt: jwt.NewNumericDate(expirationTime), + ExpiresAt: jwt.NewNumericDate(time), }, } } -func (handler *GuestHandler) readGuestKey() ([]byte, error) { +func (g *GuestHandler) readGuestKey() ([]byte, error) { return os.ReadFile(os.Getenv("GUEST_KEY")) } -func (handler *GuestHandler) readAdminKey() ([]byte, error) { +func (g *GuestHandler) readAdminKey() ([]byte, error) { return os.ReadFile(os.Getenv("ADMIN_KEY")) } -func (handler *GuestHandler) createToken(claims *Claims, key []byte) (string, error) { +func (g *GuestHandler) createToken(claims *Claims, key []byte) (string, error) { token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) return token.SignedString(key) } -func (handler *GuestHandler) marshalResponse(guest Guest, token string) ([]byte, error) { - loginResponse := handler.createLoginResponse(guest, token) +func (g *GuestHandler) marshalResponse(guest Guest, token string) ([]byte, error) { + loginResponse := g.createLoginResponse(guest, token) return json.Marshal(loginResponse) } -func (handler *GuestHandler) createLoginResponse(weddingGuest Guest, token string) *Login { +func (g *GuestHandler) createLoginResponse(guest Guest, token string) *Login { return &Login{ - Guest: weddingGuest, + Guest: guest, Token: token, } } -func (handler *GuestHandler) putGuest(request *http.Request) *appError { - guestKey, err := handler.readGuestKey() +func (g *GuestHandler) putGuest(r *http.Request) *errors.AppError { + guestKey, err := g.readGuestKey() if err != nil { - return &appError{err, "{ \"message\": \"Failed to read secret key\" }", - http.StatusInternalServerError} + return errors.NewAppError(http.StatusInternalServerError, err.Error()) } - if err := handler.validateToken(request, guestKey); err != nil { + if err := g.validateToken(r, guestKey); err != nil { return err } - if handler.findId(request) { - return &appError{errors.New("ID not found"), "{ \"message\": \"ID not found\" }", - http.StatusNotFound} + if g.findId(r) { + return errors.NewAppError(http.StatusNotFound, "cannot update guest that does not exist") } - guest, err := handler.decodeGuest(request) + guest, err := g.decodeGuest(r) if err != nil { - return &appError{err, "{ \"message\": \"Invalid guest\" }", - http.StatusBadRequest} + return errors.NewAppError(http.StatusNotFound, err.Error()) } - if err := handler.store.Update(guest); err != nil { - return &appError{err, "{ \"message\": \"Failed to update guest\" }", - http.StatusInternalServerError} + if err := g.store.Update(guest); err != nil { + return errors.NewAppError(http.StatusInternalServerError, err.Error()) } return nil } -func (handler *GuestHandler) validateToken(request *http.Request, key []byte) *appError { - authorizationHeader := handler.getToken(request) - claims := handler.newClaims() - token, err := handler.parseWithClaims(authorizationHeader, claims, key) +func (g *GuestHandler) validateToken(r *http.Request, key []byte) *errors.AppError { + authorizationHeader := g.getToken(r) + claims := g.newClaims() + token, err := g.parseWithClaims(authorizationHeader, claims, key) if err != nil { if err == jwt.ErrSignatureInvalid { - return &appError{err, "{ \"message\": \"Invalid signature\" }", - http.StatusUnauthorized} + return errors.NewAppError(http.StatusUnauthorized, err.Error()) } - return &appError{err, "{ \"message\": \"Failed to parse claims\" }", - http.StatusBadRequest} + return errors.NewAppError(http.StatusBadRequest, err.Error()) } if !token.Valid { - return &appError{err, "{ \"message\": \"Invalid token\" }", - http.StatusUnauthorized} + return errors.NewAppError(http.StatusUnauthorized, "invalid token") } return nil } -func (handler *GuestHandler) getToken(request *http.Request) string { - return request.Header.Get("Authorization") +func (g *GuestHandler) getToken(r *http.Request) string { + return r.Header.Get("Authorization") } -func (handler *GuestHandler) newClaims() *Claims { +func (g *GuestHandler) newClaims() *Claims { return &Claims{} } -func (handler *GuestHandler) parseWithClaims(token string, claims *Claims, +func (g *GuestHandler) parseWithClaims(token string, claims *Claims, key []byte) (*jwt.Token, error) { return jwt.ParseWithClaims(token, claims, func(token *jwt.Token) (any, error) { return key, nil }) } -func (handler *GuestHandler) findId(request *http.Request) bool { - matches := guestIdRegex.FindStringSubmatch(request.URL.Path) +func (g *GuestHandler) findId(r *http.Request) bool { + matches := guestIdRegex.FindStringSubmatch(r.URL.Path) return len(matches) < 2 } -func (handler *GuestHandler) decodeGuest(request *http.Request) (Guest, error) { +func (g *GuestHandler) decodeGuest(r *http.Request) (Guest, error) { var guest Guest - err := json.NewDecoder(request.Body).Decode(&guest) - defer request.Body.Close() + err := json.NewDecoder(r.Body).Decode(&guest) + defer r.Body.Close() return guest, err } -func (handler *GuestHandler) getGuests(request *http.Request) ([]byte, *appError) { - adminKey, err := handler.readAdminKey() +func (g *GuestHandler) getGuests(r *http.Request) ([]byte, *errors.AppError) { + adminKey, err := g.readAdminKey() if err != nil { - return []byte{}, &appError{err, "{ \"message\": \"Failed to read secret key\" }", - http.StatusInternalServerError} + return nil, errors.NewAppError(http.StatusInternalServerError, err.Error()) } - if err := handler.validateToken(request, adminKey); err != nil { - return []byte{}, err + if err := g.validateToken(r, adminKey); err != nil { + return nil, err } - guests, err := handler.store.Get() + guests, err := g.store.Get() if err != nil { - return []byte{}, &appError{err, "{ \"message\": \"Failed to get guests\" }", - http.StatusInternalServerError} + return nil, errors.NewAppError(http.StatusInternalServerError, err.Error()) } jsonBytes, err := json.Marshal(guests) if err != nil { - return []byte{}, &appError{err, "{ \"message\": \"Failed to marshal guests\" }", - http.StatusInternalServerError} + return nil, errors.NewAppError(http.StatusInternalServerError, err.Error()) } return jsonBytes, nil } -func (handler *GuestHandler) postGuest(request *http.Request) *appError { - adminKey, err := handler.readAdminKey() +func (g *GuestHandler) postGuest(r *http.Request) *errors.AppError { + adminKey, err := g.readAdminKey() if err != nil { - return &appError{err, "{ \"message\": \"Failed to read secret key\" }", - http.StatusInternalServerError} + return errors.NewAppError(http.StatusInternalServerError, err.Error()) } - if err := handler.validateToken(request, adminKey); err != nil { + if err := g.validateToken(r, adminKey); err != nil { return err } - guest, err := handler.decodeGuest(request) + guest, err := g.decodeGuest(r) if err != nil { - return &appError{err, "{ \"message\": \"Invalid guest\" }", - http.StatusBadRequest} + return errors.NewAppError(http.StatusBadRequest, err.Error()) } - guests, err := handler.store.Get() + guests, err := g.store.Get() if err != nil { - return &appError{err, "{ \"message\": \"Failed to get guests\" }", - http.StatusInternalServerError} + return errors.NewAppError(http.StatusInternalServerError, err.Error()) } - if err := handler.checkExistingGuests(guests, guest); err != nil { - return &appError{err, "{ \"message\": \"ID already exists\" }", - http.StatusConflict} + if g.isExistingGuest(guests, guest) { + return errors.NewAppError(http.StatusConflict, "guest already exists") } - if err := handler.store.Add(guest); err != nil { - return &appError{err, "{ \"message\": \"Failed to add guest\" }", - http.StatusInternalServerError} + if err := g.store.Add(guest); err != nil { + return errors.NewAppError(http.StatusInternalServerError, err.Error()) } return nil } -func (handler *GuestHandler) checkExistingGuests(guests []Guest, newGuest Guest) error { +func (g *GuestHandler) isExistingGuest(guests []Guest, newGuest Guest) bool { for _, guest := range guests { if guest.Id == newGuest.Id { - return errors.New("ID already exists") + return true } } - return nil + return false } -func (handler *GuestHandler) deleteGuest(request *http.Request) *appError { - adminKey, err := handler.readAdminKey() +func (g *GuestHandler) deleteGuest(r *http.Request) *errors.AppError { + adminKey, err := g.readAdminKey() if err != nil { - return &appError{err, "{ \"message\": \"Failed to read secret key\" }", - http.StatusInternalServerError} + return errors.NewAppError(http.StatusInternalServerError, err.Error()) } - if err := handler.validateToken(request, adminKey); err != nil { + if err := g.validateToken(r, adminKey); err != nil { return err } - if handler.findId(request) { - return &appError{errors.New("ID not found"), "{ \"message\": \"ID not found\" }", http.StatusNotFound} + if g.findId(r) { + return errors.NewAppError(http.StatusNotFound, "cannot delete guest that does not exist") } - guestId := getId(request) - err = handler.store.Delete(guestId) + guestId := getId(r) + err = g.store.Delete(guestId) if err != nil { - return &appError{err, "{ \"message\": \"Failed to get guests\" }", - http.StatusInternalServerError} + return errors.NewAppError(http.StatusInternalServerError, err.Error()) } return nil } -func getId(request *http.Request) string { - return guestIdRegex.FindStringSubmatch(request.URL.Path)[1] +func getId(r *http.Request) string { + return guestIdRegex.FindStringSubmatch(r.URL.Path)[1] } |