mirror of
https://github.com/pocket-id/pocket-id.git
synced 2025-12-17 02:33:02 +03:00
feat: add CSP header (#908)
Co-authored-by: Alessandro (Ale) Segala <43508+ItalyPaleAle@users.noreply.github.com>
This commit is contained in:
78
backend/internal/utils/cache_util.go
Normal file
78
backend/internal/utils/cache_util.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sync/singleflight"
|
||||
)
|
||||
|
||||
type CacheEntry[T any] struct {
|
||||
Value T
|
||||
FetchedAt time.Time
|
||||
}
|
||||
|
||||
type ErrStale struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *ErrStale) Error() string { return "returned stale cache: " + e.Err.Error() }
|
||||
func (e *ErrStale) Unwrap() error { return e.Err }
|
||||
|
||||
type Cache[T any] struct {
|
||||
ttl time.Duration
|
||||
entry atomic.Pointer[CacheEntry[T]]
|
||||
sf singleflight.Group
|
||||
}
|
||||
|
||||
func New[T any](ttl time.Duration) *Cache[T] {
|
||||
return &Cache[T]{ttl: ttl}
|
||||
}
|
||||
|
||||
// Get returns the cached value if it's still fresh.
|
||||
func (c *Cache[T]) Get() (T, bool) {
|
||||
entry := c.entry.Load()
|
||||
if entry == nil {
|
||||
var zero T
|
||||
return zero, false
|
||||
}
|
||||
if time.Since(entry.FetchedAt) < c.ttl {
|
||||
return entry.Value, true
|
||||
}
|
||||
var zero T
|
||||
return zero, false
|
||||
}
|
||||
|
||||
// GetOrFetch returns the cached value if it's still fresh, otherwise calls fetch to get a new value.
|
||||
func (c *Cache[T]) GetOrFetch(ctx context.Context, fetch func(context.Context) (T, error)) (T, error) {
|
||||
// If fresh, serve immediately
|
||||
if v, ok := c.Get(); ok {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// Fetch with singleflight to prevent multiple concurrent fetches
|
||||
vAny, err, _ := c.sf.Do("singleton", func() (any, error) {
|
||||
if v2, ok := c.Get(); ok {
|
||||
return v2, nil
|
||||
}
|
||||
val, fetchErr := fetch(ctx)
|
||||
if fetchErr != nil {
|
||||
return nil, fetchErr
|
||||
}
|
||||
c.entry.Store(&CacheEntry[T]{Value: val, FetchedAt: time.Now()})
|
||||
return val, nil
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
return vAny.(T), nil
|
||||
}
|
||||
|
||||
// Fetch failed. Return stale if possible.
|
||||
if e := c.entry.Load(); e != nil {
|
||||
return e.Value, &ErrStale{Err: err}
|
||||
}
|
||||
|
||||
var zero T
|
||||
return zero, err
|
||||
}
|
||||
Reference in New Issue
Block a user