mirror of
https://tangled.org/evan.jarrett.net/at-container-registry
synced 2026-08-29 04:06:58 +00:00
191 lines
5.5 KiB
Go
191 lines
5.5 KiB
Go
package handlers
|
|
|
|
import (
|
|
"log/slog"
|
|
"net/http"
|
|
|
|
"atcr.io/pkg/appview/db"
|
|
"atcr.io/pkg/appview/middleware"
|
|
"atcr.io/pkg/atproto"
|
|
"github.com/go-chi/chi/v5"
|
|
)
|
|
|
|
// UserPageHandler handles the public user page showing all images for a user
|
|
type UserPageHandler struct {
|
|
BaseUIHandler
|
|
}
|
|
|
|
func (h *UserPageHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
identifier := chi.URLParam(r, "handle")
|
|
|
|
// Resolve identifier (handle or DID) to canonical DID and current handle
|
|
did, resolvedHandle, pdsEndpoint, err := atproto.ResolveIdentity(r.Context(), identifier)
|
|
if err != nil {
|
|
RenderNotFound(w, r, &h.BaseUIHandler)
|
|
return
|
|
}
|
|
|
|
// Look up user by DID
|
|
viewedUser, err := db.GetUserByDID(h.ReadOnlyDB, did)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
hasProfile := true
|
|
if viewedUser == nil {
|
|
// Valid ATProto user but hasn't set up ATCR profile
|
|
hasProfile = false
|
|
viewedUser = &db.User{
|
|
DID: did,
|
|
Handle: resolvedHandle,
|
|
PDSEndpoint: pdsEndpoint,
|
|
// Avatar intentionally empty - template shows '?' placeholder
|
|
}
|
|
} else if viewedUser.Handle != resolvedHandle {
|
|
// Opportunistically update cached handle if it changed
|
|
_ = db.UpdateUserHandle(h.DB, did, resolvedHandle)
|
|
viewedUser.Handle = resolvedHandle
|
|
}
|
|
|
|
// Get current user DID for star state (empty string if not logged in)
|
|
var currentUserDID string
|
|
if user := middleware.GetUser(r); user != nil {
|
|
currentUserDID = user.DID
|
|
}
|
|
|
|
// Fetch repository cards. Track the error separately so the template can
|
|
// render a distinct error state ("couldn't load their images") rather
|
|
// than the empty profile copy ("no images yet"), which implies no push
|
|
// has ever happened.
|
|
var cardsErr bool
|
|
cards, err := db.GetUserRepoCards(h.ReadOnlyDB, viewedUser.DID, currentUserDID)
|
|
if err != nil {
|
|
slog.Error("user: fetch repo cards", "did", viewedUser.DID, "err", err)
|
|
cards = []db.RepoCardData{}
|
|
cardsErr = true
|
|
}
|
|
db.SetRegistryURL(cards, h.RegistryURL)
|
|
|
|
pageData := NewPageData(r, &h.BaseUIHandler)
|
|
db.SetOciClient(cards, pageData.OciClient)
|
|
|
|
// Check for supporter badge based on billing subscription
|
|
supporterBadge := h.BillingManager.GetSupporterBadge(viewedUser.DID)
|
|
|
|
// Build page meta
|
|
meta := NewPageMeta(
|
|
viewedUser.Handle+" - "+h.ClientShortName,
|
|
"Container images by "+viewedUser.Handle+" on "+h.ClientShortName+", the decentralized container registry",
|
|
).
|
|
WithCanonical("https://" + h.SiteURL + "/u/" + viewedUser.Handle).
|
|
WithOGImage("https://" + h.SiteURL + "/og/u/" + viewedUser.Handle).
|
|
WithOGType("profile").
|
|
WithSiteName(h.ClientShortName).
|
|
WithJSONLD(NewJSONLDProfilePage(h.SiteURL, viewedUser.Handle, viewedUser.Avatar))
|
|
|
|
data := struct {
|
|
PageData
|
|
Meta *PageMeta
|
|
ViewedUser *db.User // User whose page we're viewing
|
|
Repositories []db.RepoCardData
|
|
HasProfile bool
|
|
SupporterBadge string
|
|
HasError bool
|
|
}{
|
|
PageData: pageData,
|
|
Meta: meta,
|
|
ViewedUser: viewedUser,
|
|
Repositories: cards,
|
|
HasProfile: hasProfile,
|
|
SupporterBadge: supporterBadge,
|
|
HasError: cardsErr,
|
|
}
|
|
|
|
if err := h.Templates.ExecuteTemplate(w, "user", data); err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
}
|
|
|
|
// StarredRepositoriesHandler renders /u/{handle}/starred — the list of repos
|
|
// the viewed user has starred. Stars whose target repo no longer exists or is
|
|
// inaccessible are silently dropped by the underlying query.
|
|
type StarredRepositoriesHandler struct {
|
|
BaseUIHandler
|
|
}
|
|
|
|
func (h *StarredRepositoriesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
identifier := chi.URLParam(r, "handle")
|
|
|
|
did, resolvedHandle, pdsEndpoint, err := atproto.ResolveIdentity(r.Context(), identifier)
|
|
if err != nil {
|
|
RenderNotFound(w, r, &h.BaseUIHandler)
|
|
return
|
|
}
|
|
|
|
viewedUser, err := db.GetUserByDID(h.ReadOnlyDB, did)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
hasProfile := true
|
|
if viewedUser == nil {
|
|
hasProfile = false
|
|
viewedUser = &db.User{
|
|
DID: did,
|
|
Handle: resolvedHandle,
|
|
PDSEndpoint: pdsEndpoint,
|
|
}
|
|
} else if viewedUser.Handle != resolvedHandle {
|
|
_ = db.UpdateUserHandle(h.DB, did, resolvedHandle)
|
|
viewedUser.Handle = resolvedHandle
|
|
}
|
|
|
|
var currentUserDID string
|
|
if user := middleware.GetUser(r); user != nil {
|
|
currentUserDID = user.DID
|
|
}
|
|
|
|
var cardsErr bool
|
|
cards, err := db.GetStarredRepoCards(h.ReadOnlyDB, viewedUser.DID, currentUserDID)
|
|
if err != nil {
|
|
slog.Error("starred: fetch repo cards", "did", viewedUser.DID, "err", err)
|
|
cards = []db.RepoCardData{}
|
|
cardsErr = true
|
|
}
|
|
db.SetRegistryURL(cards, h.RegistryURL)
|
|
|
|
pageData := NewPageData(r, &h.BaseUIHandler)
|
|
db.SetOciClient(cards, pageData.OciClient)
|
|
|
|
meta := NewPageMeta(
|
|
viewedUser.Handle+"'s stars - "+h.ClientShortName,
|
|
"Repositories starred by "+viewedUser.Handle+" on "+h.ClientShortName+", the decentralized container registry",
|
|
).
|
|
WithCanonical("https://" + h.SiteURL + "/u/" + viewedUser.Handle + "/starred").
|
|
WithSiteName(h.ClientShortName)
|
|
|
|
data := struct {
|
|
PageData
|
|
Meta *PageMeta
|
|
ViewedUser *db.User
|
|
Repositories []db.RepoCardData
|
|
HasProfile bool
|
|
HasError bool
|
|
}{
|
|
PageData: pageData,
|
|
Meta: meta,
|
|
ViewedUser: viewedUser,
|
|
Repositories: cards,
|
|
HasProfile: hasProfile,
|
|
HasError: cardsErr,
|
|
}
|
|
|
|
if err := h.Templates.ExecuteTemplate(w, "user-starred", data); err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
}
|