mirror of
https://tangled.org/evan.jarrett.net/at-container-registry
synced 2026-06-08 16:22:36 +00:00
241 lines
7.6 KiB
Go
241 lines
7.6 KiB
Go
package handlers
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
|
|
"atcr.io/pkg/appview/db"
|
|
"atcr.io/pkg/appview/middleware"
|
|
"atcr.io/pkg/atproto"
|
|
"atcr.io/pkg/auth/oauth"
|
|
"github.com/bluesky-social/indigo/atproto/identity"
|
|
"github.com/bluesky-social/indigo/atproto/syntax"
|
|
"github.com/gorilla/mux"
|
|
)
|
|
|
|
// StarRepositoryHandler handles starring a repository
|
|
type StarRepositoryHandler struct {
|
|
DB *sql.DB
|
|
Directory identity.Directory
|
|
Refresher *oauth.Refresher
|
|
}
|
|
|
|
func (h *StarRepositoryHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
// Get authenticated user from middleware
|
|
user := middleware.GetUser(r)
|
|
if user == nil {
|
|
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
// Extract parameters
|
|
vars := mux.Vars(r)
|
|
handle := vars["handle"]
|
|
repository := vars["repository"]
|
|
|
|
// Resolve owner's handle to DID
|
|
ownerDID, err := resolveIdentityToDID(r.Context(), h.Directory, handle)
|
|
if err != nil {
|
|
log.Printf("StarRepository: Failed to resolve handle %s: %v", handle, err)
|
|
http.Error(w, fmt.Sprintf("Failed to resolve handle: %v", err), http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Get OAuth session for the authenticated user
|
|
log.Printf("StarRepository: Getting OAuth session for user DID %s", user.DID)
|
|
session, err := h.Refresher.GetSession(r.Context(), user.DID)
|
|
if err != nil {
|
|
log.Printf("StarRepository: Failed to get OAuth session for %s: %v", user.DID, err)
|
|
http.Error(w, fmt.Sprintf("Failed to get OAuth session: %v", err), http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
// Get user's PDS client (use indigo's API client which handles DPoP automatically)
|
|
apiClient := session.APIClient()
|
|
pdsClient := atproto.NewClientWithIndigoClient(user.PDSEndpoint, user.DID, apiClient)
|
|
|
|
// Create star record
|
|
starRecord := atproto.NewStarRecord(ownerDID, repository)
|
|
rkey := atproto.StarRecordKey(ownerDID, repository)
|
|
|
|
// Write star record to user's PDS
|
|
_, err = pdsClient.PutRecord(r.Context(), atproto.StarCollection, rkey, starRecord)
|
|
if err != nil {
|
|
log.Printf("StarRepository: Failed to create star record: %v", err)
|
|
http.Error(w, fmt.Sprintf("Failed to create star: %v", err), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// Return success
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusCreated)
|
|
json.NewEncoder(w).Encode(map[string]bool{"starred": true})
|
|
}
|
|
|
|
// UnstarRepositoryHandler handles unstarring a repository
|
|
type UnstarRepositoryHandler struct {
|
|
DB *sql.DB
|
|
Directory identity.Directory
|
|
Refresher *oauth.Refresher
|
|
}
|
|
|
|
func (h *UnstarRepositoryHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
// Get authenticated user from middleware
|
|
user := middleware.GetUser(r)
|
|
if user == nil {
|
|
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
// Extract parameters
|
|
vars := mux.Vars(r)
|
|
handle := vars["handle"]
|
|
repository := vars["repository"]
|
|
|
|
// Resolve owner's handle to DID
|
|
ownerDID, err := resolveIdentityToDID(r.Context(), h.Directory, handle)
|
|
if err != nil {
|
|
log.Printf("UnstarRepository: Failed to resolve handle %s: %v", handle, err)
|
|
http.Error(w, fmt.Sprintf("Failed to resolve handle: %v", err), http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Get OAuth session for the authenticated user
|
|
log.Printf("UnstarRepository: Getting OAuth session for user DID %s", user.DID)
|
|
session, err := h.Refresher.GetSession(r.Context(), user.DID)
|
|
if err != nil {
|
|
log.Printf("UnstarRepository: Failed to get OAuth session for %s: %v", user.DID, err)
|
|
http.Error(w, fmt.Sprintf("Failed to get OAuth session: %v", err), http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
// Get user's PDS client (use indigo's API client which handles DPoP automatically)
|
|
apiClient := session.APIClient()
|
|
pdsClient := atproto.NewClientWithIndigoClient(user.PDSEndpoint, user.DID, apiClient)
|
|
|
|
// Delete star record from user's PDS
|
|
rkey := atproto.StarRecordKey(ownerDID, repository)
|
|
log.Printf("UnstarRepository: Deleting star record for %s/%s (rkey: %s)", handle, repository, rkey)
|
|
err = pdsClient.DeleteRecord(r.Context(), atproto.StarCollection, rkey)
|
|
if err != nil {
|
|
// If record doesn't exist, still return success (idempotent)
|
|
if err.Error() != "record not found" {
|
|
log.Printf("UnstarRepository: Failed to delete star record: %v", err)
|
|
http.Error(w, fmt.Sprintf("Failed to delete star: %v", err), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
log.Printf("UnstarRepository: Star record not found (already unstarred)")
|
|
}
|
|
|
|
// Return success
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]bool{"starred": false})
|
|
}
|
|
|
|
// CheckStarHandler checks if current user has starred a repository
|
|
type CheckStarHandler struct {
|
|
DB *sql.DB
|
|
Directory identity.Directory
|
|
Refresher *oauth.Refresher
|
|
}
|
|
|
|
func (h *CheckStarHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
// Get authenticated user from middleware
|
|
user := middleware.GetUser(r)
|
|
if user == nil {
|
|
// Not authenticated - return not starred
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]bool{"starred": false})
|
|
return
|
|
}
|
|
|
|
// Extract parameters
|
|
vars := mux.Vars(r)
|
|
handle := vars["handle"]
|
|
repository := vars["repository"]
|
|
|
|
// Resolve owner's handle to DID
|
|
ownerDID, err := resolveIdentityToDID(r.Context(), h.Directory, handle)
|
|
if err != nil {
|
|
log.Printf("CheckStar: Failed to resolve handle %s: %v", handle, err)
|
|
http.Error(w, fmt.Sprintf("Failed to resolve handle: %v", err), http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Get OAuth session for the authenticated user
|
|
session, err := h.Refresher.GetSession(r.Context(), user.DID)
|
|
if err != nil {
|
|
log.Printf("CheckStar: Failed to get OAuth session for %s: %v", user.DID, err)
|
|
// No OAuth session - return not starred
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]bool{"starred": false})
|
|
return
|
|
}
|
|
|
|
// Get user's PDS client (use indigo's API client which handles DPoP automatically)
|
|
apiClient := session.APIClient()
|
|
pdsClient := atproto.NewClientWithIndigoClient(user.PDSEndpoint, user.DID, apiClient)
|
|
|
|
// Check if star record exists
|
|
rkey := atproto.StarRecordKey(ownerDID, repository)
|
|
_, err = pdsClient.GetRecord(r.Context(), atproto.StarCollection, rkey)
|
|
|
|
starred := err == nil
|
|
|
|
// Return result
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]bool{"starred": starred})
|
|
}
|
|
|
|
// GetStatsHandler returns repository statistics
|
|
type GetStatsHandler struct {
|
|
DB *sql.DB
|
|
Directory identity.Directory
|
|
}
|
|
|
|
func (h *GetStatsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
// Extract parameters
|
|
vars := mux.Vars(r)
|
|
handle := vars["handle"]
|
|
repository := vars["repository"]
|
|
|
|
// Resolve owner's handle to DID
|
|
ownerDID, err := resolveIdentityToDID(r.Context(), h.Directory, handle)
|
|
if err != nil {
|
|
http.Error(w, "Failed to resolve handle", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Get repository stats from database
|
|
stats, err := db.GetRepositoryStats(h.DB, ownerDID, repository)
|
|
if err != nil {
|
|
http.Error(w, "Failed to fetch stats", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// Return stats as JSON
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(stats)
|
|
}
|
|
|
|
// resolveIdentityToDID is a helper function that resolves a handle or DID to a DID
|
|
func resolveIdentityToDID(ctx context.Context, directory identity.Directory, identityStr string) (string, error) {
|
|
// Parse as AT identifier (handle or DID)
|
|
atID, err := syntax.ParseAtIdentifier(identityStr)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
// Resolve to DID via directory
|
|
ident, err := directory.Lookup(ctx, *atID)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return ident.DID.String(), nil
|
|
}
|