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 | |
parent | ff05f3941721de3ff343421968422d216d82e952 (diff) |
Improve logging and error handling
-rw-r--r-- | server/admin/handler.go | 89 | ||||
-rw-r--r-- | server/cmd/main.go | 5 | ||||
-rw-r--r-- | server/errors/models.go | 19 | ||||
-rw-r--r-- | server/guest/handler.go | 279 | ||||
-rw-r--r-- | server/middleware/logging.go | 34 |
5 files changed, 199 insertions, 227 deletions
diff --git a/server/admin/handler.go b/server/admin/handler.go index 29e2c11..b8f1d7f 100644 --- a/server/admin/handler.go +++ b/server/admin/handler.go @@ -6,6 +6,7 @@ import ( "os" "time" + "git.huntm.net/wedding/server/errors" "git.huntm.net/wedding/server/guest" "github.com/golang-jwt/jwt/v5" ) @@ -19,86 +20,72 @@ type adminStore interface { Find(admin Admin) (Admin, error) } -type appError struct { - Error error - Message string - Code int +func NewAdminHandler(a adminStore, g guest.GuestStore) *AdminHandler { + return &AdminHandler{a, g} } -func NewAdminHandler(adminStore adminStore, guestStore guest.GuestStore) *AdminHandler { - return &AdminHandler{adminStore, guestStore} -} - -func (adminHandler *AdminHandler) ServeHTTP(responseWriter http.ResponseWriter, - request *http.Request) { +func (a *AdminHandler) 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/admin/login": - adminHandler.handleLogIn(responseWriter, request) + case r.Method == http.MethodOptions: + w.WriteHeader(http.StatusOK) + case r.Method == http.MethodPost && r.URL.Path == "/api/admin/login": + a.handleLogIn(w, r) default: - responseWriter.WriteHeader(http.StatusNotFound) + w.WriteHeader(http.StatusNotFound) } } -func (adminHandler *AdminHandler) handleLogIn(responseWriter http.ResponseWriter, - request *http.Request) { - token, err := adminHandler.logIn(request) +func (a *AdminHandler) handleLogIn(w http.ResponseWriter, r *http.Request) { + token, err := a.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 (adminHandler *AdminHandler) logIn(request *http.Request) ([]byte, *appError) { - requestAdmin, err := adminHandler.decodeCredentials(request) +func (a *AdminHandler) logIn(r *http.Request) ([]byte, *errors.AppError) { + requestAdmin, err := a.decodeCredentials(r) if err != nil { - return []byte{}, &appError{err, "{ \"message\": \"Failed to unmarshal request\" }", - http.StatusBadRequest} + return nil, errors.NewAppError(http.StatusBadRequest, err.Error()) } - _, err = adminHandler.adminStore.Find(requestAdmin) + _, err = a.adminStore.Find(requestAdmin) if err != nil { - return []byte{}, &appError{err, "{ \"message\": \"Invalid username or password\" }", - http.StatusUnauthorized} + return nil, errors.NewAppError(http.StatusUnauthorized, err.Error()) } - expirationTime := adminHandler.setExpirationTime() - claims := adminHandler.createClaims(requestAdmin, expirationTime) - key, err := adminHandler.readKey() + expirationTime := a.setExpirationTime() + claims := a.createClaims(requestAdmin, expirationTime) + key, err := a.readKey() 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 := adminHandler.createToken(claims, key) + token, err := a.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()) } - guests, err := adminHandler.guestStore.Get() + guests, err := a.guestStore.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 := adminHandler.marshalResponse(guests, token) + jsonBytes, err := a.marshalResponse(guests, 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 (adminHandler *AdminHandler) decodeCredentials(request *http.Request) (Admin, error) { +func (a *AdminHandler) decodeCredentials(r *http.Request) (Admin, error) { var admin Admin - err := json.NewDecoder(request.Body).Decode(&admin) - defer request.Body.Close() + err := json.NewDecoder(r.Body).Decode(&admin) + defer r.Body.Close() return admin, err } -func (adminHandler *AdminHandler) setExpirationTime() time.Time { +func (a *AdminHandler) setExpirationTime() time.Time { return time.Now().Add(15 * time.Minute) } -func (adminHandler *AdminHandler) createClaims(admin Admin, expirationTime time.Time) *Claims { +func (a *AdminHandler) createClaims(admin Admin, expirationTime time.Time) *Claims { return &Claims{ admin, jwt.RegisteredClaims{ @@ -107,22 +94,22 @@ func (adminHandler *AdminHandler) createClaims(admin Admin, expirationTime time. } } -func (adminHandler *AdminHandler) readKey() ([]byte, error) { +func (a *AdminHandler) readKey() ([]byte, error) { return os.ReadFile(os.Getenv("ADMIN_KEY")) } -func (adminHandler *AdminHandler) createToken(claims *Claims, key []byte) (string, error) { +func (a *AdminHandler) createToken(claims *Claims, key []byte) (string, error) { token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) return token.SignedString(key) } -func (adminHandler *AdminHandler) marshalResponse(guests []guest.Guest, +func (a *AdminHandler) marshalResponse(guests []guest.Guest, token string) ([]byte, error) { - loginResponse := adminHandler.createLoginResponse(guests, token) + loginResponse := a.createLoginResponse(guests, token) return json.Marshal(loginResponse) } -func (adminHandler *AdminHandler) createLoginResponse(guests []guest.Guest, +func (a *AdminHandler) createLoginResponse(guests []guest.Guest, token string) *Login { return &Login{guests, token} } diff --git a/server/cmd/main.go b/server/cmd/main.go index f9da6ce..7b2310a 100644 --- a/server/cmd/main.go +++ b/server/cmd/main.go @@ -52,7 +52,7 @@ func serveHTTP(handler http.Handler) http.Handler { } func writeMethods(responseWriter http.ResponseWriter, request *http.Request) { - allowedMethods := []string{"OPTIONS", "POST", "PUT"} + allowedMethods := []string{"OPTIONS", "POST", "PUT", "GET", "DELETE"} method := request.Header.Get("Access-Control-Request-Method") if isPreflight(request) && slices.Contains(allowedMethods, method) { responseWriter.Header().Add("Access-Control-Allow-Methods", method) @@ -60,9 +60,8 @@ func writeMethods(responseWriter http.ResponseWriter, request *http.Request) { } func writeOrigins(responseWriter http.ResponseWriter, request *http.Request) { - allowedOrigins := []string{"http://localhost:5173"} origin := request.Header.Get("Origin") - if slices.Contains(allowedOrigins, origin) { + if origin == "http://localhost:5173" { responseWriter.Header().Add("Access-Control-Allow-Origin", origin) } } diff --git a/server/errors/models.go b/server/errors/models.go new file mode 100644 index 0000000..8f7d81c --- /dev/null +++ b/server/errors/models.go @@ -0,0 +1,19 @@ +package errors + +import ( + "encoding/json" + "fmt" +) + +type AppError struct { + Status int `json:"code"` + Message json.RawMessage `json:"message"` +} + +func NewAppError(status int, msg string) *AppError { + msgJson, err := json.Marshal(msg) + if err != nil { + msgJson = []byte(fmt.Sprintf(`{"message":"%s"}`, err.Error())) + } + return &AppError{status, msgJson} +} 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] } diff --git a/server/middleware/logging.go b/server/middleware/logging.go index 860a467..d91a5c8 100644 --- a/server/middleware/logging.go +++ b/server/middleware/logging.go @@ -2,8 +2,10 @@ package middleware import ( "bytes" - "log" + "fmt" + "log/slog" "net/http" + "os" "time" ) @@ -18,33 +20,31 @@ func (w *LoggingResponseWriter) WriteHeader(code int) { w.ResponseWriter.WriteHeader(code) } -func (w *LoggingResponseWriter) Write(data []byte) (int, error) { - w.responseBody.Write(data) - return w.ResponseWriter.Write(data) +func (w *LoggingResponseWriter) Write(b []byte) (int, error) { + w.responseBody.Write(b) + return w.ResponseWriter.Write(b) } func LoggingMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { start := time.Now() - loggingWriter := &LoggingResponseWriter{ + rw := &LoggingResponseWriter{ ResponseWriter: w, statusCode: http.StatusOK, responseBody: &bytes.Buffer{}, } - next.ServeHTTP(loggingWriter, r) - - log.Printf( - "Client IP: %s | Method: %s | Path: %s | Status: %d | Duration: %v", - r.RemoteAddr, - r.Method, - r.URL.Path, - loggingWriter.statusCode, - time.Since(start), - ) - if loggingWriter.responseBody.Len() > 0 { - log.Print(loggingWriter.responseBody.String()) + jsonHandler := slog.NewJSONHandler(os.Stderr, nil) + myslog := slog.New(jsonHandler) + next.ServeHTTP(rw, r) + + if rw.statusCode >= 400 { + myslog.Error("Request", "IP", r.RemoteAddr, "Method", r.Method, "Path", r.URL.Path, "Status", + rw.statusCode, "Duration", fmt.Sprint(time.Since(start)), "Response", rw.responseBody.String()) + } else { + myslog.Info("Request", "IP", r.RemoteAddr, "Method", r.Method, "Path", r.URL.Path, "Status", + rw.statusCode, "Duration", fmt.Sprint(time.Since(start))) } }) } |