package store import ( "context" "encoding/json" "errors" "strings" "time" "github.com/rs/zerolog" "gorm.io/gorm" "gorm.io/gorm/clause" "base/internal/repository/postgres/cache" "base/pkg/metrics" ) // PostgresStore implements Store interface using Redis type PostgresStore[V any] struct { db *gorm.DB logger zerolog.Logger metrics *metrics.Metrics kvTableName string hashTableName string } func NewPostgresStore[V any](db *gorm.DB, logger zerolog.Logger, metrics *metrics.Metrics) Store[V] { return &PostgresStore[V]{ db: db, logger: logger, metrics: metrics, kvTableName: cache.KVModel{}.TableName(), hashTableName: cache.HashModel{}.TableName(), } } // Delete implements [Store]. func (p *PostgresStore[V]) Delete(ctx context.Context, key string) error { err := p.db.WithContext(ctx). Table(p.kvTableName). Where("key = ?", key). Delete(&cache.KVModel{}).Error if errors.Is(err, gorm.ErrRecordNotFound) { p.logger.Error().Err(err).Str("key", key).Msg("key not found") return nil } if err != nil { p.logger.Error().Err(err).Str("key", key).Msg("failed to delete key") return err } return err } // DeleteMultiple implements [Store]. func (p *PostgresStore[V]) DeleteMultiple(ctx context.Context, keys ...string) error { err := p.db.WithContext(ctx). Table(p.kvTableName). Where("key IN (?)", keys). Delete(&cache.KVModel{}).Error if errors.Is(err, gorm.ErrRecordNotFound) { p.logger.Error().Err(err).Str("keys", strings.Join(keys, ", ")).Msg("keys not found") return nil } if err != nil { p.logger.Error().Err(err).Str("keys", strings.Join(keys, ", ")).Msg("failed to delete keys") return err } return err } // DeletePattern implements [Store]. func (p *PostgresStore[V]) DeletePattern(ctx context.Context, pattern string) error { err := p.db.WithContext(ctx). Table(p.kvTableName). Where("key LIKE ?", pattern). Delete(&cache.KVModel{}).Error if errors.Is(err, gorm.ErrRecordNotFound) { p.logger.Error().Err(err).Str("pattern", pattern).Msg("pattern not found") return nil } if err != nil { p.logger.Error().Err(err).Str("pattern", pattern).Msg("failed to delete pattern") return err } return err } // Exists implements [Store]. func (p *PostgresStore[V]) Exists(ctx context.Context, key string) (bool, error) { var count int64 err := p.db.WithContext(ctx).Table(p.kvTableName). Where("key = ? AND (expires_at IS NULL OR expires_at > ?)", key, time.Now()). Count(&count).Error if errors.Is(err, gorm.ErrRecordNotFound) { p.logger.Error().Err(err).Str("key", key).Msg("key not found") return false, nil } if err != nil { p.logger.Error().Err(err).Str("key", key).Msg("failed to check if key exists") return false, err } return count > 0, nil } // Get implements [Store]. func (p *PostgresStore[V]) Get(ctx context.Context, key string) (V, bool, error) { var row cache.KVModel err := p.db.WithContext(ctx).Table(p.kvTableName). Where("key = ? AND (expires_at IS NULL OR expires_at > ?)", key, time.Now()). First(&row).Error if errors.Is(err, gorm.ErrRecordNotFound) { var zero V return zero, false, nil } if err != nil { var zero V return zero, false, err } var val V if err := json.Unmarshal(row.Value, &val); err != nil { return val, false, err } return val, true, nil } // HGetAll implements [Store]. func (p *PostgresStore[V]) HGetAll(ctx context.Context, key string) (map[string]V, error) { panic("unimplemented") } // HMGet implements [Store]. func (p *PostgresStore[V]) HMGet(ctx context.Context, key string, fields ...string) (map[string]V, error) { panic("unimplemented") } // HMSet implements [Store]. func (p *PostgresStore[V]) HMSet(ctx context.Context, key string, values map[string]V, expiration time.Duration) error { panic("unimplemented") } // Set implements [Store]. func (p *PostgresStore[V]) Set(ctx context.Context, key string, value V, expiration time.Duration) error { data, _ := json.Marshal(value) var expires *time.Time if expiration > 0 { t := time.Now().Add(expiration) expires = &t } err := p.db.WithContext(ctx). Table(p.kvTableName). Clauses(clause.OnConflict{ UpdateAll: true, }). Create(&cache.KVModel{ Key: key, Value: data, ExpiresAt: expires, }).Error if errors.Is(err, gorm.ErrRecordNotFound) { p.logger.Error().Err(err).Str("key", key).Msg("key not found") return nil } if err != nil { p.logger.Error().Err(err).Str("key", key).Msg("failed to set key") return err } return err } // SetMultiple implements [Store]. func (p *PostgresStore[V]) SetMultiple(ctx context.Context, items map[string]V, expiration time.Duration) error { panic("unimplemented") } // SetNX implements [Store]. func (p *PostgresStore[V]) SetNX(ctx context.Context, key string, value V, expiration time.Duration) (bool, error) { panic("unimplemented") }