commit 35fa88b45b6a909e1ecff42c583c12bb36df7645 Author: DeveloperDurp Date: Mon Sep 2 13:38:46 2024 -0500 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..485dee6 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.idea diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a4ec4a1 --- /dev/null +++ b/Makefile @@ -0,0 +1,4 @@ +test: + go test ./... -v +lint: + golangci-lint run ./... diff --git a/cmd/handlers/go.mod b/cmd/handlers/go.mod new file mode 100644 index 0000000..d96d761 --- /dev/null +++ b/cmd/handlers/go.mod @@ -0,0 +1,5 @@ +module gitlab.com/developerdurp/durpify/handlers + +go 1.23.0 + +require gitlab.com/developerdurp/logger v1.0.0 diff --git a/cmd/handlers/go.sum b/cmd/handlers/go.sum new file mode 100644 index 0000000..61104f0 --- /dev/null +++ b/cmd/handlers/go.sum @@ -0,0 +1,2 @@ +gitlab.com/developerdurp/logger v1.0.0 h1:wozbKR26RVoFVaUgJV2x8WsZHLWOJBqaSCSTpK7crfk= +gitlab.com/developerdurp/logger v1.0.0/go.mod h1:x6gZvBeEq8oQUXeQoLY2m78mYJjvb5KE+7Vb5AcS8oo= diff --git a/cmd/handlers/handlers.go b/cmd/handlers/handlers.go new file mode 100644 index 0000000..28489ff --- /dev/null +++ b/cmd/handlers/handlers.go @@ -0,0 +1,109 @@ +package handlers + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + + "gitlab.com/developerdurp/logger" +) + +type BasicMessage struct { + Message string `json:"message"` +} + +type StandardMessage struct { + Message interface{} + Status int `json:"status"` +} + +type StandardError struct { + Message string `json:"message"` + Status int `json:"status"` + Description []string `json:"description"` +} + +func (e StandardError) Error() string { + return fmt.Sprintf("Api error: %d", e.Status) +} + +type Response interface { + SendResponse(w http.ResponseWriter) + Test(http.Handler) +} + +func (message *StandardMessage) SendReponse(w http.ResponseWriter) { + setHeader(&w, message.Status) + + // Write the message to the response body. + err := json.NewEncoder(w).Encode(message.Message) + if err != nil { + logger.LogError("Failed to Encode") + } +} + +func (message *StandardError) SendReponse(w http.ResponseWriter) { + setHeader(&w, message.Status) + + // Write the message to the response body. + err := json.NewEncoder(w).Encode(message) + if err != nil { + logger.LogError("Failed to Encode") + } +} + +// NewFailureResponse returns a new instance of StandardError with the given message, status code and description. +func NewFailureResponse(message string, status int, description []string) StandardError { + return StandardError{ + Message: message, + Status: status, + Description: description, + } +} + +// NewMessageResponse returns a new instance of StandardMessage with the given message and status code. +func NewMessageResponse(message interface{}, status int) *StandardMessage { + return &StandardMessage{ + Message: message, + Status: status, + } +} + +// NewBasicResponse returns a new basic instance of StandardMessage with the message of OK and Status OK. +func NewBasicResponse() *StandardMessage { + return &StandardMessage{ + Message: BasicMessage{ + Message: "OK", + }, + Status: http.StatusOK, + } +} + +// SetHeader sets the HTTP response headers for a JSON response. +func setHeader(w *http.ResponseWriter, statusCode int) { + (*w).WriteHeader(statusCode) + (*w).Header().Set("Content-Type", "application/json") +} + +type APIFunc func(w http.ResponseWriter, r *http.Request) (*StandardMessage, error) + +func Make(handler APIFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + resp, err := handler(w, r) + if err != nil { + var apiErr StandardError + if errors.As(err, &apiErr) { + apiErr.SendReponse(w) + return + } + resp := NewFailureResponse( + "Internal Server Error", + http.StatusInternalServerError, + []string{err.Error()}, + ) + resp.SendReponse(w) + } + resp.SendReponse(w) + } +} diff --git a/cmd/handlers/handlers_test.go b/cmd/handlers/handlers_test.go new file mode 100644 index 0000000..a56b3c4 --- /dev/null +++ b/cmd/handlers/handlers_test.go @@ -0,0 +1,113 @@ +package handlers + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "reflect" + "testing" +) + +func TestSendResponseStandardMessage(t *testing.T) { + message := &BasicMessage{ + Message: "Hello World!", + } + resp := &StandardMessage{ + Status: http.StatusAccepted, + Message: message, + } + w := httptest.NewRecorder() + resp.SendReponse(w) + + // Check the status code is set correctly + if w.Code != 202 { + t.Errorf("Expected status code to be 202, but got %d", w.Code) + } + + // Check that the content type header is set to "application/json" + contentType := w.Header().Get("Content-Type") + if contentType != "application/json" { + t.Errorf("Expected content type to be 'application/json', but got %s", contentType) + } + + // Check that the message is written to the response body correctly + response := &BasicMessage{} + err := json.NewDecoder(w.Body).Decode(response) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(message, response) { + t.Errorf("Expected body to be %s but got %s", message, response) + } +} + +func TestSendResponseStandardError(t *testing.T) { + resp := &StandardError{ + Status: http.StatusInternalServerError, + Message: "An error has occured", + Description: []string{"An Error"}, + } + + w := httptest.NewRecorder() + resp.SendReponse(w) + + // Check the status code is set correctly + if w.Code != 500 { + t.Errorf("Expected status code to be 500, but got %d", w.Code) + } + + // Check that the content type header is set to "application/json" + contentType := w.Header().Get("Content-Type") + if contentType != "application/json" { + t.Errorf("Expected content type to be 'application/json', but got %s", contentType) + } + + // Check that the message is written to the response body correctly + response := &StandardError{} + err := json.NewDecoder(w.Body).Decode(response) + if err != nil { + t.Fatal(err) + } + + if response.Message != resp.Message { + t.Errorf("Expected Message of %s but got %s", resp.Message, response.Message) + } + + if !reflect.DeepEqual(resp, response) { + t.Errorf("Expected Message of %v but got %v", resp, response) + } +} + +// NewFailureResponse returns a new instance of StandardError with the given message, status code and description. +func TestNewFailureResponse(t *testing.T) { + message := "An error has occured" + status := http.StatusInternalServerError + description := []string{"An Error"} + resp := NewFailureResponse(message, status, description) + + if resp.Status != status { + t.Errorf("Expected Status to be %d but got %d", status, resp.Status) + } + if resp.Message != message { + t.Errorf("Expected Status to be %s but got %s", message, resp.Message) + } + if !reflect.DeepEqual(description, resp.Description) { + t.Errorf("Expected Status to be %v but got %v", description, resp.Description) + } +} + +func TestNewMessageResponse(t *testing.T) { + + message := &BasicMessage{ + Message: "Hello World!", + } + + resp := NewMessageResponse(message, http.StatusOK) + + if resp.Status != http.StatusOK { + t.Errorf("Expected Status to be %d but got %d", http.StatusOK, resp.Status) + } + if !reflect.DeepEqual(message, resp.Message) { + t.Errorf("Expected Message to be %s but got %s", message, resp.Message) + } +} diff --git a/cmd/logger/go.mod b/cmd/logger/go.mod new file mode 100644 index 0000000..b660b19 --- /dev/null +++ b/cmd/logger/go.mod @@ -0,0 +1,19 @@ +module gitlab.com/developerdurp/durpify/logger + +go 1.23.0 + +require github.com/charmbracelet/log v0.4.0 + +require ( + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/charmbracelet/lipgloss v0.10.0 // indirect + github.com/go-logfmt/logfmt v0.6.0 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/mattn/go-isatty v0.0.18 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/muesli/reflow v0.3.0 // indirect + github.com/muesli/termenv v0.15.2 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect + golang.org/x/sys v0.13.0 // indirect +) diff --git a/cmd/logger/go.sum b/cmd/logger/go.sum new file mode 100644 index 0000000..2fb9cb1 --- /dev/null +++ b/cmd/logger/go.sum @@ -0,0 +1,36 @@ +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/charmbracelet/lipgloss v0.10.0 h1:KWeXFSexGcfahHX+54URiZGkBFazf70JNMtwg/AFW3s= +github.com/charmbracelet/lipgloss v0.10.0/go.mod h1:Wig9DSfvANsxqkRsqj6x87irdy123SR4dOXlKa91ciE= +github.com/charmbracelet/log v0.4.0 h1:G9bQAcx8rWA2T3pWvx7YtPTPwgqpk7D68BX21IRW8ZM= +github.com/charmbracelet/log v0.4.0/go.mod h1:63bXt/djrizTec0l11H20t8FDSvA4CRZJ1KH22MdptM= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= +github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= +github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= +github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= +github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= +github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/cmd/logger/main.go b/cmd/logger/main.go new file mode 100644 index 0000000..5c2a7e7 --- /dev/null +++ b/cmd/logger/main.go @@ -0,0 +1,15 @@ +package logger + +import "github.com/charmbracelet/log" + +func LogInfo(message string) { + log.Printf("INFO", message) +} + +func LogWarning(message string) { + log.Printf("WARN - %v", message) +} + +func LogError(message string) { + log.Printf("ERROR - %v", message) +} diff --git a/cmd/middleware/auth.go b/cmd/middleware/auth.go new file mode 100644 index 0000000..3eadded --- /dev/null +++ b/cmd/middleware/auth.go @@ -0,0 +1,155 @@ +package middleware + +import ( + "context" + "errors" + "gitlab.com/developerdurp/stdmodels" + "net/http" + "strings" + "time" + + "github.com/MicahParks/keyfunc" + "github.com/golang-jwt/jwt/v4" + "gitlab.com/developerdurp/logger" +) + +func InitAuthMiddleware(allowedGroups []string, jwks string) *AuthConfig { + return &AuthConfig{ + allowedGroups: allowedGroups, + jwks: jwks, + } +} + +type AuthConfig struct { + allowedGroups []string + jwks string +} + +type StandardMessage struct { + Message string `json:"message" example:"message"` +} + +func (cfg *AuthConfig) AuthMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + + var groups []string + + tokenString, err := getToken(w) + if err != nil { + resp := stdmodels.NewFailureResponse( + err.Error(), + http.StatusUnauthorized, + []string{}, + ) + resp.SendReponse(w) + } + + token, err := cfg.validateToken(tokenString) + if err != nil { + resp := stdmodels.NewFailureResponse( + "Failed to Validate Token", + http.StatusUnauthorized, + []string{err.Error()}, + ) + resp.SendReponse(w) + } + + claims, ok := token.Claims.(jwt.MapClaims) + if !ok { + resp := stdmodels.NewFailureResponse( + "Invalid Authorization token claim", + http.StatusUnauthorized, + []string{}, + ) + resp.SendReponse(w) + return + } + + groupsClaim, ok := claims["groups"].([]interface{}) + if !ok { + resp := stdmodels.NewFailureResponse( + "Missing or invalid groups in the token", + http.StatusUnauthorized, + []string{}, + ) + resp.SendReponse(w) + return + } + + for _, group := range groupsClaim { + if groupName, ok := group.(string); ok { + groups = append(groups, groupName) + } + } + + isAllowed := false + for _, allowedGroup := range cfg.allowedGroups { + for _, group := range groups { + if group == allowedGroup { + isAllowed = true + break + } + } + if isAllowed { + break + } + } + + if !isAllowed { + resp := stdmodels.NewFailureResponse( + "Unauthorized to use this endpoint", + http.StatusUnauthorized, + []string{}, + ) + resp.SendReponse(w) + return + } + + next.ServeHTTP(w, r) + }) +} + +func (cfg *AuthConfig) validateToken(tokenString string) (*jwt.Token, error) { + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + options := keyfunc.Options{ + Ctx: ctx, + RefreshErrorHandler: func(err error) { + logger.LogError("There was an error with the jwt.Keyfunc" + err.Error()) + }, + RefreshInterval: time.Hour, + RefreshRateLimit: time.Minute * 5, + RefreshTimeout: time.Second * 10, + RefreshUnknownKID: true, + } + + jwks, err := keyfunc.Get(cfg.jwks, options) + defer jwks.EndBackground() + if err != nil { + return nil, errors.New("Failed to get JWKS") + } + + token, err := jwt.Parse(tokenString, jwks.Keyfunc) + if err != nil { + return nil, errors.New("Failed to Parse JWT") + } + + if !token.Valid { + return nil, errors.New("Invalid Token") + } + + return token, nil +} + +func getToken(w http.ResponseWriter) (string, error) { + tokenString := w.Header().Get("Authorization") + + if tokenString == "" { + return "", errors.New("No Token Detected") + } + + tokenString = strings.TrimPrefix(tokenString, "Bearer ") + return tokenString, nil +} diff --git a/cmd/middleware/go.mod b/cmd/middleware/go.mod new file mode 100644 index 0000000..7ebf82a --- /dev/null +++ b/cmd/middleware/go.mod @@ -0,0 +1,25 @@ +module gitlab.com/developerdurp/durpify/middleware + +go 1.23.0 + +require ( + github.com/MicahParks/keyfunc v1.9.0 + github.com/charmbracelet/log v0.4.0 + github.com/golang-jwt/jwt/v4 v4.5.0 + gitlab.com/developerdurp/logger v1.0.0 + gitlab.com/developerdurp/stdmodels v1.0.2 +) + +require ( + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/charmbracelet/lipgloss v0.10.0 // indirect + github.com/go-logfmt/logfmt v0.6.0 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/mattn/go-isatty v0.0.18 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/muesli/reflow v0.3.0 // indirect + github.com/muesli/termenv v0.15.2 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect + golang.org/x/sys v0.13.0 // indirect +) diff --git a/cmd/middleware/go.sum b/cmd/middleware/go.sum new file mode 100644 index 0000000..ffd39fb --- /dev/null +++ b/cmd/middleware/go.sum @@ -0,0 +1,45 @@ +github.com/MicahParks/keyfunc v1.9.0 h1:lhKd5xrFHLNOWrDc4Tyb/Q1AJ4LCzQ48GVJyVIID3+o= +github.com/MicahParks/keyfunc v1.9.0/go.mod h1:IdnCilugA0O/99dW+/MkvlyrsX8+L8+x95xuVNtM5jw= +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/charmbracelet/lipgloss v0.10.0 h1:KWeXFSexGcfahHX+54URiZGkBFazf70JNMtwg/AFW3s= +github.com/charmbracelet/lipgloss v0.10.0/go.mod h1:Wig9DSfvANsxqkRsqj6x87irdy123SR4dOXlKa91ciE= +github.com/charmbracelet/log v0.4.0 h1:G9bQAcx8rWA2T3pWvx7YtPTPwgqpk7D68BX21IRW8ZM= +github.com/charmbracelet/log v0.4.0/go.mod h1:63bXt/djrizTec0l11H20t8FDSvA4CRZJ1KH22MdptM= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= +github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= +github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= +github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= +github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= +github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= +github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +gitlab.com/developerdurp/logger v1.0.0 h1:wozbKR26RVoFVaUgJV2x8WsZHLWOJBqaSCSTpK7crfk= +gitlab.com/developerdurp/logger v1.0.0/go.mod h1:x6gZvBeEq8oQUXeQoLY2m78mYJjvb5KE+7Vb5AcS8oo= +gitlab.com/developerdurp/stdmodels v1.0.2 h1:yXaX6MM37Y9uJccy33trezbao/2TzlCxwVQexwbyFJU= +gitlab.com/developerdurp/stdmodels v1.0.2/go.mod h1:RRLS9Wek0YYMy3Wz0pWhfYyYcu52vr06KPFVxQ6g0OU= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/cmd/middleware/logging.go b/cmd/middleware/logging.go new file mode 100644 index 0000000..0fab703 --- /dev/null +++ b/cmd/middleware/logging.go @@ -0,0 +1,33 @@ +package middleware + +import ( + "github.com/charmbracelet/log" + + "net/http" + "time" +) + +type wrappedWriter struct { + http.ResponseWriter + statusCode int +} + +func (w *wrappedWriter) WriteHeader(statusCode int) { + w.ResponseWriter.WriteHeader(statusCode) + w.statusCode = statusCode +} + +func Logging(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + start := time.Now() + + wrapped := &wrappedWriter{ + ResponseWriter: w, + statusCode: http.StatusOK, + } + + next.ServeHTTP(wrapped, r) + + log.Info("INFO", wrapped.statusCode, r.Method, r.URL.Path, time.Since(start)) + }) +} diff --git a/cmd/middleware/main.go b/cmd/middleware/main.go new file mode 100644 index 0000000..387301b --- /dev/null +++ b/cmd/middleware/main.go @@ -0,0 +1,16 @@ +package middleware + +import "net/http" + +type Middleware func(http.Handler) http.Handler + +func CreateStack(xs ...Middleware) Middleware { + return func(next http.Handler) http.Handler { + for i := len(xs) - 1; i >= 0; i-- { + x := xs[i] + next = x(next) + } + + return next + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..643187b --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module gitlab.com/developerdurp/durpify + +go 1.23.0 diff --git a/go.work b/go.work new file mode 100644 index 0000000..e21c4d8 --- /dev/null +++ b/go.work @@ -0,0 +1,5 @@ +go 1.23.0 + +use cmd/handlers +use cmd/logger +use cmd/middleware \ No newline at end of file diff --git a/go.work.sum b/go.work.sum new file mode 100644 index 0000000..6fb5bce --- /dev/null +++ b/go.work.sum @@ -0,0 +1,44 @@ +github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= +github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/charmbracelet/lipgloss v0.10.0 h1:KWeXFSexGcfahHX+54URiZGkBFazf70JNMtwg/AFW3s= +github.com/charmbracelet/lipgloss v0.10.0/go.mod h1:Wig9DSfvANsxqkRsqj6x87irdy123SR4dOXlKa91ciE= +github.com/charmbracelet/log v0.4.0 h1:G9bQAcx8rWA2T3pWvx7YtPTPwgqpk7D68BX21IRW8ZM= +github.com/charmbracelet/log v0.4.0/go.mod h1:63bXt/djrizTec0l11H20t8FDSvA4CRZJ1KH22MdptM= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= +github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= +github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= +github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= +github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= +github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= +golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= +golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= +golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=