From 9bbd992e95a659d9702918b1dc5ec857decd06da Mon Sep 17 00:00:00 2001 From: cdricms <36056008+cdricms@users.noreply.github.com> Date: Thu, 16 Jan 2025 14:12:32 +0100 Subject: [PATCH 1/2] Middlewares --- backend/api/auth.go | 41 ++++++++++++---- backend/api/core/router.go | 30 ++++-------- backend/api/core/schemas.go | 2 +- backend/api/get_user.go | 2 + backend/api/get_users.go | 52 ++++++++++++++++++++ backend/api/new_user.go | 2 +- backend/api/update_user.go | 97 +++++++++++++++++++++++++++++++++++++ backend/main.go | 31 +++++++++--- backend/utils/contains.go | 11 +++++ backend/utils/map.go | 9 ++++ 10 files changed, 238 insertions(+), 39 deletions(-) create mode 100644 backend/api/get_users.go create mode 100644 backend/api/update_user.go create mode 100644 backend/utils/contains.go create mode 100644 backend/utils/map.go diff --git a/backend/api/auth.go b/backend/api/auth.go index c1b03e5..663950c 100644 --- a/backend/api/auth.go +++ b/backend/api/auth.go @@ -10,12 +10,13 @@ import ( "time" core "fr.latosa-escrima/api/core" + "fr.latosa-escrima/utils" "github.com/golang-jwt/jwt/v5" ) var MySigningKey = []byte("COUCOU") -type LoginInformation struct { +type LoginArgs struct { Email string `json:"email"` Password string `json:"password"` } @@ -26,13 +27,13 @@ type Claims struct { } func HandleLogin(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - core.JSONError{ - Status: core.Error, - Message: "Method is not allowed", - }.Respond(w, http.StatusMethodNotAllowed) - return - } + // if r.Method != http.MethodPost { + // core.JSONError{ + // Status: core.Error, + // Message: "Method is not allowed", + // }.Respond(w, http.StatusMethodNotAllowed) + // return + // } if r.Body == nil { core.JSONError{ @@ -50,7 +51,7 @@ func HandleLogin(w http.ResponseWriter, r *http.Request) { }.Respond(w, http.StatusNoContent) return } - var login LoginInformation + var login LoginArgs err = json.Unmarshal(body, &login) if err != nil { core.JSONError{ @@ -151,3 +152,25 @@ func AuthJWT(next http.Handler) http.Handler { next.ServeHTTP(w, r) }) } + +// @param methods string -> HttpMethods separated by commas. +func Methods(methods string) core.Middleware { + _methods := strings.Split(strings.ToUpper(methods), ",") + middleware := func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if len(methods) > 0 { + if !utils.Contains(_methods, r.Method) { + core.JSONError{ + Status: core.Error, + Message: "Method is not allowed.", + }.Respond(w, http.StatusMethodNotAllowed) + return + } + } + + next.ServeHTTP(w, r) + }) + } + + return middleware +} diff --git a/backend/api/core/router.go b/backend/api/core/router.go index 95a445d..639a652 100644 --- a/backend/api/core/router.go +++ b/backend/api/core/router.go @@ -1,8 +1,8 @@ package core import ( - "net/http" "encoding/json" + "net/http" ) type JSONStatus string @@ -55,33 +55,23 @@ func (r JSONSuccess) Respond(w http.ResponseWriter, code int) { defaultResponse(&r, w, code) } -func HandleMiddlewareRoute(pattern string, - handler func(w http.ResponseWriter, r *http.Request), - middleware func(http.Handler) http.Handler, - mux *http.ServeMux, -) { - // mux.HandleFunc(pattern, handler) - mux.Handle(pattern, middleware(http.HandlerFunc(handler))) -} - -type HandlerFunc func(w http.ResponseWriter, r *http.Request) +type Middleware func(http.Handler) http.Handler type Handler struct { - Handler HandlerFunc - Middleware func(http.Handler) http.Handler + Handler http.HandlerFunc + Middlewares []Middleware } func HandleRoutes(mux *http.ServeMux, routes map[string]Handler) { for pattern, handler := range routes { - if handler.Middleware == nil { + if handler.Middlewares == nil { mux.HandleFunc(pattern, handler.Handler) } else { - HandleMiddlewareRoute( - pattern, - handler.Handler, - handler.Middleware, - mux, - ) + h := http.HandlerFunc(handler.Handler) + for _, middleware := range handler.Middlewares { + h = http.HandlerFunc(middleware(h).ServeHTTP) + } + mux.Handle(pattern, h) } } } diff --git a/backend/api/core/schemas.go b/backend/api/core/schemas.go index 92062c7..e53b94c 100644 --- a/backend/api/core/schemas.go +++ b/backend/api/core/schemas.go @@ -40,7 +40,7 @@ type User struct { FirstName string `bun:"firstname,notnull" json:"firstname"` LastName string `bun:"lastname,notnull" json:"lastname"` Email string `bun:"email,unique,notnull" json:"email"` - Password string `bun:"password,notnull" json:"password"` + Password string `bun:"password,notnull" json:"password,omitempty"` Phone string `bun:"phone,notnull" json:"phone"` Role Role `bun:"role,notnull,default:'user'" json:"role"` CreatedAt time.Time `bun:"created_at,default:current_timestamp" json:"createdAt"` diff --git a/backend/api/get_user.go b/backend/api/get_user.go index f8a55f5..1cf5b75 100644 --- a/backend/api/get_user.go +++ b/backend/api/get_user.go @@ -40,6 +40,8 @@ func HandleGetUser(w http.ResponseWriter, r *http.Request) { return } + user.Password = "" + // TODO : Remove password core.JSONSuccess{ Status: core.Success, diff --git a/backend/api/get_users.go b/backend/api/get_users.go new file mode 100644 index 0000000..12a8179 --- /dev/null +++ b/backend/api/get_users.go @@ -0,0 +1,52 @@ +package api + +import ( + "context" + "net/http" + + "fr.latosa-escrima/api/core" + "fr.latosa-escrima/utils" +) + +func HandleGetUsers(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + core.JSONError{ + Status: core.Error, + Message: "Method is not allowed.", + }.Respond(w, http.StatusMethodNotAllowed) + return + } + + var users []core.User + count, err := core.DB.NewSelect(). + Model(&users). + ScanAndCount(context.Background()) + + users = utils.Map(users, func(user core.User) core.User { + user.Password = "" + return user + }) + + if count == 0 { + core.JSONError{ + Status: core.Error, + Message: "Not users.", + }.Respond(w, http.StatusNotFound) + return + } + + if err != nil { + core.JSONError{ + Status: core.Error, + Message: err.Error(), + }.Respond(w, http.StatusInternalServerError) + return + } + + // TODO : Remove password + core.JSONSuccess{ + Status: core.Success, + Message: "Users found.", + Data: users, + }.Respond(w, http.StatusOK) +} diff --git a/backend/api/new_user.go b/backend/api/new_user.go index 7d42873..c450172 100644 --- a/backend/api/new_user.go +++ b/backend/api/new_user.go @@ -41,7 +41,7 @@ func HandleCreateUser(w http.ResponseWriter, r *http.Request) { log.Println(user) - res, err := core.DB.NewInsert().Model(user).Exec(context.Background()) + res, err := core.DB.NewInsert().Model(&user).Exec(context.Background()) if res == nil { core.JSONError{ Status: core.Error, diff --git a/backend/api/update_user.go b/backend/api/update_user.go new file mode 100644 index 0000000..6dd5256 --- /dev/null +++ b/backend/api/update_user.go @@ -0,0 +1,97 @@ +package api + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "reflect" + "strings" + "time" + + "fr.latosa-escrima/api/core" +) + +type UpdateUserArgs struct { + FirstName *string `json:"firstname,omitempty"` + LastName *string `json:"lastname,omitempty"` + Email *string `json:"email,omitempty"` + Password *string `json:"password,omitempty"` + Phone *string `json:"phone,omitempty"` + Role *core.Role `json:"role,omitempty"` +} + +func HandleUpdateUser(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPatch { + core.JSONError{ + Status: core.Error, + Message: "Method is not allowed.", + }.Respond(w, http.StatusMethodNotAllowed) + return + } + + var updateArgs UpdateUserArgs + body, err := io.ReadAll(r.Body) + if err != nil { + core.JSONError{ + Status: core.Error, + Message: err.Error(), + }.Respond(w, http.StatusInternalServerError) + return + } + err = json.Unmarshal(body, &updateArgs) + if err != nil { + core.JSONError{ + Status: core.Error, + Message: err.Error(), + }.Respond(w, http.StatusInternalServerError) + return + } + + var user core.User + updateQuery := core.DB.NewUpdate().Model(&user) + + val := reflect.ValueOf(updateArgs) + typ := reflect.TypeOf(updateArgs) + + for i := 0; i < val.NumField(); i++ { + field := val.Field(i) + + tag := typ.Field(i).Tag.Get("bun") + if tag == "" { + tag = typ.Field(i).Tag.Get("json") + } + + // Only add fields that are non-nil and non-zero + if field.IsValid() && !field.IsNil() && !field.IsZero() { + updateQuery.Set(fmt.Sprintf("%s = ?", strings.Split(tag, ",")[0]), field.Interface()) + } + } + + // Always update the `updated_at` field + updateQuery.Set("updated_at = ?", time.Now()) + + uuid := r.PathValue("user_uuid") + _, err = updateQuery. + Where("user_id = ?", uuid). + Returning("*"). + Exec(context.Background()) + + if err != nil { + core.JSONError{ + Status: core.Error, + Message: err.Error(), + }.Respond(w, http.StatusInternalServerError) + return + } + + user.Password = "" + + core.JSONSuccess{ + Status: core.Success, + Message: "User updated.", + Data: user, + }.Respond(w, http.StatusOK) +} + diff --git a/backend/main.go b/backend/main.go index 72869eb..e9ff310 100644 --- a/backend/main.go +++ b/backend/main.go @@ -43,18 +43,33 @@ func main() { mux := http.NewServeMux() core.HandleRoutes(mux, map[string]core.Handler{ - "/": {Handler: handler, Middleware: nil}, - "/users/login": {Handler: api.HandleLogin, Middleware: nil}, - "/users/new": {Handler: api.HandleCreateUser, Middleware: api.AuthJWT}, - "/users/{user_uuid}": {Handler: api.HandleGetUser, Middleware: api.AuthJWT}, - "/users/{user_uuid}/delete": {Handler: api.HandleDeleteUser, Middleware: api.AuthJWT}, - // "/users/{user_uuid}/update": {Handler: api.HandleUpdateUser, Middleware: api.AuthJWT}, + "/": { + Handler: handler, + Middlewares: []core.Middleware{api.Methods("post")}}, + "/users/login": { + Handler: api.HandleLogin, + Middlewares: []core.Middleware{api.Methods("POST")}}, + "/users": { + Handler: api.HandleGetUsers, + Middlewares: []core.Middleware{api.Methods("GET"), api.AuthJWT}}, + "/users/new": { + Handler: api.HandleCreateUser, + Middlewares: []core.Middleware{api.Methods("POST"), api.AuthJWT}}, + "/users/{user_uuid}": { + Handler: api.HandleGetUser, + Middlewares: []core.Middleware{api.Methods("GET"), api.AuthJWT}}, + "/users/{user_uuid}/delete": { + Handler: api.HandleDeleteUser, + Middlewares: []core.Middleware{api.Methods("DELETE"), api.AuthJWT}}, + "/users/{user_uuid}/update": { + Handler: api.HandleUpdateUser, + Middlewares: []core.Middleware{api.Methods("PATCH"), api.AuthJWT}}, // "/users/{user_uuid}/events": {Handler: nil, Middleware: nil}, // "/users/{user_uuid}/events/{event_uuid}": {Handler: nil, Middleware: nil}, // "/users/{user_uuid}/events/{event_uuid}/delete": {Handler: nil, Middleware: nil}, // "/users/{user_uuid}/events/{event_uuid}/update": {Handler: nil, Middleware: nil}, - "/blogs/new": {Handler: api.HandleCreateBlog, Middleware: nil}, - "/blogs/{uuid}": {Handler: api.HandleGetBlog, Middleware: nil}, + "/blogs/new": {Handler: api.HandleCreateBlog, Middlewares: nil}, + "/blogs/{uuid}": {Handler: api.HandleGetBlog, Middlewares: nil}, }) fmt.Printf("Serving on port %s\n", port) diff --git a/backend/utils/contains.go b/backend/utils/contains.go new file mode 100644 index 0000000..f527958 --- /dev/null +++ b/backend/utils/contains.go @@ -0,0 +1,11 @@ +package utils + +func Contains[T comparable](arr []T, el T) bool { + for _, a := range arr { + if a == el { + return true + } + } + + return false +} diff --git a/backend/utils/map.go b/backend/utils/map.go new file mode 100644 index 0000000..3dc6d19 --- /dev/null +++ b/backend/utils/map.go @@ -0,0 +1,9 @@ +package utils + +func Map[T any, U any](input []T, fn func(T) U) []U { + var result []U + for _, v := range input { + result = append(result, fn(v)) + } + return result +} From 2fc8a3f347799f7630cacdcefabe8a4a27da0d26 Mon Sep 17 00:00:00 2001 From: cdricms <36056008+cdricms@users.noreply.github.com> Date: Thu, 16 Jan 2025 14:42:20 +0100 Subject: [PATCH 2/2] Small improvements --- backend/.gitignore | 1 + backend/api/auth.go | 8 -------- backend/api/delete_user.go | 8 -------- backend/api/get_user.go | 8 -------- backend/api/get_users.go | 8 -------- backend/api/new_user.go | 9 --------- backend/api/update_user.go | 9 --------- 7 files changed, 1 insertion(+), 50 deletions(-) create mode 100644 backend/.gitignore diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 0000000..a9a5aec --- /dev/null +++ b/backend/.gitignore @@ -0,0 +1 @@ +tmp diff --git a/backend/api/auth.go b/backend/api/auth.go index 663950c..74d55ce 100644 --- a/backend/api/auth.go +++ b/backend/api/auth.go @@ -27,14 +27,6 @@ type Claims struct { } func HandleLogin(w http.ResponseWriter, r *http.Request) { - // if r.Method != http.MethodPost { - // core.JSONError{ - // Status: core.Error, - // Message: "Method is not allowed", - // }.Respond(w, http.StatusMethodNotAllowed) - // return - // } - if r.Body == nil { core.JSONError{ Status: core.Error, diff --git a/backend/api/delete_user.go b/backend/api/delete_user.go index 996c89d..23f1ffd 100644 --- a/backend/api/delete_user.go +++ b/backend/api/delete_user.go @@ -8,14 +8,6 @@ import ( ) func HandleDeleteUser(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodDelete { - core.JSONError{ - Status: core.Error, - Message: "Method is not allowed.", - }.Respond(w, http.StatusMethodNotAllowed) - return - } - uuid := r.PathValue("user_uuid") _, err := core.DB.NewDelete(). Model((*core.User)(nil)). diff --git a/backend/api/get_user.go b/backend/api/get_user.go index 1cf5b75..71b8eb9 100644 --- a/backend/api/get_user.go +++ b/backend/api/get_user.go @@ -8,14 +8,6 @@ import ( ) func HandleGetUser(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - core.JSONError{ - Status: core.Error, - Message: "Method is not allowed.", - }.Respond(w, http.StatusMethodNotAllowed) - return - } - uuid := r.PathValue("user_uuid") var user core.User count, err := core.DB.NewSelect(). diff --git a/backend/api/get_users.go b/backend/api/get_users.go index 12a8179..707807e 100644 --- a/backend/api/get_users.go +++ b/backend/api/get_users.go @@ -9,14 +9,6 @@ import ( ) func HandleGetUsers(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - core.JSONError{ - Status: core.Error, - Message: "Method is not allowed.", - }.Respond(w, http.StatusMethodNotAllowed) - return - } - var users []core.User count, err := core.DB.NewSelect(). Model(&users). diff --git a/backend/api/new_user.go b/backend/api/new_user.go index c450172..0dbd6d2 100644 --- a/backend/api/new_user.go +++ b/backend/api/new_user.go @@ -11,15 +11,6 @@ import ( ) func HandleCreateUser(w http.ResponseWriter, r *http.Request) { - - if r.Method != http.MethodPost { - core.JSONError{ - Status: core.Error, - Message: "This method is not allowed", - }.Respond(w, http.StatusMethodNotAllowed) - return - } - body, err := io.ReadAll(r.Body) if err != nil { core.JSONError{ diff --git a/backend/api/update_user.go b/backend/api/update_user.go index 6dd5256..3c7c915 100644 --- a/backend/api/update_user.go +++ b/backend/api/update_user.go @@ -23,14 +23,6 @@ type UpdateUserArgs struct { } func HandleUpdateUser(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPatch { - core.JSONError{ - Status: core.Error, - Message: "Method is not allowed.", - }.Respond(w, http.StatusMethodNotAllowed) - return - } - var updateArgs UpdateUserArgs body, err := io.ReadAll(r.Body) if err != nil { @@ -94,4 +86,3 @@ func HandleUpdateUser(w http.ResponseWriter, r *http.Request) { Data: user, }.Respond(w, http.StatusOK) } -