Skip to content

Commit a4d2886

Browse files
Thomas StrombergThomas Stromberg
authored andcommitted
more code cleanup
1 parent e78bf2c commit a4d2886

File tree

8 files changed

+89
-458
lines changed

8 files changed

+89
-458
lines changed

README.md

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -213,24 +213,30 @@ The library fetches the following event kinds:
213213
- **Bot detection** (marks events from bots with `"bot": true`)
214214
- **Mention extraction** (populated in `targets` field for comments/reviews)
215215
- **Question detection** (marks comments containing questions)
216-
- **Caching support** via `prx.NewCacheClient()` for reduced API calls
216+
- **Caching support** via `prx.WithCacheStore()` for reduced API calls
217217
- **Structured logging** with slog
218218
- **Retry logic** with exponential backoff for API reliability
219219

220220
## Caching
221221

222-
For applications that need to fetch the same PR data repeatedly:
222+
Caching is enabled by default with disk persistence to the user cache directory.
223+
224+
For custom cache directories:
225+
226+
```go
227+
store, err := prx.NewCacheStore("/tmp/prx-cache")
228+
client := prx.NewClient(token, prx.WithCacheStore(store))
229+
```
230+
231+
To disable caching persistence (memory-only):
223232

224233
```go
225-
// Create a caching client
226-
cacheDir := "/tmp/prx-cache"
227-
client, err := prx.NewCacheClient(token, cacheDir)
234+
import "github.com/codeGROOVE-dev/sfcache/pkg/store/null"
228235

229-
// Fetch with caching (uses updated_at timestamp for cache invalidation)
230-
data, err := client.PullRequest(ctx, "owner", "repo", 123, time.Now())
236+
client := prx.NewClient(token, prx.WithCacheStore(null.New[string, prx.PullRequestData]()))
231237
```
232238

233-
Cache files are automatically cleaned up after 20 days.
239+
Cache entries expire after 20 days.
234240

235241
## Authentication
236242

pkg/prx/cache.go

Lines changed: 0 additions & 66 deletions
This file was deleted.

pkg/prx/client.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,17 @@ package prx
55

66
import (
77
"context"
8+
"crypto/sha256"
9+
"encoding/hex"
810
"encoding/json"
11+
"errors"
12+
"fmt"
913
"log/slog"
1014
"net/http"
1115
"os"
1216
"path/filepath"
17+
"strconv"
18+
"strings"
1319
"time"
1420

1521
"github.com/codeGROOVE-dev/sfcache"
@@ -21,6 +27,10 @@ const (
2127
maxIdleConns = 100
2228
maxIdleConnsPerHost = 10
2329
idleConnTimeoutSec = 90
30+
31+
// Cache TTL constants.
32+
prCacheTTL = 20 * 24 * time.Hour // 20 days
33+
collaboratorsCacheTTL = 4 * time.Hour
2434
)
2535

2636
// PRStore is the interface for PR cache storage backends.
@@ -187,3 +197,40 @@ func (c *Client) PullRequestWithReferenceTime(
187197
}
188198
return &result, nil
189199
}
200+
201+
// Close releases cache resources.
202+
func (c *Client) Close() error {
203+
if c.prCache != nil {
204+
return c.prCache.Close()
205+
}
206+
return nil
207+
}
208+
209+
// NewCacheStore creates a cache store backed by the given directory.
210+
// This is a convenience function for use with WithCacheStore.
211+
func NewCacheStore(dir string) (PRStore, error) {
212+
dir = filepath.Clean(dir)
213+
if !filepath.IsAbs(dir) {
214+
return nil, errors.New("cache directory must be absolute path")
215+
}
216+
if err := os.MkdirAll(dir, 0o700); err != nil {
217+
return nil, fmt.Errorf("creating cache directory: %w", err)
218+
}
219+
store, err := localfs.New[string, PullRequestData]("prx-pr", dir)
220+
if err != nil {
221+
return nil, fmt.Errorf("creating PR cache store: %w", err)
222+
}
223+
return store, nil
224+
}
225+
226+
// prCacheKey generates a cache key for PR data.
227+
func prCacheKey(owner, repo string, prNumber int) string {
228+
key := strings.Join([]string{"graphql", "pr_graphql", owner, repo, strconv.Itoa(prNumber)}, "/")
229+
hash := sha256.Sum256([]byte(key))
230+
return hex.EncodeToString(hash[:])
231+
}
232+
233+
// collaboratorsCacheKey generates a cache key for collaborators data.
234+
func collaboratorsCacheKey(owner, repo string) string {
235+
return fmt.Sprintf("%s/%s", owner, repo)
236+
}
Lines changed: 14 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package prx
22

