package joist import ( "context" "errors" "log" "sync" "time" "git.markbailey.dev/cerbervs/joist/internal/badger" jerr "git.markbailey.dev/cerbervs/joist/internal/errors" ) type routeCache struct { rs map[string]string ris map[string]*RouteInfo s Storer ttl time.Duration } type RouteCacheOpt func(*routeCache) var ( rcOnce sync.Once cache *routeCache ) func WithTTL(ttl time.Duration) RouteCacheOpt { return func(r *routeCache) { r.ttl = ttl } } func WithStore(store Storer) RouteCacheOpt { return func(r *routeCache) { r.s = store } } func NewRouteCacheService(opts ...RouteCacheOpt) (*routeCache, error) { rcOnce.Do(func() { c, err := newRouteCache(opts...) if err != nil { log.Fatal(err) } cache = c }) if cache == nil { return nil, jerr.ErrCacheUninitialized } return cache, nil } func (r *routeCache) All() (map[string]string, error) { if r.rs == nil { return nil, jerr.ErrCacheUninitialized } return r.rs, nil } func (r *routeCache) Add(name string, path string) error { ttl := r.ttl if ttl == 0 { ttl = 1 * time.Hour } if err := r.s.Put(context.Background(), name, []byte(path), ttl); err != nil { return err } r.rs[name] = path return nil } func (r *routeCache) GetPath(name string) (string, error) { if path, ok := r.rs[name]; ok { return path, nil } if path, err := r.s.Get(context.Background(), name); err == nil { return string(path), nil } return "", errors.New("route not found in cache") } func (r *routeCache) AddRouteInfo(routeInfo RouteInfo) error { ttl := r.ttl if ttl == 0 { ttl = 1 * time.Hour } if err := r.s.Put(context.Background(), routeInfo.Name+":method", []byte(routeInfo.Method), ttl); err != nil { return err } if err := r.s.Put(context.Background(), routeInfo.Name+":path", []byte(routeInfo.Path), ttl); err != nil { return err } if err := r.s.Put(context.Background(), routeInfo.Name+":description", []byte(routeInfo.Description), ttl); err != nil { return err } if err := r.s.Put(context.Background(), routeInfo.Name+":title", []byte(routeInfo.Title), ttl); err != nil { return err } r.rs[routeInfo.Name] = routeInfo.Path r.ris[routeInfo.Name] = &routeInfo return nil } func (r *routeCache) GetRouteInfo(name string) (RouteInfo, error) { if ri, ok := r.ris[name]; ok { return *ri, nil } if info, err := r.s.GetForPrefix(context.Background(), name); err == nil { return RouteInfo{ Method: string(info[":method"]), Name: name, Path: string(info[":path"]), Description: string(info[":description"]), Title: string(info[":title"]), }, nil } return RouteInfo{}, errors.New("route info not found in cache") } func newRouteCache(opts ...RouteCacheOpt) (*routeCache, error) { s, err := badger.NewBadgerStore(badger.WithSubDir("route_cache")) if err != nil { return nil, jerr.Wrap(jerr.ErrFailedToCreateRouteCache, err) } cache := &routeCache{ rs: make(map[string]string), s: s, } for _, opt := range opts { opt(cache) } return cache, nil }