From d41686c3401b362876ee97800a8a99d0d9b44e9e Mon Sep 17 00:00:00 2001 From: Evan Jarrett Date: Fri, 17 Oct 2025 17:16:09 -0500 Subject: [PATCH] remove unused files, add workflow for tests --- .tangled/workflows/tests.yml | 17 +++++++ pkg/hold/handlers.go | 6 --- pkg/hold/multipart.go | 71 ++++------------------------- pkg/hold/resolve.go | 88 ------------------------------------ 4 files changed, 26 insertions(+), 156 deletions(-) create mode 100644 .tangled/workflows/tests.yml delete mode 100644 pkg/hold/handlers.go delete mode 100644 pkg/hold/resolve.go diff --git a/.tangled/workflows/tests.yml b/.tangled/workflows/tests.yml new file mode 100644 index 0000000..9b04e59 --- /dev/null +++ b/.tangled/workflows/tests.yml @@ -0,0 +1,17 @@ +when: + - event: ["push"] + branch: ["main"] + - event: ["pull_request"] + branch: ["main"] + +engine: "nixery" + +dependencies: + nixpkgs: + - git + - go + +steps: + - name: Run Tests + command: | + go test -cover ./... \ No newline at end of file diff --git a/pkg/hold/handlers.go b/pkg/hold/handlers.go deleted file mode 100644 index 54c009e..0000000 --- a/pkg/hold/handlers.go +++ /dev/null @@ -1,6 +0,0 @@ -package hold - -// This file previously contained legacy HTTP handlers that have been replaced by XRPC endpoints. -// The handlers (HandleProxyGet, HandleProxyPut, HandleMultipartPartUpload) are no longer needed -// as all blob operations now go through the XRPC com.atproto.repo.uploadBlob and -// com.atproto.sync.getBlob endpoints. diff --git a/pkg/hold/multipart.go b/pkg/hold/multipart.go index ef8b663..1cedddc 100644 --- a/pkg/hold/multipart.go +++ b/pkg/hold/multipart.go @@ -5,9 +5,7 @@ import ( "crypto/sha256" "encoding/hex" "fmt" - "io" "log" - "net/http" "sync" "time" @@ -262,24 +260,18 @@ func (s *HoldService) StartMultipartUploadWithManager(ctx context.Context, diges return session.UploadID, Buffered, nil } -// GetPartUploadURL generates a URL for uploading a part -// For S3Native: returns presigned URL -// For Buffered: returns proxy endpoint +// GetPartUploadURL generates a presigned URL for uploading a part +// Only used for S3Native mode - Buffered mode is handled by blobstore adapter func (s *HoldService) GetPartUploadURL(ctx context.Context, session *MultipartSession, partNumber int, did string) (string, error) { - if session.Mode == S3Native { - // Generate S3 presigned URL for this part - url, err := s.getPartPresignedURL(ctx, session.Digest, session.S3UploadID, partNumber) - if err != nil { - return "", fmt.Errorf("failed to generate S3 part URL: %w", err) - } - return url, nil + if session.Mode != S3Native { + return "", fmt.Errorf("GetPartUploadURL only supports S3Native mode") } - // Buffered mode: return proxy endpoint - // url := fmt.Sprintf("%s/xrpc/com.atproto.repo.uploadBlob", s.config.Server.PublicURL) - - url := fmt.Sprintf("%s/multipart-parts/%s/%d?did=%s", - s.config.Server.PublicURL, session.UploadID, partNumber, did) + // Generate S3 presigned URL for this part + url, err := s.getPartPresignedURL(ctx, session.Digest, session.S3UploadID, partNumber) + if err != nil { + return "", fmt.Errorf("failed to generate S3 part URL: %w", err) + } return url, nil } @@ -342,48 +334,3 @@ func (s *HoldService) AbortMultipartUploadWithManager(ctx context.Context, sessi return nil } -// HandleMultipartPartUpload handles uploading a part in buffered mode -// This is a new endpoint: PUT /multipart-parts/{uploadID}/{partNumber} -func (s *HoldService) HandleMultipartPartUpload(w http.ResponseWriter, r *http.Request, uploadID string, partNumber int, did string, manager *MultipartManager) { - if r.Method != http.MethodPut { - http.Error(w, "method not allowed", http.StatusMethodNotAllowed) - return - } - - // Get session - session, err := manager.GetSession(uploadID) - if err != nil { - http.Error(w, fmt.Sprintf("session not found: %v", err), http.StatusNotFound) - return - } - - // Verify authorization - if !s.isAuthorizedWrite(did) { - if did == "" { - http.Error(w, "unauthorized: authentication required", http.StatusUnauthorized) - } else { - http.Error(w, "forbidden: write access denied", http.StatusForbidden) - } - return - } - - // Verify session is in buffered mode - if session.Mode != Buffered { - http.Error(w, "session is not in buffered mode", http.StatusBadRequest) - return - } - - // Read part data - data, err := io.ReadAll(r.Body) - if err != nil { - http.Error(w, fmt.Sprintf("failed to read part data: %v", err), http.StatusInternalServerError) - return - } - - // Store part and get ETag - etag := session.StorePart(partNumber, data) - - // Return ETag in response - w.Header().Set("ETag", etag) - w.WriteHeader(http.StatusOK) -} diff --git a/pkg/hold/resolve.go b/pkg/hold/resolve.go deleted file mode 100644 index 8a83330..0000000 --- a/pkg/hold/resolve.go +++ /dev/null @@ -1,88 +0,0 @@ -package hold - -import ( - "context" - "fmt" - "sync" - "time" - - "github.com/bluesky-social/indigo/atproto/identity" - "github.com/bluesky-social/indigo/atproto/syntax" -) - -// handleCache provides caching for DID → handle resolution -// This reduces latency for pattern matching authorization checks -type handleCache struct { - mu sync.RWMutex - cache map[string]cacheEntry // did → handle -} - -type cacheEntry struct { - handle string - expiresAt time.Time -} - -const handleCacheTTL = 10 * time.Minute - -var ( - // Global handle cache instance - globalHandleCache = &handleCache{ - cache: make(map[string]cacheEntry), - } -) - -// get retrieves a cached handle for a DID -func (c *handleCache) get(did string) (string, bool) { - c.mu.RLock() - defer c.mu.RUnlock() - - entry, ok := c.cache[did] - if !ok || time.Now().After(entry.expiresAt) { - return "", false - } - return entry.handle, true -} - -// set stores a handle in the cache -func (c *handleCache) set(did, handle string) { - c.mu.Lock() - defer c.mu.Unlock() - - c.cache[did] = cacheEntry{ - handle: handle, - expiresAt: time.Now().Add(handleCacheTTL), - } -} - -// resolveHandle resolves a DID to its current handle using ATProto identity resolution -// Results are cached for 10 minutes to reduce latency -func resolveHandle(did string) (string, error) { - // Check cache first - if handle, ok := globalHandleCache.get(did); ok { - return handle, nil - } - - // Cache miss - resolve from network - ctx := context.Background() - directory := identity.DefaultDirectory() - - didParsed, err := syntax.ParseDID(did) - if err != nil { - return "", fmt.Errorf("invalid DID: %w", err) - } - - ident, err := directory.LookupDID(ctx, didParsed) - if err != nil { - return "", fmt.Errorf("failed to resolve DID: %w", err) - } - - handle := ident.Handle.String() - if handle == "" || handle == "handle.invalid" { - return "", fmt.Errorf("no valid handle found for DID") - } - - // Cache the result - globalHandleCache.set(did, handle) - - return handle, nil -}