summaryrefslogtreecommitdiff
path: root/server/guest/handler.go
diff options
context:
space:
mode:
authorMichael Hunteman <huntemanmt@gmail.com>2025-01-31 12:32:09 -0600
committerMichael Hunteman <huntemanmt@gmail.com>2025-01-31 12:32:09 -0600
commit6150d079ea06ba55e5f145b67f02065d94b757f9 (patch)
treed8994e3793fc55e052c7d7a780112b323128de13 /server/guest/handler.go
parentff05f3941721de3ff343421968422d216d82e952 (diff)
Improve logging and error handling
Diffstat (limited to 'server/guest/handler.go')
-rw-r--r--server/guest/handler.go279
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]
}