11package prx
22
33import (
4- "context"
54 "crypto/sha256"
65 "encoding/hex"
76 "errors"
87 "fmt"
9- "log/slog"
108 "os"
119 "path/filepath"
1210 "strconv"
1311 "strings"
1412 "time"
1513
16- "github.com/codeGROOVE-dev/sfcache"
17- "github.com/codeGROOVE-dev/sfcache/pkg/persist/localfs"
14+ "github.com/codeGROOVE-dev/sfcache/pkg/store/localfs"
1815)
1916
2017const (
@@ -24,51 +21,6 @@ const (
2421 collaboratorsCacheTTL = 4 * time .Hour
2522)
2623
27- // prxCache provides tiered caching with disk persistence using sfcache.
28- type prxCache struct {
29- pr * sfcache.TieredCache [string , PullRequestData ]
30- logger * slog.Logger
31- }
32-
33- // newCache creates a new cache with disk persistence at the given directory.
34- func newCache (cacheDir string , logger * slog.Logger ) (* prxCache , error ) {
35- if logger == nil {
36- logger = slog .Default ()
37- }
38-
39- cleanPath := filepath .Clean (cacheDir )
40- if ! filepath .IsAbs (cleanPath ) {
41- return nil , errors .New ("cache directory must be absolute path" )
42- }
43-
44- if err := os .MkdirAll (cleanPath , 0o700 ); err != nil {
45- return nil , fmt .Errorf ("creating cache directory: %w" , err )
46- }
47-
48- prStore , err := localfs .New [string , PullRequestData ]("prx-pr" , cleanPath )
49- if err != nil {
50- return nil , fmt .Errorf ("creating PR cache store: %w" , err )
51- }
52-
53- prCache , err := sfcache .NewTiered (prStore , sfcache .TTL (prCacheTTL ))
54- if err != nil {
55- return nil , fmt .Errorf ("creating PR cache: %w" , err )
56- }
57-
58- return & prxCache {
59- pr : prCache ,
60- logger : logger ,
61- }, nil
62- }
63-
64- // close releases cache resources.
65- func (c * prxCache ) close () error {
66- if c .pr != nil {
67- return c .pr .Close ()
68- }
69- return nil
70- }
71-
7224// prCacheKey generates a cache key for PR data.
7325func prCacheKey (owner , repo string , prNumber int ) string {
7426 key := strings .Join ([]string {"graphql" , "pr_graphql" , owner , repo , strconv .Itoa (prNumber )}, "/" )
@@ -81,108 +33,34 @@ func collaboratorsCacheKey(owner, repo string) string {
8133 return fmt .Sprintf ("%s/%s" , owner , repo )
8234}
8335
84- // CacheClient wraps the regular Client and adds disk-based caching.
85- type CacheClient struct {
86- * Client
87-
88- cache * prxCache
89- }
90-
91- // NewCacheClient creates a new caching client with the given cache directory.
92- func NewCacheClient (token string , cacheDir string , opts ... Option ) (* CacheClient , error ) {
93- // Create a temporary logger to use during cache creation
94- logger := slog .Default ()
95-
96- cache , err := newCache (cacheDir , logger )
36+ // CacheClient is an alias for Client, kept for backward compatibility.
37+ //
38+ // Deprecated: Use NewClient with WithCacheStore instead.
39+ type CacheClient = Client
40+
41+ // NewCacheClient creates a client with a specific cache directory.
42+ //
43+ // Deprecated: Use NewClient with WithCacheStore instead.
44+ func NewCacheClient (token , dir string , opts ... Option ) (* Client , error ) {
45+ dir = filepath .Clean (dir )
46+ if ! filepath .IsAbs (dir ) {
47+ return nil , errors .New ("cache directory must be absolute path" )
48+ }
49+ if err := os .MkdirAll (dir , 0o700 ); err != nil {
50+ return nil , fmt .Errorf ("creating cache directory: %w" , err )
51+ }
52+ store , err := localfs .New [string , PullRequestData ]("prx-pr" , dir )
9753 if err != nil {
98- return nil , err
54+ return nil , fmt . Errorf ( "creating PR cache store: %w" , err )
9955 }
100-
101- // Create client with no cache since CacheClient handles caching
102- opts = append (opts , WithNoCache ())
103- client := NewClient (token , opts ... )
104-
105- // Update logger to the one from client options
106- cache .logger = client .logger
107-
108- return & CacheClient {
109- Client : client ,
110- cache : cache ,
111- }, nil
56+ opts = append (opts , WithCacheStore (store ))
57+ return NewClient (token , opts ... ), nil
11258}
11359
11460// Close releases cache resources.
115- func (c * CacheClient ) Close () error {
116- if c .cache != nil {
117- return c .cache . close ()
61+ func (c * Client ) Close () error {
62+ if c .prCache != nil {
63+ return c .prCache . Close ()
11864 }
11965 return nil
12066}
121-
122- // PullRequest fetches a pull request with all its events and metadata, with caching support.
123- func (c * CacheClient ) PullRequest (ctx context.Context , owner , repo string , prNumber int , referenceTime time.Time ) (* PullRequestData , error ) {
124- return c .PullRequestWithReferenceTime (ctx , owner , repo , prNumber , referenceTime )
125- }
126-
127- // PullRequestWithReferenceTime fetches PR data using GetSet for thundering herd protection.
128- func (c * CacheClient ) PullRequestWithReferenceTime (
129- ctx context.Context , owner , repo string , prNumber int , referenceTime time.Time ,
130- ) (* PullRequestData , error ) {
131- if c .cache == nil || c .cache .pr == nil {
132- return c .pullRequestViaGraphQL (ctx , owner , repo , prNumber )
133- }
134-
135- cacheKey := prCacheKey (owner , repo , prNumber )
136-
137- // Try to get from cache first with time validation
138- cached , found , err := c .cache .pr .Get (ctx , cacheKey )
139- if err != nil {
140- c .logger .WarnContext (ctx , "cache get error" ,
141- "owner" , owner ,
142- "repo" , repo ,
143- "pr" , prNumber ,
144- "error" , err )
145- }
146- if found {
147- // Check if cache entry is fresh enough (cached after reference time)
148- // Note: sfcache handles TTL expiration; we check freshness against referenceTime
149- if cached .CachedAt .After (referenceTime ) || cached .CachedAt .Equal (referenceTime ) {
150- c .logger .InfoContext (ctx , "cache hit: GraphQL pull request" ,
151- "owner" , owner ,
152- "repo" , repo ,
153- "pr" , prNumber ,
154- "cached_at" , cached .CachedAt )
155- return & cached , nil
156- }
157- c .logger .InfoContext (ctx , "cache miss: GraphQL pull request expired" ,
158- "owner" , owner ,
159- "repo" , repo ,
160- "pr" , prNumber ,
161- "cached_at" , cached .CachedAt ,
162- "reference_time" , referenceTime )
163- // Delete stale entry so GetSet will fetch fresh data
164- if delErr := c .cache .pr .Delete (ctx , cacheKey ); delErr != nil {
165- c .logger .WarnContext (ctx , "failed to delete stale cache entry" , "error" , delErr )
166- }
167- } else {
168- c .logger .InfoContext (ctx , "cache miss: GraphQL pull request not in cache" ,
169- "owner" , owner ,
170- "repo" , repo ,
171- "pr" , prNumber )
172- }
173-
174- // Fetch from API using GetSet for thundering herd protection
175- result , err := c .cache .pr .GetSet (ctx , cacheKey , func (ctx context.Context ) (PullRequestData , error ) {
176- prData , err := c .pullRequestViaGraphQL (ctx , owner , repo , prNumber )
177- if err != nil {
178- return PullRequestData {}, err
179- }
180- prData .CachedAt = time .Now ()
181- return * prData , nil
182- })
183- if err != nil {
184- return nil , err
185- }
186-
187- return & result , nil
188- }
0 commit comments