diff --git a/internal/limiter/base.go b/internal/limiter/base.go new file mode 100644 index 0000000..17b13fb --- /dev/null +++ b/internal/limiter/base.go @@ -0,0 +1,5 @@ +package limiter + +type ILimitStorage interface { + GetLimits() (*Limits, error) +} diff --git a/internal/limiter/limit.go b/internal/limiter/limit.go new file mode 100644 index 0000000..d2afe21 --- /dev/null +++ b/internal/limiter/limit.go @@ -0,0 +1,17 @@ +package limiter + +type LimitType string + +const ( + LoginLimit LimitType = "login" + PasswordLimit LimitType = "password" + IPLimit LimitType = "ip" +) + +type Limits []Limit + +type Limit struct { + limitType LimitType + value string + description string +} diff --git a/internal/limiter/limit_storage.go b/internal/limiter/limit_storage.go new file mode 100644 index 0000000..b29de67 --- /dev/null +++ b/internal/limiter/limit_storage.go @@ -0,0 +1,86 @@ +package limiter + +import ( + "context" + "database/sql" + "errors" + "fmt" +) + +var ErrConnectFailed = errors.New("error connecting to db") + +const DSN = "postgresql://main:main@localhost:5432/rate_limiter?sslmode=disable" + +type LimitStorage struct { + db *sql.DB + ctx context.Context +} + +func NewLimitStorage() *LimitStorage { + return &LimitStorage{} +} + +func (s *LimitStorage) GetLimits() (*Limits, error) { + limits := make(Limits, 0) + + rows, err := s.db.QueryContext(s.ctx, "select type, value, description from rate_limit;") + if err != nil { + return nil, err + } + defer rows.Close() + + for rows.Next() { + limit, scanErr := s.scanRow(rows) + if scanErr != nil { + return nil, err + } + + limits = append(limits, *limit) + } + + return &limits, nil +} + +func (s *LimitStorage) Connect(ctx context.Context) error { + db, openErr := sql.Open("postgres", DSN) + if openErr != nil { + return fmt.Errorf(ErrConnectFailed.Error()+":%w", openErr) + } + + pingErr := db.PingContext(ctx) + if pingErr != nil { + return fmt.Errorf(ErrConnectFailed.Error()+":%w", pingErr) + } + + s.db = db + s.ctx = ctx + + return nil +} + +func (s *LimitStorage) Close(_ context.Context) error { + closeErr := s.db.Close() + if closeErr != nil { + return closeErr + } + + s.ctx = nil + + return nil +} + +func (s *LimitStorage) scanRow(rows *sql.Rows) (*Limit, error) { + limit := Limit{} + nullableDescription := sql.NullString{} + + err := rows.Scan(&limit.limitType, &limit.value, &nullableDescription) + if err != nil { + return nil, err + } + + if nullableDescription.Valid { + limit.description = nullableDescription.String + } + + return &limit, nil +} diff --git a/migrations/20231101165517_add_limit_table.go b/migrations/20231101165517_add_limit_table.go new file mode 100644 index 0000000..0bebc54 --- /dev/null +++ b/migrations/20231101165517_add_limit_table.go @@ -0,0 +1,34 @@ +package migrations + +import ( + "context" + "database/sql" + + "github.com/pressly/goose/v3" +) + +func init() { + goose.AddMigrationContext(upAddLimitTable, downAddLimitTable) +} + +func upAddLimitTable(ctx context.Context, tx *sql.Tx) error { + query := `create table rate_limit( + type varchar(50) primary key, + value int not null, + description varchar(255) null +);` + + if _, err := tx.ExecContext(ctx, query); err != nil { + return err + } + + return nil +} + +func downAddLimitTable(ctx context.Context, tx *sql.Tx) error { + if _, err := tx.ExecContext(ctx, "DROP TABLE IF EXISTS rate_limit;"); err != nil { + return err + } + + return nil +} diff --git a/migrations/20231101201204_fill_limit_table.go b/migrations/20231101201204_fill_limit_table.go new file mode 100644 index 0000000..4193a8e --- /dev/null +++ b/migrations/20231101201204_fill_limit_table.go @@ -0,0 +1,35 @@ +package migrations + +import ( + "context" + "database/sql" + + "api-rate-limiter/internal/limiter" + "github.com/pressly/goose/v3" +) + +func init() { + goose.AddMigrationContext(upFillLimitTable, downFillLimitTable) +} + +func upFillLimitTable(ctx context.Context, tx *sql.Tx) error { + query := `insert into rate_limit(type, value, description) + values ($1, 10, 'Ограничение для логина'), + ($2, 100, 'Ограничение для пароля'), + ($3, 1000, null) + ;` + + if _, err := tx.ExecContext(ctx, query, limiter.LoginLimit, limiter.PasswordLimit, limiter.IPLimit); err != nil { + return err + } + + return nil +} + +func downFillLimitTable(ctx context.Context, tx *sql.Tx) error { + if _, err := tx.ExecContext(ctx, `truncate table rate_limit;`); err != nil { + return err + } + + return nil +}