33
import (
44
"context"
5-
"encoding/json"
65
"log/slog"
76
"net/http"
87
"net/http/httptest"
@@ -15,62 +14,12 @@ func TestCacheClient(t *testing.T) {
1514
// Create temporary cache directory
1615
cacheDir := t.TempDir()
1716

18-
// Create test server
17+
// Create test server that handles GraphQL and REST endpoints
1918
requestCount := 0
2019
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2120
requestCount++
2221

2322
switch r.URL.Path {
24-
case "/repos/test/repo/pulls/1":
25-
pr := githubPullRequest{
26-
Number: 1,
27-
Title: "Test PR",
28-
Body: "Test body",
29-
CreatedAt: time.Now().Add(-24 * time.Hour),
30-
UpdatedAt: time.Now().Add(-2 * time.Hour),
31-
User: &githubUser{Login: "testuser"},
32-
State: "closed",
33-
ClosedAt: time.Now().Add(-1 * time.Hour),
34-
}
35-
pr.Head.SHA = "abc123"
36-
if err := json.NewEncoder(w).Encode(pr); err != nil {
37-
http.Error(w, err.Error(), http.StatusInternalServerError)
38-
return
39-
}
40-
case "/repos/test/repo/pulls/1/commits":
41-
commit := &githubPullRequestCommit{
42-
Author: &githubUser{Login: "testuser"},
43-
}
44-
commit.Commit.Author.Date = time.Now().Add(-12 * time.Hour)
45-
commit.Commit.Message = "Test commit"
46-
if err := json.NewEncoder(w).Encode([]*githubPullRequestCommit{commit}); err != nil {
47-
http.Error(w, err.Error(), http.StatusInternalServerError)
48-
return
49-
}
50-
case "/repos/test/repo/commits/abc123/status":
51-
// Combined status endpoint - return empty response
52-
if _, err := w.Write([]byte(`{"state": "success", "statuses": []}`)); err != nil {
53-
http.Error(w, err.Error(), http.StatusInternalServerError)
54-
return
55-
}
56-
case "/repos/test/repo/branches//protection":
57-
// Branch protection endpoint - return empty protection
58-
if _, err := w.Write([]byte(`{}`)); err != nil {
59-
http.Error(w, err.Error(), http.StatusInternalServerError)
60-
return
61-
}
62-
case "/repos/test/repo/branches//protection/required_status_checks":
63-
// Required status checks endpoint - return no required checks
64-
if _, err := w.Write([]byte(`{}`)); err != nil {
65-
http.Error(w, err.Error(), http.StatusInternalServerError)
66-
return
67-
}
68-
case "/repos/test/repo/rulesets":
69-
// Rulesets endpoint - return empty rulesets
70-
if _, err := w.Write([]byte(`[]`)); err != nil {
71-
http.Error(w, err.Error(), http.StatusInternalServerError)
72-
return
73-
}
7423
case "/graphql":
7524
// GraphQL endpoint - return a minimal PR response
7625
response := `{"data": {"repository": {"pullRequest": {
@@ -107,8 +56,13 @@ func TestCacheClient(t *testing.T) {
10756
http.Error(w, err.Error(), http.StatusInternalServerError)
10857
return
10958
}
59+
case "/repos/test/repo/rulesets":
60+
if _, err := w.Write([]byte(`[]`)); err != nil {
61+
http.Error(w, err.Error(), http.StatusInternalServerError)
62+
return
63+
}
11064
default:
111-
// Check runs endpoint expects a different format
65+
// Check runs endpoint
11266
if r.URL.Path == "/repos/test/repo/commits/abc123/check-runs" {
11367
if _, err := w.Write([]byte(`{"check_runs": []}`)); err != nil {
11468
http.Error(w, err.Error(), http.StatusInternalServerError)
@@ -125,14 +79,16 @@ func TestCacheClient(t *testing.T) {
12579
}))
12680
defer server.Close()
12781

128-
// Create cache client with test server
129-
client, err := NewCacheClient("test-token", cacheDir,
82+
// Create cache store and client with test server
83+
store, err := NewCacheStore(cacheDir)
84+
if err != nil {
85+
t.Fatalf("Failed to create cache store: %v", err)
86+
}
87+
client := NewClient("test-token",
88+
WithCacheStore(store),
13089
WithHTTPClient(&http.Client{Transport: &http.Transport{}}),
13190
WithLogger(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelDebug}))),
13291
)
133-
if err != nil {
134-
t.Fatalf("Failed to create cache client: %v", err)
135-
}
13692
defer func() {
13793
if closeErr := client.Close(); closeErr != nil {
13894
t.Errorf("Failed to close client: %v", closeErr)
Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import (
55
"testing"
66
)
77

8-
func TestNewCacheClient(t *testing.T) {
8+
func TestNewCacheStore(t *testing.T) {
99
tmpDir := t.TempDir()
1010

1111
tests := []struct {
@@ -27,7 +27,7 @@ func TestNewCacheClient(t *testing.T) {
2727

2828
for _, tt := range tests {
2929
t.Run(tt.name, func(t *testing.T) {
30-
client, err := NewCacheClient("test-token", tt.dir)
30+
store, err := NewCacheStore(tt.dir)
3131
if tt.wantErr {
3232
if err == nil {
3333
t.Errorf("Expected error but got none")
@@ -36,14 +36,9 @@ func TestNewCacheClient(t *testing.T) {
3636
if err != nil {
3737
t.Errorf("Unexpected error: %v", err)
3838
}
39-
if client == nil {
40-
t.Errorf("Expected client but got nil")
39+
if store == nil {
40+
t.Errorf("Expected store but got nil")
4141
}
42-
defer func() {
43-
if closeErr := client.Close(); closeErr != nil {
44-
t.Errorf("Failed to close client: %v", closeErr)
45-
}
46-
}()
4742
}
4843
})
4944
}
@@ -109,12 +104,13 @@ func TestCollaboratorsCacheKey(t *testing.T) {
109104
}
110105
}
111106

112-
func TestCacheClientClose(t *testing.T) {
107+
func TestClientClose(t *testing.T) {
113108
tmpDir := t.TempDir()
114-
client, err := NewCacheClient("test-token", tmpDir)
109+
store, err := NewCacheStore(tmpDir)
115110
if err != nil {
116-
t.Fatalf("Failed to create cache client: %v", err)
111+
t.Fatalf("Failed to create cache store: %v", err)
117112
}
113+
client := NewClient("test-token", WithCacheStore(store))
118114

119115
// Close should not error
120116
if err := client.Close(); err != nil {

pkg/prx/client_pullrequest_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,10 +135,11 @@ func TestClient_PullRequestWithCache(t *testing.T) {
135135
defer server.Close()
136136

137137
httpClient := &http.Client{Transport: http.DefaultTransport}
138-
client, err := NewCacheClient("test-token", tmpDir, WithHTTPClient(httpClient))
138+
store, err := NewCacheStore(tmpDir)
139139
if err != nil {
140-
t.Fatalf("Failed to create cache client: %v", err)
140+
t.Fatalf("Failed to create cache store: %v", err)
141141
}
142+
client := NewClient("test-token", WithCacheStore(store), WithHTTPClient(httpClient))
142143

143144
// Override the API URL
144145
client.github = &githubClient{

0 commit comments

Comments
 (0)