summaryrefslogtreecommitdiff
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
parentff05f3941721de3ff343421968422d216d82e952 (diff)
Improve logging and error handling
-rw-r--r--server/admin/handler.go89
-rw-r--r--server/cmd/main.go5
-rw-r--r--server/errors/models.go19
-rw-r--r--server/guest/handler.go279
-rw-r--r--server/middleware/logging.go34
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)))
}
})
}