mirror of
https://tangled.org/evan.jarrett.net/at-container-registry
synced 2026-08-29 04:06:58 +00:00
remove unused files, add workflow for tests
This commit is contained in:
@@ -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 ./...
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user