mirror of
https://tangled.org/evan.jarrett.net/at-container-registry
synced 2026-08-29 04:06:58 +00:00
1. Multiple registry domains + per-user domain preference
The biggest feature. The appview can serve several registry domains (e.g. buoy.cr, atcr.io),
and users can now pick which one shows up in their pull/push commands.
- Lexicon/record: adds registryDomain (and documents ociClient)
to the sailor profile (lexicons/.../profile.json, pkg/atproto/lexicon.go).
- DB: new registry_domain column on users (schema.sql + migration 0027),
with GetUserByDID/Handle reads, UpdateUserRegistryDomain writer,
and Jetstream caching it on profile updates (writes unconditionally so clearing propagates).
- UI/handlers: new UpdateRegistryDomainHandler + /api/profile/registry-domain route,
a <select> in the user settings panel (only shown when >1 domain configured), and resolveRegistryURL()
which falls back to the primary domain if the user's pref is stale/removed. Tests added for all of it.
2. default_hold_did removed → first managed_holds entry is the default
Consolidates two overlapping config fields into one. ServerConfig.DefaultHoldDID is gone;
PrimaryHoldDID() now returns managed_holds[0]. managed_holds is now REQUIRED.
Updated in config, validation, server wiring, test harness, example YAML, and the deploy template.
3. Admin long-running operations → generic background-job framework
New pkg/hold/admin/jobs.go introduces a reusable startJob/jobRegistry pattern
(a detached context.Background() job + a /admin/api/jobs/{key}/status polling endpoint).
This replaces the bespoke scan-backfill goroutine state machine, and now also wraps crew tier remap and crew import
all three previously looped synchronously on the request context and got 504'd/cancelled mid-run by the reverse proxy.
Forms switched from POST-redirect to htmx fragments (job_progress.html, job_result.html, crew_import_results.html)
the old crew_import_results.html page and scan_backfill_progress.html partial were deleted.
This is also captured as a new rule in CLAUDE.md.
4. Cascade-delete manifest on last-tag deletion
DeleteTagHandler now, after removing the last tag pointing to a digest, cascade-deletes the manifest itself
(PDS + DB + hold blob purge) — but only if it's not a child of a manifest list (multi-arch parent).
New GetTagDigest and ShouldCascadeDeleteManifest queries back it, plus cascade_delete_test.go.
Also switches tag rkey computation to the atproto.RepositoryTagToRKey helper.
5. Billing simplification
Drops the OwnerBadge config option (hold-owner supporter badge).
The user-profile template no longer special-cases an "owner" badge value (only "Captain").
Example tiers renamed to the nautical scheme (deckhand/bosun/quartermaster).
6. Build/deploy: go generate always runs via Make
make generate is now a phony target that always runs go generate ./... (regenerating cbor_gen, icon sprites, etc.),
and build-trixie depends on it. The deploy tooling (provision.go/update.go)
drops its own runGenerate calls since the Makefile handles it.
7. New cmd/firehose-tap tool (untracked)
A standalone CLI that subscribes to a com.atproto.sync.subscribeRepos endpoint and pretty-prints events,
with emphasis on Sync 1.1 compliance fields (per-op prev CIDs, commit prevData) and a --validate CI mode.
Fits with the recent "more sync1.1 compliant" commit.
65 lines
2.3 KiB
Go
65 lines
2.3 KiB
Go
package handlers
|
|
|
|
import (
|
|
"net/http"
|
|
"slices"
|
|
"strings"
|
|
|
|
"atcr.io/pkg/appview/db"
|
|
"atcr.io/pkg/appview/middleware"
|
|
)
|
|
|
|
// PageData contains common fields shared across all page templates
|
|
type PageData struct {
|
|
User *db.User // Logged-in user (nil if not logged in)
|
|
Query string // Search query from URL parameter
|
|
RegistryURL string // Docker registry domain (e.g., "buoy.cr")
|
|
SiteURL string // Website domain (e.g., "seamark.dev")
|
|
ClientName string // Brand name for templates (e.g., "AT Container Registry")
|
|
ClientShortName string // Brand name for templates (e.g., "ATCR")
|
|
OciClient string // Preferred OCI client for pull commands (e.g., "docker", "podman")
|
|
AIAdvisorEnabled bool // True when AI Image Advisor is available
|
|
SourceURL string // Source code URL for the footer "Source" link
|
|
CurrentPath string // Request path (used for OAuth return_to)
|
|
}
|
|
|
|
// resolveRegistryURL returns the user's preferred registry domain when it is
|
|
// non-empty and still one of the configured domains; otherwise the primary.
|
|
// This keeps a stale or removed preference from breaking the displayed commands.
|
|
func resolveRegistryURL(primary string, domains []string, pref string) string {
|
|
if pref != "" && slices.Contains(domains, pref) {
|
|
return pref
|
|
}
|
|
return primary
|
|
}
|
|
|
|
// NewPageData creates a PageData struct with common fields populated from the request
|
|
func NewPageData(r *http.Request, h *BaseUIHandler) PageData {
|
|
user := middleware.GetUser(r)
|
|
var ociClient, registryPref string
|
|
if user != nil {
|
|
ociClient = user.OciClient
|
|
registryPref = user.RegistryDomain
|
|
}
|
|
return PageData{
|
|
User: user,
|
|
Query: r.URL.Query().Get("q"),
|
|
RegistryURL: resolveRegistryURL(h.RegistryURL, h.RegistryDomains, registryPref),
|
|
SiteURL: h.SiteURL,
|
|
ClientName: h.ClientName,
|
|
ClientShortName: h.ClientShortName,
|
|
OciClient: ociClient,
|
|
AIAdvisorEnabled: h.AIAdvisorEnabled,
|
|
SourceURL: h.SourceURL,
|
|
CurrentPath: r.URL.RequestURI(),
|
|
}
|
|
}
|
|
|
|
// TrimRegistryURL removes http:// or https:// prefix from a URL
|
|
// for use in Docker commands where only the host:port is needed
|
|
func TrimRegistryURL(url string) string {
|
|
url = strings.TrimPrefix(url, "https://")
|
|
url = strings.TrimPrefix(url, "http://")
|
|
return url
|
|
}
|