remove unused files, add workflow for tests

This commit is contained in:
Evan Jarrett
2025-10-17 17:16:09 -05:00
parent 48414be75d
commit d41686c340
4 changed files with 26 additions and 156 deletions
+17
View File
@@ -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 ./...
-6
View File
@@ -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.
+9 -62
View File
@@ -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)
}
-88
View File
@@ -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
}