Some checks failed
CI / v3 / Check ui (pull_request) Failing after 15s
CI / v3 / Test backend (pull_request) Failing after 16s
CI / v3 / Docker / backend (pull_request) Has been skipped
CI / v3 / Docker / runner (pull_request) Has been skipped
CI / v3 / Docker / ui (pull_request) Has been skipped
- Remove all pre-v3 code: scraper, ui-v2, backend v1, ios v1+v2, legacy CI workflows - Flatten v3/ contents to repo root - Add Doppler secrets management (project=libnovel, config=prd) - Add justfile with doppler run wrappers for all docker compose commands - Strip hardcoded env fallbacks from docker-compose.yml - Add minimal README.md - Clean up .gitignore
97 lines
3.5 KiB
Go
97 lines
3.5 KiB
Go
// Package presigncache provides a Valkey (Redis-compatible) backed cache for
|
|
// MinIO presigned URLs. The backend generates presigned URLs and stores them
|
|
// here with a TTL; subsequent requests for the same key return the cached URL
|
|
// without re-contacting MinIO.
|
|
//
|
|
// Design:
|
|
// - Cache is intentionally best-effort: Get returns ("", false, nil) on any
|
|
// Valkey error, so callers always have a fallback path to regenerate.
|
|
// - Set silently drops errors — a miss on the next request is acceptable.
|
|
// - TTL should be set shorter than the actual presigned URL lifetime so that
|
|
// cached URLs are always valid when served. Recommended: 55 minutes for a
|
|
// 1-hour presigned URL.
|
|
package presigncache
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/redis/go-redis/v9"
|
|
)
|
|
|
|
// Cache is the interface for presign URL caching.
|
|
// Implementations must be safe for concurrent use.
|
|
type Cache interface {
|
|
// Get returns the cached URL for key. ok is false on cache miss or error.
|
|
Get(ctx context.Context, key string) (url string, ok bool, err error)
|
|
// Set stores url under key with the given TTL.
|
|
Set(ctx context.Context, key, url string, ttl time.Duration) error
|
|
// Delete removes key from the cache.
|
|
Delete(ctx context.Context, key string) error
|
|
}
|
|
|
|
// ValkeyCache is a Cache backed by Valkey / Redis via go-redis.
|
|
type ValkeyCache struct {
|
|
rdb *redis.Client
|
|
}
|
|
|
|
// New creates a ValkeyCache connecting to addr (e.g. "valkey:6379").
|
|
// The connection is not established until the first command; use Ping to
|
|
// verify connectivity at startup.
|
|
func New(addr string) *ValkeyCache {
|
|
rdb := redis.NewClient(&redis.Options{
|
|
Addr: addr,
|
|
DialTimeout: 2 * time.Second,
|
|
ReadTimeout: 1 * time.Second,
|
|
WriteTimeout: 1 * time.Second,
|
|
})
|
|
return &ValkeyCache{rdb: rdb}
|
|
}
|
|
|
|
// Ping checks connectivity. Call once at startup.
|
|
func (c *ValkeyCache) Ping(ctx context.Context) error {
|
|
if err := c.rdb.Ping(ctx).Err(); err != nil {
|
|
return fmt.Errorf("presigncache: ping valkey: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Get returns (url, true, nil) on hit, ("", false, nil) on miss, and
|
|
// ("", false, err) only on unexpected errors (not redis.Nil).
|
|
func (c *ValkeyCache) Get(ctx context.Context, key string) (string, bool, error) {
|
|
val, err := c.rdb.Get(ctx, key).Result()
|
|
if err == redis.Nil {
|
|
return "", false, nil
|
|
}
|
|
if err != nil {
|
|
return "", false, fmt.Errorf("presigncache: get %q: %w", key, err)
|
|
}
|
|
return val, true, nil
|
|
}
|
|
|
|
// Set stores url under key with ttl. Errors are returned but are non-fatal
|
|
// for callers — a Set failure means the next request will miss and regenerate.
|
|
func (c *ValkeyCache) Set(ctx context.Context, key, url string, ttl time.Duration) error {
|
|
if err := c.rdb.Set(ctx, key, url, ttl).Err(); err != nil {
|
|
return fmt.Errorf("presigncache: set %q: %w", key, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Delete removes key from the cache. It is not an error if the key does not exist.
|
|
func (c *ValkeyCache) Delete(ctx context.Context, key string) error {
|
|
if err := c.rdb.Del(ctx, key).Err(); err != nil {
|
|
return fmt.Errorf("presigncache: delete %q: %w", key, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// NoopCache is a no-op Cache that always returns a miss. Used when Valkey is
|
|
// not configured (e.g. local development without Docker).
|
|
type NoopCache struct{}
|
|
|
|
func (NoopCache) Get(_ context.Context, _ string) (string, bool, error) { return "", false, nil }
|
|
func (NoopCache) Set(_ context.Context, _, _ string, _ time.Duration) error { return nil }
|
|
func (NoopCache) Delete(_ context.Context, _ string) error { return nil }
|