diff --git a/appview/repo/repo.go b/appview/repo/repo.go --- a/appview/repo/repo.go +++ b/appview/repo/repo.go @@ -970,7 +970,7 @@ rp.notifier.RenameRepo(r.Context(), syntax.DID(user.Did), &oldRepo, &newRepo) if newRkey != f.Rkey { - rp.migrateSiteOnRename(r.Context(), f, newRkey) + rp.migrateSiteOnRename(r.Context(), f, newName, newRkey) } rp.pages.HxLocation(w, fmt.Sprintf("/%s", f.RepoDid)) @@ -994,7 +994,7 @@ return newName, nil } -func (rp *Repo) migrateSiteOnRename(ctx context.Context, oldRepo *models.Repo, newRkey string) { +func (rp *Repo) migrateSiteOnRename(ctx context.Context, oldRepo *models.Repo, newName, newRkey string) { l := rp.logger.With("handler", "migrateSiteOnRename", "repo_did", oldRepo.RepoDid) siteConfig, err := db.GetRepoSiteConfig(rp.db, oldRepo.RepoDid) @@ -1011,27 +1011,32 @@ go func() { bgCtx := context.Background() oldRkey := oldRepo.Rkey + oldName := oldRepo.Name if err := sites.Delete(bgCtx, rp.cfClient, oldRepo.Did, oldRkey); err != nil { l.Error("sites: failed to delete old R2 prefix", "oldRkey", oldRkey, "err", err) } newRepo := *oldRepo + newRepo.Name = newName newRepo.Rkey = newRkey if deployErr := sites.Deploy(bgCtx, rp.cfClient, rp.config, &newRepo, siteConfig.Branch, siteConfig.Dir); deployErr != nil { l.Error("sites: redeploy after rename failed", "err", deployErr) } if ownerClaim != nil { - if err := sites.DeleteDomainMapping(bgCtx, rp.cfClient, ownerClaim.Domain, oldRkey); err != nil { - l.Error("sites: failed to remove old KV mapping", "oldRkey", oldRkey, "err", err) + // drop the old name's entry when the name actually changed. + if oldName != newName { + if err := sites.DeleteDomainMapping(bgCtx, rp.cfClient, ownerClaim.Domain, oldName); err != nil { + l.Error("sites: failed to remove old KV mapping", "oldName", oldName, "err", err) + } } - if err := sites.PutDomainMapping(bgCtx, rp.cfClient, ownerClaim.Domain, oldRepo.Did, newRkey, siteConfig.IsIndex); err != nil { - l.Error("sites: failed to write new KV mapping", "newRkey", newRkey, "err", err) + if err := sites.PutDomainMapping(bgCtx, rp.cfClient, ownerClaim.Domain, oldRepo.Did, newName, newRkey, siteConfig.IsIndex); err != nil { + l.Error("sites: failed to write new KV mapping", "newName", newName, "newRkey", newRkey, "err", err) } } - l.Info("sites: migrated on rename", "oldRkey", oldRkey, "newRkey", newRkey) + l.Info("sites: migrated on rename", "oldName", oldName, "oldRkey", oldRkey, "newName", newName, "newRkey", newRkey) }() } diff --git a/appview/repo/settings.go b/appview/repo/settings.go --- a/appview/repo/settings.go +++ b/appview/repo/settings.go @@ -196,8 +196,7 @@ host := fmt.Sprintf("%s://%s", scheme, f.Knot) xrpcc := &indigoxrpc.Client{Host: host} - repo := fmt.Sprintf("%s/%s", f.Did, f.Rkey) - xrpcBytes, err := tangled.RepoBranches(r.Context(), xrpcc, "", 0, repo) + xrpcBytes, err := tangled.RepoBranches(r.Context(), xrpcc, "", 0, f.RepoIdentifier()) if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { l.Error("failed to call XRPC repo.branches", "xrpcerr", xrpcerr, "err", err) rp.pages.Error503(w) @@ -318,7 +317,7 @@ } if deployErr == nil { - if err := sites.PutDomainMapping(ctx, rp.cfClient, ownerClaim.Domain, f.Did, f.Rkey, isIndex); err != nil { + if err := sites.PutDomainMapping(ctx, rp.cfClient, ownerClaim.Domain, f.Did, f.Name, f.Rkey, isIndex); err != nil { l.Error("sites: KV write failed", "domain", ownerClaim.Domain, "err", err) } rp.logger.Info("site deployed to r2", "repo", f.RepoIdentifier(), "is_index", isIndex) @@ -363,7 +362,7 @@ l.Error("sites: R2 delete failed", "repo", f.RepoIdentifier(), "err", err) } if ownerClaim != nil { - if err := sites.DeleteDomainMapping(ctx, rp.cfClient, ownerClaim.Domain, f.Rkey); err != nil { + if err := sites.DeleteDomainMapping(ctx, rp.cfClient, ownerClaim.Domain, f.Name); err != nil { l.Error("sites: KV delete failed", "domain", ownerClaim.Domain, "err", err) } } diff --git a/appview/sites/sites.go b/appview/sites/sites.go --- a/appview/sites/sites.go +++ b/appview/sites/sites.go @@ -21,10 +21,18 @@ ) // DomainMapping is the value stored in Workers KV, keyed by the bare domain. -// Repos maps repo name → is_index; at most one repo may have is_index = true. +// Repos maps repo name → RepoEntry; at most one repo may have IsIndex = true. type DomainMapping struct { - Did string `json:"did"` - Repos map[string]bool `json:"repos"` + Did string `json:"did"` + Repos map[string]RepoEntry `json:"repos"` +} + +// RepoEntry is the per-repo value within a DomainMapping. Rkey is the +// repository's atproto record key, which identifies the {did}/{rkey}/ +// prefix in R2 where the site's objects live. +type RepoEntry struct { + Rkey string `json:"rkey"` + IsIndex bool `json:"is_index"` } // getOrNewMapping fetches the existing KV entry for domain, or returns a @@ -35,28 +43,41 @@ return DomainMapping{}, fmt.Errorf("reading domain mapping for %q: %w", domain, err) } if raw == nil { - return DomainMapping{Did: did, Repos: make(map[string]bool)}, nil + return DomainMapping{Did: did, Repos: make(map[string]RepoEntry)}, nil } var m DomainMapping if err := json.Unmarshal(raw, &m); err != nil { return DomainMapping{}, fmt.Errorf("unmarshalling domain mapping for %q: %w", domain, err) } if m.Repos == nil { - m.Repos = make(map[string]bool) + m.Repos = make(map[string]RepoEntry) } return m, nil } // PutDomainMapping adds or updates a single repo entry within the per-domain // KV record. If isIndex is true, any previously indexed repo is demoted first. -func PutDomainMapping(ctx context.Context, cf *cloudflare.Client, domain, did, repo string, isIndex bool) error { +func PutDomainMapping(ctx context.Context, cf *cloudflare.Client, domain, did, repoName, repoRkey string, isIndex bool) error { m, err := getOrNewMapping(ctx, cf, domain, did) if err != nil { return err } m.Did = did - m.Repos[repo] = isIndex + + if isIndex { + for name, entry := range m.Repos { + if name == repoName { + continue + } + if entry.IsIndex { + entry.IsIndex = false + m.Repos[name] = entry + } + } + } + + m.Repos[repoName] = RepoEntry{Rkey: repoRkey, IsIndex: isIndex} val, err := json.Marshal(m) if err != nil { @@ -70,13 +91,13 @@ // DeleteDomainMapping removes a single repo from the per-domain KV record. // If it was the last repo, the key is deleted entirely. -func DeleteDomainMapping(ctx context.Context, cf *cloudflare.Client, domain, repo string) error { +func DeleteDomainMapping(ctx context.Context, cf *cloudflare.Client, domain, repoName string) error { m, err := getOrNewMapping(ctx, cf, domain, "") if err != nil { return err } - delete(m.Repos, repo) + delete(m.Repos, repoName) if len(m.Repos) == 0 { if err := cf.KVDelete(ctx, domain); err != nil {