diff --git a/.gitignore b/.gitignore index e5d391b..c795046 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,7 @@ go.work # Testdata directory -testdata/ \ No newline at end of file +testdata/ + +# temporary files +tmp/ \ No newline at end of file diff --git a/internal/healthcheck/handlers/handler.go b/internal/healthcheck/handlers/handler.go index db3ead7..66a295b 100644 --- a/internal/healthcheck/handlers/handler.go +++ b/internal/healthcheck/handlers/handler.go @@ -16,7 +16,7 @@ type Handler struct { } func (h *Handler) Healthcheck(c *gin.Context) { - data := httphelpers.Envelope{ + data := gin.H{ "status": "available", "system_info": map[string]string{ "environment": h.Env, diff --git a/internal/helloworld/handlers/ginhelloworld.go b/internal/helloworld/handlers/ginhelloworld.go deleted file mode 100644 index 9b8f9f5..0000000 --- a/internal/helloworld/handlers/ginhelloworld.go +++ /dev/null @@ -1,92 +0,0 @@ -package handlers - -import ( - "context" - "errors" - "strconv" - - "github.com/gin-gonic/gin" - - "greenlight/internal/helloworld/logicerrors" - "greenlight/internal/models" - "greenlight/pkg/httphelpers" -) - -type GinHelloWorldHandler struct { - logic HelloWorldLogic -} - -type HelloWorldLogic interface { - Greet(ctx context.Context, user *models.User, saveUser bool) (string, error) - GetUserByName(ctx context.Context, name string) (models.User, error) - ListUsers(ctx context.Context) ([]models.User, error) -} - -func NewHelloWorldHandler(logic HelloWorldLogic) *GinHelloWorldHandler { - return &GinHelloWorldHandler{ - logic: logic, - } -} - -// post /helloworld -func (h *GinHelloWorldHandler) Greet() gin.HandlerFunc { - // you might want to do some processing before returning the handlerFunc, - // for example if you use a regex, you might want to compile it beforehand - return func(c *gin.Context) { - var userInput struct { - Name string `json:"name"` - } - if err := httphelpers.JSONDecode(c, &userInput); err != nil { - httphelpers.StatusBadRequestResponse(c, err.Error()) - return - } - - user := models.User{Name: userInput.Name} - saveUser, err := strconv.ParseBool(c.DefaultQuery("save", "false")) - if err != nil { - httphelpers.StatusBadRequestResponse(c, "user not found") - return - } - - helloStr, err := h.logic.Greet(c, &user, saveUser) - if err != nil { - httphelpers.StatusInternalServerErrorResponse(c, err) - return - } - - httphelpers.StatusOKJSONPayloadResponse(c, gin.H{"message": helloStr}) - } -} - -// get /helloworld/:name -func (h *GinHelloWorldHandler) GetUserByName() gin.HandlerFunc { - return func(c *gin.Context) { - name := c.Param("name") - - user, err := h.logic.GetUserByName(c, name) - if err != nil { - switch { - case errors.Is(err, logicerrors.ErrUserDoesNotExist): - httphelpers.StatusBadRequestResponse(c, "user not found") - default: - httphelpers.StatusInternalServerErrorResponse(c, err) - } - return - } - - httphelpers.StatusOKJSONPayloadResponse(c, user) - } -} - -// get /helloworld -func (h *GinHelloWorldHandler) ListUsers() gin.HandlerFunc { - return func(c *gin.Context) { - users, err := h.logic.ListUsers(c) - if err != nil { - httphelpers.StatusInternalServerErrorResponse(c, err) - return - } - - httphelpers.StatusOKJSONPayloadResponse(c, users) - } -} diff --git a/internal/helloworld/logic/helloworld.go b/internal/helloworld/logic/helloworld.go deleted file mode 100644 index 608a861..0000000 --- a/internal/helloworld/logic/helloworld.go +++ /dev/null @@ -1,56 +0,0 @@ -package logic - -import ( - "context" - "errors" - "fmt" - - "greenlight/internal/helloworld/logicerrors" - "greenlight/internal/helloworld/repositoryerrors" - "greenlight/internal/models" -) - -type helloWorldLogic struct { - repo HelloWorldRepository -} - -type HelloWorldRepository interface { - SaveGreetedUser(context.Context, *models.User) error - GetUser(ctx context.Context, name string) (models.User, error) - GetAllGreetedUsers(context.Context) ([]models.User, error) -} - -func NewHelloWorldLogic(repo HelloWorldRepository) *helloWorldLogic { - return &helloWorldLogic{ - repo: repo, - } -} - -func (l helloWorldLogic) Greet(ctx context.Context, user *models.User, saveUser bool) (string, error) { - if saveUser { - err := l.repo.SaveGreetedUser(ctx, user) - if err != nil { - return "", err - } - } - - return fmt.Sprintf("Hello, %s!", user.Name), nil -} - -func (l helloWorldLogic) GetUserByName(ctx context.Context, name string) (models.User, error) { - user, err := l.repo.GetUser(ctx, name) - if err != nil { - switch { - case errors.Is(err, repositoryerrors.ErrRecordNotFound): - return models.User{}, logicerrors.ErrUserDoesNotExist - default: - return models.User{}, err - } - } - - return user, nil -} - -func (l helloWorldLogic) ListUsers(ctx context.Context) ([]models.User, error) { - return l.repo.GetAllGreetedUsers(ctx) -} diff --git a/internal/helloworld/logicerrors/logicerrors.go b/internal/helloworld/logicerrors/logicerrors.go deleted file mode 100644 index 3867269..0000000 --- a/internal/helloworld/logicerrors/logicerrors.go +++ /dev/null @@ -1,7 +0,0 @@ -package logicerrors - -import "errors" - -var ( - ErrUserDoesNotExist = errors.New("user does not exists") -) diff --git a/internal/helloworld/repo/sqlxrepo.go b/internal/helloworld/repo/sqlxrepo.go deleted file mode 100644 index e811b18..0000000 --- a/internal/helloworld/repo/sqlxrepo.go +++ /dev/null @@ -1,79 +0,0 @@ -package repo - -import ( - "context" - "database/sql" - "errors" - "greenlight/internal/helloworld/repositoryerrors" - "greenlight/internal/models" - - "github.com/jmoiron/sqlx" -) - -type sqlxRepo struct { - db *sqlx.DB -} - -func NewSqlxRepo(db *sqlx.DB) *sqlxRepo { - return &sqlxRepo{ - db: db, - } -} - -func (r *sqlxRepo) SaveGreetedUser(ctx context.Context, user *models.User) error { - - query := `INSERT INTO users (name) - VALUES($1) - ON CONFLICT(name) DO NOTHING` - - row := r.db.QueryRowxContext(ctx, query, user.Name) - if err := row.Err(); err != nil { - return err - } - - err := row.StructScan(user) - if err != nil { - return err - } - - return nil -} - -func (r *sqlxRepo) GetUser(ctx context.Context, name string) (models.User, error) { - var user models.User - - query := `SELECT name, id, registered_at FROM users - WHERE name = $1` - - // db.Get loads the first element into dest - err := r.db.GetContext(ctx, &user, query, name) - if err != nil { - switch { - case errors.Is(err, sql.ErrNoRows): - return models.User{}, repositoryerrors.ErrRecordNotFound - default: - return models.User{}, err - } - } - - return user, nil -} - -func (r *sqlxRepo) GetAllGreetedUsers(ctx context.Context) ([]models.User, error) { - var users []models.User - query := `SELECT name, id, registered_at - FROM users` - - // db.Select loads a slice of elements into dest - err := r.db.SelectContext(ctx, &users, query) - if err != nil { - switch { - case errors.Is(err, sql.ErrNoRows): - return []models.User{}, nil - default: - return []models.User{}, err - } - } - - return users, nil -} diff --git a/internal/helloworld/repositoryerrors/repositoryerrors.go b/internal/helloworld/repositoryerrors/repositoryerrors.go deleted file mode 100644 index 25280ef..0000000 --- a/internal/helloworld/repositoryerrors/repositoryerrors.go +++ /dev/null @@ -1,5 +0,0 @@ -package repositoryerrors - -import "errors" - -var ErrRecordNotFound = errors.New("record not found") diff --git a/internal/models/user.go b/internal/models/user.go index f72964f..7837bc3 100644 --- a/internal/models/user.go +++ b/internal/models/user.go @@ -2,8 +2,6 @@ package models import "time" -// remove "db" tag if not using sqlx -// remove "gorm" tag if not using gorm type User struct { ID int64 `json:"id" db:"id" gorm:"primaryKey;autoIncrement"` Name string `json:"name" db:"name" gorm:"uniqueIndex"` diff --git a/internal/movies/handlers/handler.go b/internal/movies/handlers/handler.go index 8d1c7cf..967ed66 100644 --- a/internal/movies/handlers/handler.go +++ b/internal/movies/handlers/handler.go @@ -33,14 +33,15 @@ type Handler struct { Repo Repo } -func (h *Handler) CreateMovie(c *gin.Context) { - var input struct { - Title string `json:"title"` - Year int32 `json:"year"` - Runtime models.Runtime `json:"runtime"` - Genres []string `json:"genres"` - } +type createMovieInput struct { + Title string `json:"title"` + Year int32 `json:"year"` + Runtime models.Runtime `json:"runtime"` + Genres []string `json:"genres"` +} +func (h *Handler) CreateMovie(c *gin.Context) { + var input createMovieInput err := httphelpers.JSONDecode(c, &input) if err != nil { h.Logger.PrintError(err, nil) @@ -73,7 +74,7 @@ func (h *Handler) CreateMovie(c *gin.Context) { headers := make(http.Header) headers.Set("Location", fmt.Sprintf("/v1/movies/%d", movie.ID)) - err = httphelpers.WriteJson(c, http.StatusCreated, httphelpers.Envelope{"movie": movie}, headers) + err = httphelpers.WriteJson(c, http.StatusCreated, gin.H{"movie": movie}, headers) if err != nil { httphelpers.StatusInternalServerErrorResponse(c, err) } @@ -89,7 +90,7 @@ func (h *Handler) ShowMovie(c *gin.Context) { movie, err := h.Repo.Get(id) if err != nil { switch { - case errors.Is(err, repositoryerrors.ErrRecordNotFound): // err == repositoryerrors.ErrRecordNotFound: + case errors.Is(err, repositoryerrors.ErrRecordNotFound): httphelpers.StatusNotFoundResponse(c) default: httphelpers.StatusInternalServerErrorResponse(c, err) @@ -97,12 +98,19 @@ func (h *Handler) ShowMovie(c *gin.Context) { return } - err = httphelpers.WriteJson(c, http.StatusOK, httphelpers.Envelope{"movie": movie}, nil) + err = httphelpers.WriteJson(c, http.StatusOK, gin.H{"movie": movie}, nil) if err != nil { httphelpers.StatusInternalServerErrorResponse(c, err) } } +type updateMovieInput struct { + Title *string `json:"title"` + Year *int32 `json:"year"` + Runtime *models.Runtime `json:"runtime"` + Genres []string `json:"genres"` +} + func (h *Handler) UpdateMovie(c *gin.Context) { id, err := httphelpers.ReadIDParam(c) if err != nil { @@ -121,13 +129,7 @@ func (h *Handler) UpdateMovie(c *gin.Context) { return } - var input struct { - Title *string `json:"title"` - Year *int32 `json:"year"` - Runtime *models.Runtime `json:"runtime"` - Genres []string `json:"genres"` - } - + var input updateMovieInput err = httphelpers.JSONDecode(c, &input) if err != nil { httphelpers.StatusBadRequestResponse(c, err.Error()) @@ -165,7 +167,7 @@ func (h *Handler) UpdateMovie(c *gin.Context) { return } - err = httphelpers.WriteJson(c, http.StatusOK, httphelpers.Envelope{"movie": movie}, nil) + err = httphelpers.WriteJson(c, http.StatusOK, gin.H{"movie": movie}, nil) if err != nil { httphelpers.StatusInternalServerErrorResponse(c, err) } @@ -189,30 +191,33 @@ func (h *Handler) DeleteMovie(c *gin.Context) { return } - err = httphelpers.WriteJson(c, http.StatusOK, httphelpers.Envelope{"message": "movie successfully deleted"}, nil) + err = httphelpers.WriteJson(c, http.StatusOK, gin.H{"message": "movie successfully deleted"}, nil) if err != nil { httphelpers.StatusInternalServerErrorResponse(c, err) } } -func (h *Handler) ListMovies(c *gin.Context) { - var input struct { - Title string - Genres []string - httphelpers.Filters - } +type listMoviesInput struct { + Title string + Genres []string + httphelpers.Filters +} +func (h *Handler) ListMovies(c *gin.Context) { v := validator.New() qs := c.Request.URL.Query() - input.Title = httphelpers.ReadString(qs, "title", "") - input.Genres = httphelpers.ReadCSV(qs, "genres", []string{}) - input.Filters.Page = httphelpers.ReadInt(qs, "page", 1, v) - input.Filters.PageSize = httphelpers.ReadInt(qs, "page_size", 10, v) - input.Filters.Sort = httphelpers.ReadString(qs, "sort", "id") - input.Filters.SortSafeList = []string{"id", "title", "year", "runtime", "-id", "-title", "-year", "-runtime"} - + input := listMoviesInput{ + Title: httphelpers.ReadString(qs, "title", ""), + Genres: httphelpers.ReadCSV(qs, "genres", []string{}), + Filters: httphelpers.Filters{ + Page: httphelpers.ReadInt(qs, "page", 1, v), + PageSize: httphelpers.ReadInt(qs, "page_size", 10, v), + Sort: httphelpers.ReadString(qs, "sort", "id"), + SortSafeList: []string{"id", "title", "year", "runtime", "-id", "-title", "-year", "-runtime"}, + }, + } if httphelpers.ValidateFilters(v, input.Filters); !v.Valid() { httphelpers.StatusUnprocesableEntities(c, v.Errors) return @@ -224,7 +229,7 @@ func (h *Handler) ListMovies(c *gin.Context) { return } - err = httphelpers.WriteJson(c, http.StatusOK, httphelpers.Envelope{"movies": movies, "metadata": metadata}, nil) + err = httphelpers.WriteJson(c, http.StatusOK, gin.H{"movies": movies, "metadata": metadata}, nil) if err != nil { httphelpers.StatusInternalServerErrorResponse(c, err) } diff --git a/internal/movies/repo/sqlxrepo.go b/internal/movies/repo/sqlxrepo.go index 893b2be..61113c1 100644 --- a/internal/movies/repo/sqlxrepo.go +++ b/internal/movies/repo/sqlxrepo.go @@ -64,17 +64,19 @@ func (r *sqlxRepo) Get(id int64) (*models.Movie, error) { // Esta seria la mejor forma de hacerlo con sqlx, el problema es que el Scan interno // que hace GetContext no funciona con arrays, entonces para que funcione bien - // deberia crearme un DABO de tipo Movie y luego hacer un scan a ese tipo de dato + // deberia crearme un DAO (Data Access Object) de tipo Movie y luego hacer un scan a ese tipo de dato // err := r.db.GetContext(ctx, &movie, query, id) // Esta es la forma que lo hace el libro. No esta tan buena, pero - // prefiero hacerlo asi ahora porque me da pereza hacer el DABO de Movie + // prefiero hacerlo asi ahora porque me da pereza hacer el DAO de Movie + // 😡 row := r.db.QueryRowxContext(ctx, query, id) if err := row.Err(); err != nil { return nil, err } + // esto lo podes extraer a una funcion, lo volves a usar en GetAll err := row.Scan( &movie.ID, &movie.CreatedAt, @@ -154,6 +156,8 @@ func (r *sqlxRepo) Delete(id int64) error { } func (r *sqlxRepo) GetAll(title string, genres []string, filters httphelpers.Filters) ([]*models.Movie, httphelpers.Metadata, error) { + // acordate que aca solo podes hacer esto si estas 100% seguro que el dato esta sanitizado + // JAMAS hagas esto con datos que no esten sanitizados porque te pueden hacer una inyeccion de SQL query := fmt.Sprintf(` SELECT count(*) OVER(), id, created_at, title, year, runtime, genres, version FROM movies diff --git a/internal/movies/router/router.go b/internal/movies/router/router.go index 7695e66..44b6f2f 100644 --- a/internal/movies/router/router.go +++ b/internal/movies/router/router.go @@ -22,10 +22,20 @@ type PermissionsRepo interface { func InitRouter(engine *gin.RouterGroup, handler Handler, permissionsRepo PermissionsRepo) { movies := engine.Group("/movies") { - movies.POST("", middlewares.RequirePermission(permissionsRepo, "movies:write"), handler.CreateMovie) - movies.GET("", middlewares.RequirePermission(permissionsRepo, "movies:read"), handler.ListMovies) - movies.GET("/:id", middlewares.RequirePermission(permissionsRepo, "movies:read"), handler.ShowMovie) - movies.PATCH("/:id", middlewares.RequirePermission(permissionsRepo, "movies:write"), handler.UpdateMovie) - movies.DELETE("/:id", middlewares.RequirePermission(permissionsRepo, "movies:write"), handler.DeleteMovie) + // minimizar los lugares donde definimos strings magicos + // despues tenemos un typo y no anda nada + movies.POST("", requireWritePermission(permissionsRepo), handler.CreateMovie) + movies.GET("", requireReadPermission(permissionsRepo), handler.ListMovies) + movies.GET("/:id", requireReadPermission(permissionsRepo), handler.ShowMovie) + movies.PATCH("/:id", requireWritePermission(permissionsRepo), handler.UpdateMovie) + movies.DELETE("/:id", requireWritePermission(permissionsRepo), handler.DeleteMovie) } } + +func requireWritePermission(permissionsRepo PermissionsRepo) gin.HandlerFunc { + return middlewares.RequirePermission(permissionsRepo, "movies:write") +} + +func requireReadPermission(permissionsRepo PermissionsRepo) gin.HandlerFunc { + return middlewares.RequirePermission(permissionsRepo, "movies:read") +} diff --git a/internal/users/handlers/token_handler.go b/internal/users/handlers/token_handler.go index 925aab4..3f7e693 100644 --- a/internal/users/handlers/token_handler.go +++ b/internal/users/handlers/token_handler.go @@ -68,7 +68,7 @@ func (h TokenHandler) CreateAuthenticationToken(c *gin.Context) { return } - httphelpers.WriteJson(c, http.StatusOK, httphelpers.Envelope{"token": token}, nil) + httphelpers.WriteJson(c, http.StatusOK, gin.H{"token": token}, nil) if err != nil { httphelpers.StatusInternalServerErrorResponse(c, err) return diff --git a/internal/users/handlers/user_handler.go b/internal/users/handlers/user_handler.go index 4aab5e0..718fc3e 100644 --- a/internal/users/handlers/user_handler.go +++ b/internal/users/handlers/user_handler.go @@ -79,6 +79,8 @@ func (h *UserHandler) Register(c *gin.Context) { return } + // y la capa de servicio/logica?? + // esto refactorealo a otro paquete err = h.UserRepo.Insert(c, user) if err != nil { switch { @@ -116,8 +118,8 @@ func (h *UserHandler) Register(c *gin.Context) { } }) }() - - err = httphelpers.WriteJson(c, http.StatusCreated, httphelpers.Envelope{"user": user}, nil) + // hasta aca va todo en capa de servicio, devolviendo el error correspondiente a cada caso + err = httphelpers.WriteJson(c, http.StatusCreated, gin.H{"user": user}, nil) if err != nil { httphelpers.StatusInternalServerErrorResponse(c, err) } @@ -141,6 +143,7 @@ func (h *UserHandler) ActivateUser(c *gin.Context) { return } + // capa de servicio user, err := h.UserRepo.GetForToken(models.ScopeActivation, input.TokenPlaintext) if err != nil { switch { @@ -171,8 +174,9 @@ func (h *UserHandler) ActivateUser(c *gin.Context) { httphelpers.StatusInternalServerErrorResponse(c, err) return } + // hasta aca en capa de servicio - err = httphelpers.WriteJson(c, http.StatusOK, httphelpers.Envelope{"user": user}, nil) + err = httphelpers.WriteJson(c, http.StatusOK, gin.H{"user": user}, nil) if err != nil { httphelpers.StatusInternalServerErrorResponse(c, err) } diff --git a/internal/users/models/tokens.go b/internal/users/models/tokens.go index e25c14c..f3eeb2f 100644 --- a/internal/users/models/tokens.go +++ b/internal/users/models/tokens.go @@ -22,6 +22,7 @@ type Token struct { Scope string `json:"-"` } +// esto lo dejaria en capa de servicio, asi es mas facil de testear y de cambiar func GenerateToken(userID int64, ttl time.Duration, scope string) (*Token, error) { token := &Token{ UserID: userID, diff --git a/internal/users/models/users.go b/internal/users/models/users.go index 40bdbb4..cc5fb45 100644 --- a/internal/users/models/users.go +++ b/internal/users/models/users.go @@ -26,6 +26,7 @@ type Password struct { Hash []byte } +// esto va en la capa de servicio/logica, asi es mas facil de testear y de cambiar func (p *Password) Set(plaintextPassword string) error { hash, err := bcrypt.GenerateFromPassword([]byte(plaintextPassword), 12) if err != nil { @@ -38,6 +39,7 @@ func (p *Password) Set(plaintextPassword string) error { return nil } +// esto va en la capa de servicio/logica, asi es mas facil de testear y de cambiar func (p *Password) Matches(plaintextPassword string) (bool, error) { err := bcrypt.CompareHashAndPassword(p.Hash, []byte(plaintextPassword)) if err != nil { diff --git a/pkg/httphelpers/filters.go b/pkg/httphelpers/filters.go index 5e0cb23..57c9307 100644 --- a/pkg/httphelpers/filters.go +++ b/pkg/httphelpers/filters.go @@ -15,8 +15,10 @@ type Filters struct { func ValidateFilters(v *validator.Validator, filters Filters) { v.Check(filters.Page > 0, "page", "must be greater than zero") + // numero magico, va en config v.Check(filters.Page <= 10_000_000, "page", "must be a maximum of 10 million") v.Check(filters.PageSize > 0, "page_size", "must be greater than zero") + // numero magico, va en config v.Check(filters.PageSize <= 100, "page_size", "must be a maximum of 100") // Check that the sort parameter matches a value in the safelist. diff --git a/pkg/httphelpers/helpers.go b/pkg/httphelpers/helpers.go index fe36fef..de19de5 100644 --- a/pkg/httphelpers/helpers.go +++ b/pkg/httphelpers/helpers.go @@ -23,9 +23,7 @@ func ReadIDParam(c *gin.Context) (int64, error) { return id, nil } -type Envelope map[string]any - -func WriteJson(c *gin.Context, status int, data Envelope, headers http.Header) error { +func WriteJson(c *gin.Context, status int, data any, headers http.Header) error { js, err := json.MarshalIndent(data, "", "\t") if err != nil { return err diff --git a/pkg/mailer/mailer.go b/pkg/mailer/mailer.go index d0810bb..67eca7c 100644 --- a/pkg/mailer/mailer.go +++ b/pkg/mailer/mailer.go @@ -33,19 +33,26 @@ func (m Mailer) Send(recipient, templateFile string, data any) error { return err } - subject := new(bytes.Buffer) + // no se suele usar la funcion new, se suele usar el operador & para crear un puntero + // yo lo veo como que tiene 2 ventajas + // 1 - es mas claro que se esta creando un puntero + // 2 - es mas explicito que estamos con el valor cero del struct, y no nos confundimos con un + // new de otro lenguaje que puede tener un comportamiento distinto + // ademas a nivel del compilador, un new le estas diciendo que aloque memoria en el heap + // mientras que el & seguramente entre en el stack que es sustancialmente mas rapido + subject := &bytes.Buffer{} err = tmpl.ExecuteTemplate(subject, "subject", data) if err != nil { return err } - plainbody := new(bytes.Buffer) + plainbody := &bytes.Buffer{} err = tmpl.ExecuteTemplate(plainbody, "plainBody", data) if err != nil { return err } - htmlbody := new(bytes.Buffer) + htmlbody := &bytes.Buffer{} err = tmpl.ExecuteTemplate(htmlbody, "htmlBody", data) if err != nil { return err diff --git a/pkg/middlewares/middleware.go b/pkg/middlewares/middleware.go index f4abd0a..5636988 100644 --- a/pkg/middlewares/middleware.go +++ b/pkg/middlewares/middleware.go @@ -27,6 +27,7 @@ type PermissionsRepo interface { GetAllForUser(userID int64) (permissionsModels.Permissions, error) } +// a su propio archivo func RecoverPanic() gin.HandlerFunc { return func(c *gin.Context) { defer func() { @@ -38,6 +39,7 @@ func RecoverPanic() gin.HandlerFunc { } } +// a su propio archivo func LogErrorMiddleware(l *jsonlog.Logger) gin.HandlerFunc { return func(c *gin.Context) { c.Next() @@ -51,15 +53,19 @@ func LogErrorMiddleware(l *jsonlog.Logger) gin.HandlerFunc { } } -func RateLimit(ratelimit, tokens int, enabled bool, l *jsonlog.Logger) gin.HandlerFunc { - type client struct { - limiter *rate.Limiter - lastSeen time.Time - } +// a su propio archivo +type rlClient struct { + limiter *rate.Limiter + lastSeen time.Time +} +func RateLimit(ratelimit, tokens int, enabled bool, l *jsonlog.Logger) gin.HandlerFunc { var ( - mu sync.Mutex - clients = make(map[string]*client) + mu sync.Mutex + // hay que tener cuidado con los mapas de vida infinita, porque pueden generar un memory leak + // una opcion seria usar un cache como redis (ideal) + // otra opcion es usar un map con un tiempo de vida, y que se limpie cada cierto tiempo + clients = make(map[string]*rlClient) ) go func() { @@ -90,7 +96,7 @@ func RateLimit(ratelimit, tokens int, enabled bool, l *jsonlog.Logger) gin.Handl mu.Lock() if _, found := clients[ip]; !found { - clients[ip] = &client{limiter: rate.NewLimiter(rate.Limit(ratelimit), tokens)} + clients[ip] = &rlClient{limiter: rate.NewLimiter(rate.Limit(ratelimit), tokens)} } clients[ip].lastSeen = time.Now() @@ -107,6 +113,7 @@ func RateLimit(ratelimit, tokens int, enabled bool, l *jsonlog.Logger) gin.Handl } } +// a su propio archivo func Authenticate(UserRepo UserRepo) gin.HandlerFunc { return func(c *gin.Context) { c.Header("Vary", "Authorization") @@ -179,6 +186,7 @@ func RequireActivatedUser(next gin.HandlerFunc) gin.HandlerFunc { return RequireAuthenticatedUser(fn) } +// a su propio archivo func RequirePermission(permissionsRepo PermissionsRepo, code string) gin.HandlerFunc { fn := func(c *gin.Context) { user := httphelpers.ContextGetUser(c) diff --git a/tmp/build-errors.log b/tmp/build-errors.log deleted file mode 100644 index cab5696..0000000 --- a/tmp/build-errors.log +++ /dev/null @@ -1 +0,0 @@ -exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1 \ No newline at end of file diff --git a/tmp/main b/tmp/main deleted file mode 100755 index 073c25c..0000000 Binary files a/tmp/main and /dev/null differ