This commit is contained in:
Alex Shevchuk
2025-08-18 17:12:04 +03:00
commit d84487d238
157 changed files with 160686 additions and 0 deletions

16
internal/cache/client.go vendored Normal file
View File

@@ -0,0 +1,16 @@
package cache
import "fmt"
func New(c Config, t Type) (Client, error) {
switch t {
case ValkeyCacheType:
return newValKeyCache(c)
default:
return nil, ErrUnknownCacheType
}
}
func getKey(k string, t ValueType) string {
return fmt.Sprintf("%s:%s", t, k)
}

16
internal/cache/errors.go vendored Normal file
View File

@@ -0,0 +1,16 @@
package cache
import "errors"
var (
ErrUnknownCacheType = errors.New("unknown cache type")
ErrInvalidConfig = errors.New("invalid config")
ErrConnect = errors.New("connecting to server")
ErrTlsConfig = errors.New("tls config")
ErrInvalidInstance = errors.New("check instance failed")
ErrKeyNotFound = errors.New("key not found")
ErrKeyExists = errors.New("key already exists")
ErrGet = errors.New("failed to get value")
ErrSet = errors.New("failed to set value")
ErrDel = errors.New("failed to delete value")
)

53
internal/cache/types.go vendored Normal file
View File

@@ -0,0 +1,53 @@
package cache
import (
"context"
"time"
)
type Client interface {
// Set добавляет в кеш под ключем key значение value
//
// Если такого ключа с таким типом значения нет,
// то возвращает ошибку ErrKeyExists
Set(ctx context.Context, key string, valueType ValueType, value any, expiration time.Duration) error
// Del удаляет значение по ключу key и указанному valueType
//
// Если такого ключа с таким типом значения нет,
// то возвращает ошибку ErrKeyNotFound
Del(ctx context.Context, key string, valueType ValueType) error
// Get возвращает значение по ключу key и valueType
//
// Если такого ключа с таким типом значения нет,
// то возвращает ошибку ErrKeyNotFound
//
// В случае, если получаемое значение не является строкой,
// необходимо воспользоваться соответсвующим методом под
// этот тип данных
Get(ctx context.Context, key string, valueType ValueType) (string, error)
// TODO: По необходимости расширить необходимыми методами
}
type (
Type uint
ValueType string
Config any
)
const (
ValkeyCacheType Type = iota
)
const (
LogoValueType ValueType = "value_logo_type"
DocumentsValueType ValueType = "value_documents_type"
PswResetOTPValueType ValueType = "value_otp_type"
PswResetTokenValueType ValueType = "value_token_type"
IntegrationCompanyValueType ValueType = "integration:company"
IntegrationVacancyValueType ValueType = "integration:vacancy"
)

143
internal/cache/valkey.go vendored Normal file
View File

@@ -0,0 +1,143 @@
package cache
import (
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"os"
"time"
"github.com/go-redis/redis/v7"
)
type ValKeyCacheConfig struct {
Addrs []string
Password string
ReadOnly bool
DialTimeout time.Duration
PoolSize int
DefaultTTL time.Duration
RootCaFilePath string
}
type valkeyClient interface {
Set(key string, value any, expiration time.Duration) *redis.StatusCmd
Get(key string) *redis.StringCmd
Del(keys ...string) *redis.IntCmd
Exists(keys ...string) *redis.IntCmd
Ping() *redis.StatusCmd
}
type valKeyCache struct {
config ValKeyCacheConfig
client valkeyClient
}
func newValKeyCache(c Config) (*valKeyCache, error) {
cfg, ok := c.(ValKeyCacheConfig)
if !ok {
return nil, ErrInvalidConfig
}
caCert, err := os.ReadFile(cfg.RootCaFilePath)
if err != nil {
return nil, fmt.Errorf("%w: %s", ErrTlsConfig, err.Error())
}
caCertPool := x509.NewCertPool()
if ok := caCertPool.AppendCertsFromPEM(caCert); !ok {
return nil, fmt.Errorf("%w: %s", ErrTlsConfig, "failed to append CA cert to pool")
}
tlsConfig := &tls.Config{
RootCAs: caCertPool,
MinVersion: tls.VersionTLS12,
}
client := redis.NewUniversalClient(&redis.UniversalOptions{
Addrs: cfg.Addrs,
Password: cfg.Password,
ReadOnly: cfg.ReadOnly,
DialTimeout: cfg.DialTimeout,
PoolSize: cfg.PoolSize,
TLSConfig: tlsConfig,
})
if err := client.Ping().Err(); err != nil {
return nil, fmt.Errorf("%w: %v", ErrConnect, err)
}
return &valKeyCache{
config: cfg,
client: client,
}, nil
}
func (c *valKeyCache) Get(ctx context.Context, key string, valueType ValueType) (string, error) {
if err := c.checkInstance(); err != nil {
return "", fmt.Errorf("%w: %v", ErrGet, err)
}
val, err := c.client.Get(getKey(key, valueType)).Result()
if err == redis.Nil {
return "", ErrKeyNotFound
}
if err != nil {
return "", fmt.Errorf("%w: %v", ErrGet, err)
}
return val, nil
}
func (c *valKeyCache) Set(ctx context.Context, key string, valueType ValueType, value any, expiration time.Duration) error {
if err := c.checkInstance(); err != nil {
return fmt.Errorf("%w: %v", ErrSet, err)
}
key = getKey(key, valueType)
if exists, err := c.client.Exists(key).Result(); err != nil || exists == 1 {
if exists == 1 {
return ErrKeyExists
}
return fmt.Errorf("%w: %v", ErrSet, err)
}
if err := c.client.Set(key, value, expiration).Err(); err != nil {
return fmt.Errorf("%w: %v", ErrSet, err)
}
return nil
}
func (c *valKeyCache) Del(ctx context.Context, key string, valueType ValueType) error {
if err := c.checkInstance(); err != nil {
return fmt.Errorf("%w: %v", ErrDel, err.Error())
}
if res, err := c.client.Del(getKey(key, valueType)).Result(); err != nil || res == 0 {
if res == 0 {
return ErrKeyNotFound
}
return fmt.Errorf("%w: %s", ErrDel, err.Error())
}
return nil
}
func (c *valKeyCache) checkInstance() error {
if c == nil {
return ErrInvalidInstance
}
if s := c.client.Ping(); s.Err() != nil {
return fmt.Errorf("%w: %v", ErrInvalidInstance, s.Err().Error())
}
return nil
}